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