]> git.mxchange.org Git - friendica.git/blob - src/Module/Contact.php
c3075febedb7523d6127032bffb3d57c21fe29f9
[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 static function post(array $parameters = [])
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 static function content(array $parameters = [], $update = 0)
230         {
231                 if (!local_user()) {
232                         return Login::form($_SERVER['REQUEST_URI']);
233                 }
234
235                 $a = DI::app();
236
237                 $search = Strings::escapeTags(trim($_GET['search'] ?? ''));
238                 $nets   = Strings::escapeTags(trim($_GET['nets']   ?? ''));
239                 $rel    = Strings::escapeTags(trim($_GET['rel']    ?? ''));
240                 $group  = Strings::escapeTags(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(Strings::escapeTags($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                 $total = 0;
672                 $stmt = DBA::p("SELECT COUNT(*) AS `total`
673                         FROM `contact`
674                         WHERE `uid` = ?
675                         AND `self` = 0
676                         AND NOT `deleted`
677                         $sql_extra
678                         " . Widget::unavailableNetworks(),
679                         $sql_values
680                 );
681                 if (DBA::isResult($stmt)) {
682                         $total = DBA::fetch($stmt)['total'];
683                 }
684                 DBA::close($stmt);
685
686                 $pager = new Pager(DI::l10n(), DI::args()->getQueryString());
687
688                 $sql_values[] = $pager->getStart();
689                 $sql_values[] = $pager->getItemsPerPage();
690
691                 $contacts = [];
692
693                 $stmt = DBA::p("SELECT *
694                         FROM `contact`
695                         WHERE `uid` = ?
696                         AND `self` = 0
697                         AND NOT `deleted`
698                         $sql_extra
699                         ORDER BY `name` ASC
700                         LIMIT ?, ?",
701                         $sql_values
702                 );
703                 while ($contact = DBA::fetch($stmt)) {
704                         $contact['blocked'] = Model\Contact\User::isBlocked($contact['id'], local_user());
705                         $contact['readonly'] = Model\Contact\User::isIgnored($contact['id'], local_user());
706                         $contacts[] = self::getContactTemplateVars($contact);
707                 }
708                 DBA::close($stmt);
709
710                 $tabs = [
711                         [
712                                 'label' => DI::l10n()->t('All Contacts'),
713                                 'url'   => 'contact',
714                                 'sel'   => !$type ? 'active' : '',
715                                 'title' => DI::l10n()->t('Show all contacts'),
716                                 'id'    => 'showall-tab',
717                                 'accesskey' => 'l',
718                         ],
719                         [
720                                 'label' => DI::l10n()->t('Pending'),
721                                 'url'   => 'contact/pending',
722                                 'sel'   => $type == 'pending' ? 'active' : '',
723                                 'title' => DI::l10n()->t('Only show pending contacts'),
724                                 'id'    => 'showpending-tab',
725                                 'accesskey' => 'p',
726                         ],
727                         [
728                                 'label' => DI::l10n()->t('Blocked'),
729                                 'url'   => 'contact/blocked',
730                                 'sel'   => $type == 'blocked' ? 'active' : '',
731                                 'title' => DI::l10n()->t('Only show blocked contacts'),
732                                 'id'    => 'showblocked-tab',
733                                 'accesskey' => 'b',
734                         ],
735                         [
736                                 'label' => DI::l10n()->t('Ignored'),
737                                 'url'   => 'contact/ignored',
738                                 'sel'   => $type == 'ignored' ? 'active' : '',
739                                 'title' => DI::l10n()->t('Only show ignored contacts'),
740                                 'id'    => 'showignored-tab',
741                                 'accesskey' => 'i',
742                         ],
743                         [
744                                 'label' => DI::l10n()->t('Archived'),
745                                 'url'   => 'contact/archived',
746                                 'sel'   => $type == 'archived' ? 'active' : '',
747                                 'title' => DI::l10n()->t('Only show archived contacts'),
748                                 'id'    => 'showarchived-tab',
749                                 'accesskey' => 'y',
750                         ],
751                         [
752                                 'label' => DI::l10n()->t('Hidden'),
753                                 'url'   => 'contact/hidden',
754                                 'sel'   => $type == 'hidden' ? 'active' : '',
755                                 'title' => DI::l10n()->t('Only show hidden contacts'),
756                                 'id'    => 'showhidden-tab',
757                                 'accesskey' => 'h',
758                         ],
759                         [
760                                 'label' => DI::l10n()->t('Groups'),
761                                 'url'   => 'group',
762                                 'sel'   => '',
763                                 'title' => DI::l10n()->t('Organize your contact groups'),
764                                 'id'    => 'contactgroups-tab',
765                                 'accesskey' => 'e',
766                         ],
767                 ];
768
769                 $tabs_tpl = Renderer::getMarkupTemplate('common_tabs.tpl');
770                 $tabs_html = Renderer::replaceMacros($tabs_tpl, ['$tabs' => $tabs]);
771
772                 switch ($rel) {
773                         case 'followers': $header = DI::l10n()->t('Followers'); break;
774                         case 'following': $header = DI::l10n()->t('Following'); break;
775                         case 'mutuals':   $header = DI::l10n()->t('Mutual friends'); break;
776                         default:          $header = DI::l10n()->t('Contacts');
777                 }
778
779                 switch ($type) {
780                         case 'pending':  $header .= ' - ' . DI::l10n()->t('Pending'); break;
781                         case 'blocked':  $header .= ' - ' . DI::l10n()->t('Blocked'); break;
782                         case 'hidden':   $header .= ' - ' . DI::l10n()->t('Hidden'); break;
783                         case 'ignored':  $header .= ' - ' . DI::l10n()->t('Ignored'); break;
784                         case 'archived': $header .= ' - ' . DI::l10n()->t('Archived'); break;
785                 }
786
787                 $header .= $nets ? ' - ' . ContactSelector::networkToName($nets) : '';
788
789                 $tpl = Renderer::getMarkupTemplate('contacts-template.tpl');
790                 $o .= Renderer::replaceMacros($tpl, [
791                         '$header'     => $header,
792                         '$tabs'       => $tabs_html,
793                         '$total'      => $total,
794                         '$search'     => $search_hdr,
795                         '$desc'       => DI::l10n()->t('Search your contacts'),
796                         '$finding'    => $searching ? DI::l10n()->t('Results for: %s', $search) : '',
797                         '$submit'     => DI::l10n()->t('Find'),
798                         '$cmd'        => DI::args()->getCommand(),
799                         '$contacts'   => $contacts,
800                         '$form_security_token'  => BaseModule::getFormSecurityToken('contact_batch_actions'),
801                         'multiselect' => 1,
802                         '$batch_actions' => [
803                                 'contacts_batch_update'  => DI::l10n()->t('Update'),
804                                 'contacts_batch_block'   => DI::l10n()->t('Block') . '/' . DI::l10n()->t('Unblock'),
805                                 'contacts_batch_ignore'  => DI::l10n()->t('Ignore') . '/' . DI::l10n()->t('Unignore'),
806                         ],
807                         '$h_batch_actions' => DI::l10n()->t('Batch Actions'),
808                         '$paginate'   => $pager->renderFull($total),
809                 ]);
810
811                 return $o;
812         }
813
814         /**
815          * List of pages for the Contact TabBar
816          *
817          * Available Pages are 'Status', 'Profile', 'Contacts' and 'Common Friends'
818          *
819          * @param array $contact    The contact array
820          * @param int   $active_tab 1 if tab should be marked as active
821          *
822          * @return string HTML string of the contact page tabs buttons.
823          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
824          * @throws \ImagickException
825          */
826         public static function getTabsHTML(array $contact, int $active_tab)
827         {
828                 $cid = $pcid = $contact['id'];
829                 $data = Model\Contact::getPublicAndUserContactID($contact['id'], local_user());
830                 if (!empty($data['user']) && ($contact['id'] == $data['public'])) {
831                         $cid = $data['user'];
832                 } elseif (!empty($data['public'])) {
833                         $pcid = $data['public'];
834                 }
835
836                 // tabs
837                 $tabs = [
838                         [
839                                 'label' => DI::l10n()->t('Status'),
840                                 'url'   => 'contact/' . $pcid . '/conversations',
841                                 'sel'   => (($active_tab == self::TAB_CONVERSATIONS) ? 'active' : ''),
842                                 'title' => DI::l10n()->t('Conversations started by this contact'),
843                                 'id'    => 'status-tab',
844                                 'accesskey' => 'm',
845                         ],
846                         [
847                                 'label' => DI::l10n()->t('Posts and Comments'),
848                                 'url'   => 'contact/' . $pcid . '/posts',
849                                 'sel'   => (($active_tab == self::TAB_POSTS) ? 'active' : ''),
850                                 'title' => DI::l10n()->t('Status Messages and Posts'),
851                                 'id'    => 'posts-tab',
852                                 'accesskey' => 'p',
853                         ],
854                         [
855                                 'label' => DI::l10n()->t('Media'),
856                                 'url'   => 'contact/' . $pcid . '/media',
857                                 'sel'   => (($active_tab == self::TAB_MEDIA) ? 'active' : ''),
858                                 'title' => DI::l10n()->t('Posts containing media objects'),
859                                 'id'    => 'media-tab',
860                                 'accesskey' => 'd',
861                         ],
862                         [
863                                 'label' => DI::l10n()->t('Profile'),
864                                 'url'   => 'contact/' . $cid,
865                                 'sel'   => (($active_tab == self::TAB_PROFILE) ? 'active' : ''),
866                                 'title' => DI::l10n()->t('Profile Details'),
867                                 'id'    => 'profile-tab',
868                                 'accesskey' => 'o',
869                         ],
870                         ['label' => DI::l10n()->t('Contacts'),
871                                 'url'   => 'contact/' . $pcid . '/contacts',
872                                 'sel'   => (($active_tab == self::TAB_CONTACTS) ? 'active' : ''),
873                                 'title' => DI::l10n()->t('View all known contacts'),
874                                 'id'    => 'contacts-tab',
875                                 'accesskey' => 't'
876                         ],
877                 ];
878
879                 if (!empty($contact['network']) && in_array($contact['network'], [Protocol::FEED, Protocol::MAIL]) && ($cid != $pcid)) {
880                         $tabs[] = ['label' => DI::l10n()->t('Advanced'),
881                                 'url'   => 'contact/' . $cid . '/advanced/',
882                                 'sel'   => (($active_tab == self::TAB_ADVANCED) ? 'active' : ''),
883                                 'title' => DI::l10n()->t('Advanced Contact Settings'),
884                                 'id'    => 'advanced-tab',
885                                 'accesskey' => 'r'
886                         ];
887                 }
888
889                 $tab_tpl = Renderer::getMarkupTemplate('common_tabs.tpl');
890                 $tab_str = Renderer::replaceMacros($tab_tpl, ['$tabs' => $tabs]);
891
892                 return $tab_str;
893         }
894
895         public static function getConversationsHMTL($a, $contact_id, $update, $parent = 0)
896         {
897                 $o = '';
898
899                 if (!$update) {
900                         // We need the editor here to be able to reshare an item.
901                         if (local_user()) {
902                                 $o = DI::conversation()->statusEditor([], 0, true);
903                         }
904                 }
905
906                 $contact = DBA::selectFirst('contact', ['uid', 'url', 'id'], ['id' => $contact_id, 'deleted' => false]);
907
908                 if (!$update) {
909                         $o .= self::getTabsHTML($contact, self::TAB_CONVERSATIONS);
910                 }
911
912                 if (DBA::isResult($contact)) {
913                         if (!$update) {
914                                 $profiledata = Model\Contact::getByURLForUser($contact['url'], local_user());
915                                 DI::page()['aside'] = Widget\VCard::getHTML($profiledata);
916                         } else {
917                                 DI::page()['aside'] = '';
918                         }
919
920                         if ($contact['uid'] == 0) {
921                                 $o .= Model\Contact::getPostsFromId($contact['id'], true, $update, $parent);
922                         } else {
923                                 $o .= Model\Contact::getPostsFromUrl($contact['url'], true, $update, $parent);
924                         }
925                 }
926
927                 return $o;
928         }
929
930         private static function getPostsHTML(int $contact_id)
931         {
932                 $contact = DBA::selectFirst('contact', ['uid', 'url', 'id'], ['id' => $contact_id, 'deleted' => false]);
933
934                 $o = self::getTabsHTML($contact, self::TAB_POSTS);
935
936                 if (DBA::isResult($contact)) {
937                         $profiledata = Model\Contact::getByURLForUser($contact['url'], local_user());
938
939                         if (local_user() && in_array($profiledata['network'], Protocol::FEDERATED)) {
940                                 $profiledata['remoteconnect'] = DI::baseUrl() . '/follow?url=' . urlencode($profiledata['url']);
941                         }
942
943                         DI::page()['aside'] = Widget\VCard::getHTML($profiledata);
944
945                         if ($contact['uid'] == 0) {
946                                 $o .= Model\Contact::getPostsFromId($contact['id']);
947                         } else {
948                                 $o .= Model\Contact::getPostsFromUrl($contact['url']);
949                         }
950                 }
951
952                 return $o;
953         }
954
955         /**
956          * Return the fields for the contact template
957          *
958          * @param array $contact Contact array
959          * @return array Template fields
960          */
961         public static function getContactTemplateVars(array $contact)
962         {
963                 $alt_text = '';
964
965                 if (!empty($contact['url']) && isset($contact['uid']) && ($contact['uid'] == 0) && local_user()) {
966                         $personal = Model\Contact::getByURL($contact['url'], false, ['uid', 'rel', 'self'], local_user());
967                         if (!empty($personal)) {
968                                 $contact['uid'] = $personal['uid'];
969                                 $contact['rel'] = $personal['rel'];
970                                 $contact['self'] = $personal['self'];
971                         }
972                 }
973
974                 if (!empty($contact['uid']) && !empty($contact['rel']) && local_user() == $contact['uid']) {
975                         switch ($contact['rel']) {
976                                 case Model\Contact::FRIEND:
977                                         $alt_text = DI::l10n()->t('Mutual Friendship');
978                                         break;
979
980                                 case Model\Contact::FOLLOWER;
981                                         $alt_text = DI::l10n()->t('is a fan of yours');
982                                         break;
983
984                                 case Model\Contact::SHARING;
985                                         $alt_text = DI::l10n()->t('you are a fan of');
986                                         break;
987
988                                 default:
989                                         break;
990                         }
991                 }
992
993                 $url = Model\Contact::magicLinkByContact($contact);
994
995                 if (strpos($url, 'redir/') === 0) {
996                         $sparkle = ' class="sparkle" ';
997                 } else {
998                         $sparkle = '';
999                 }
1000
1001                 if ($contact['pending']) {
1002                         if (in_array($contact['rel'], [Model\Contact::FRIEND, Model\Contact::SHARING])) {
1003                                 $alt_text = DI::l10n()->t('Pending outgoing contact request');
1004                         } else {
1005                                 $alt_text = DI::l10n()->t('Pending incoming contact request');
1006                         }
1007                 }
1008
1009                 if ($contact['self']) {
1010                         $alt_text = DI::l10n()->t('This is you');
1011                         $url = $contact['url'];
1012                         $sparkle = '';
1013                 }
1014
1015                 return [
1016                         'id'           => $contact['id'],
1017                         'url'          => $url,
1018                         'img_hover'    => DI::l10n()->t('Visit %s\'s profile [%s]', $contact['name'], $contact['url']),
1019                         'photo_menu'   => Model\Contact::photoMenu($contact),
1020                         'thumb'        => Model\Contact::getThumb($contact, true),
1021                         'alt_text'     => $alt_text,
1022                         'name'         => $contact['name'],
1023                         'nick'         => $contact['nick'],
1024                         'details'      => $contact['location'],
1025                         'tags'         => $contact['keywords'],
1026                         'about'        => $contact['about'],
1027                         'account_type' => Model\Contact::getAccountType($contact),
1028                         'sparkle'      => $sparkle,
1029                         'itemurl'      => ($contact['addr'] ?? '') ?: $contact['url'],
1030                         'network'      => ContactSelector::networkToName($contact['network'], $contact['url'], $contact['protocol'], $contact['gsid']),
1031                 ];
1032         }
1033
1034         /**
1035          * Gives a array with actions which can performed to a given contact
1036          *
1037          * This includes actions like e.g. 'block', 'hide', 'delete' and others
1038          *
1039          * @param array $contact Data about the Contact
1040          * @return array with contact related actions
1041          */
1042         private static function getContactActions($contact)
1043         {
1044                 $poll_enabled = in_array($contact['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::OSTATUS, Protocol::FEED, Protocol::MAIL]);
1045                 $contact_actions = [];
1046
1047                 $formSecurityToken = self::getFormSecurityToken('contact_action');
1048
1049                 // Provide friend suggestion only for Friendica contacts
1050                 if ($contact['network'] === Protocol::DFRN) {
1051                         $contact_actions['suggest'] = [
1052                                 'label' => DI::l10n()->t('Suggest friends'),
1053                                 'url'   => 'fsuggest/' . $contact['id'],
1054                                 'title' => '',
1055                                 'sel'   => '',
1056                                 'id'    => 'suggest',
1057                         ];
1058                 }
1059
1060                 if ($poll_enabled) {
1061                         $contact_actions['update'] = [
1062                                 'label' => DI::l10n()->t('Update now'),
1063                                 'url'   => 'contact/' . $contact['id'] . '/update?t=' . $formSecurityToken,
1064                                 'title' => '',
1065                                 'sel'   => '',
1066                                 'id'    => 'update',
1067                         ];
1068                 }
1069
1070                 if (in_array($contact['network'], Protocol::NATIVE_SUPPORT)) {
1071                         $contact_actions['updateprofile'] = [
1072                                 'label' => DI::l10n()->t('Refetch contact data'),
1073                                 'url'   => 'contact/' . $contact['id'] . '/updateprofile?t=' . $formSecurityToken,
1074                                 'title' => '',
1075                                 'sel'   => '',
1076                                 'id'    => 'updateprofile',
1077                         ];
1078                 }
1079
1080                 $contact_actions['block'] = [
1081                         'label' => (intval($contact['blocked']) ? DI::l10n()->t('Unblock') : DI::l10n()->t('Block')),
1082                         'url'   => 'contact/' . $contact['id'] . '/block?t=' . $formSecurityToken,
1083                         'title' => DI::l10n()->t('Toggle Blocked status'),
1084                         'sel'   => (intval($contact['blocked']) ? 'active' : ''),
1085                         'id'    => 'toggle-block',
1086                 ];
1087
1088                 $contact_actions['ignore'] = [
1089                         'label' => (intval($contact['readonly']) ? DI::l10n()->t('Unignore') : DI::l10n()->t('Ignore')),
1090                         'url'   => 'contact/' . $contact['id'] . '/ignore?t=' . $formSecurityToken,
1091                         'title' => DI::l10n()->t('Toggle Ignored status'),
1092                         'sel'   => (intval($contact['readonly']) ? 'active' : ''),
1093                         'id'    => 'toggle-ignore',
1094                 ];
1095
1096                 if ($contact['uid'] != 0 && Protocol::supportsRevokeFollow($contact['network']) && in_array($contact['rel'], [Model\Contact::FOLLOWER, Model\Contact::FRIEND])) {
1097                         $contact_actions['revoke_follow'] = [
1098                                 'label' => DI::l10n()->t('Revoke Follow'),
1099                                 'url'   => 'contact/' . $contact['id'] . '/revoke',
1100                                 'title' => DI::l10n()->t('Revoke the follow from this contact'),
1101                                 'sel'   => '',
1102                                 'id'    => 'revoke_follow',
1103                         ];
1104                 }
1105
1106                 return $contact_actions;
1107         }
1108 }