]> git.mxchange.org Git - friendica.git/blob - src/Module/Contact.php
1ca681a1eb1b86fbcda1a93ed6eb20307577b211
[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                                 '$about'          => BBCode::convertForUriId($contact['uri-id'] ?? 0, $contact['about'], BBCode::EXTERNAL),
613                                 '$about_label'    => DI::l10n()->t('About:'),
614                                 '$keywords'       => $contact['keywords'],
615                                 '$keywords_label' => DI::l10n()->t('Tags:'),
616                                 '$contact_action_button' => DI::l10n()->t('Actions'),
617                                 '$contact_actions'=> $contact_actions,
618                                 '$contact_status' => DI::l10n()->t('Status'),
619                                 '$contact_settings_label' => $contact_settings_label,
620                                 '$contact_profile_label' => DI::l10n()->t('Profile'),
621                                 '$allow_remote_self' => $allow_remote_self,
622                                 '$remote_self'       => ['remote_self',
623                                         DI::l10n()->t('Mirror postings from this contact'),
624                                         $contact['remote_self'],
625                                         DI::l10n()->t('Mark this contact as remote_self, this will cause friendica to repost new entries from this contact.'),
626                                         $remote_self_options
627                                 ],
628                         ]);
629
630                         $arr = ['contact' => $contact, 'output' => $o];
631
632                         Hook::callAll('contact_edit', $arr);
633
634                         return $arr['output'];
635                 }
636
637                 $sql_values = [local_user()];
638
639                 // @TODO: Replace with parameter from router
640                 $type = DI::args()->getArgv()[1] ?? '';
641
642                 switch ($type) {
643                         case 'blocked':
644                                 $sql_extra = " AND EXISTS(SELECT `id` from `user-contact` WHERE `contact`.`id` = `user-contact`.`cid` and `user-contact`.`uid` = ? and `user-contact`.`blocked`)";
645                                 // This makes the query look for contact.uid = 0
646                                 array_unshift($sql_values, 0);
647                                 break;
648                         case 'hidden':
649                                 $sql_extra = " AND `hidden` AND NOT `blocked` AND NOT `pending`";
650                                 break;
651                         case 'ignored':
652                                 $sql_extra = " AND EXISTS(SELECT `id` from `user-contact` WHERE `contact`.`id` = `user-contact`.`cid` and `user-contact`.`uid` = ? and `user-contact`.`ignored`)";
653                                 // This makes the query look for contact.uid = 0
654                                 array_unshift($sql_values, 0);
655                                 break;
656                         case 'archived':
657                                 $sql_extra = " AND `archive` AND NOT `blocked` AND NOT `pending`";
658                                 break;
659                         case 'pending':
660                                 $sql_extra = " AND `pending` AND NOT `archive` AND NOT `failed` AND ((`rel` = ?)
661                                         OR EXISTS (SELECT `id` FROM `intro` WHERE `contact-id` = `contact`.`id` AND NOT `ignore`))";
662                                 $sql_values[] = Model\Contact::SHARING;
663                                 break;
664                         default:
665                                 $sql_extra = " AND NOT `archive` AND NOT `blocked` AND NOT `pending`";
666                                 break;
667                 }
668
669                 if (isset($accounttypeid)) {
670                         $sql_extra .= " AND `contact-type` = ?";
671                         $sql_values[] = $accounttypeid;
672                 }
673
674                 $searching = false;
675                 $search_hdr = null;
676                 if ($search) {
677                         $searching = true;
678                         $search_hdr = $search;
679                         $search_txt = preg_quote($search);
680                         $sql_extra .= " AND (name REGEXP ? OR url REGEXP ? OR nick REGEXP ?)";
681                         $sql_values[] = $search_txt;
682                         $sql_values[] = $search_txt;
683                         $sql_values[] = $search_txt;
684                 }
685
686                 if ($nets) {
687                         $sql_extra .= " AND network = ? ";
688                         $sql_values[] = $nets;
689                 }
690
691                 switch ($rel) {
692                         case 'followers':
693                                 $sql_extra .= " AND `rel` IN (?, ?)";
694                                 $sql_values[] = Model\Contact::FOLLOWER;
695                                 $sql_values[] = Model\Contact::FRIEND;
696                                 break;
697                         case 'following':
698                                 $sql_extra .= " AND `rel` IN (?, ?)";
699                                 $sql_values[] = Model\Contact::SHARING;
700                                 $sql_values[] = Model\Contact::FRIEND;
701                                 break;
702                         case 'mutuals':
703                                 $sql_extra .= " AND `rel` = ?";
704                                 $sql_values[] = Model\Contact::FRIEND;
705                                 break;
706                 }
707
708                 if ($group) {
709                         $sql_extra = " AND EXISTS(SELECT `id` FROM `group_member` WHERE `gid` = ? AND `contact`.`id` = `contact-id`)";
710                         $sql_values[] = $group;
711                 }
712
713                 $total = 0;
714                 $stmt = DBA::p("SELECT COUNT(*) AS `total`
715                         FROM `contact`
716                         WHERE `uid` = ?
717                         AND `self` = 0
718                         AND NOT `deleted`
719                         $sql_extra
720                         " . Widget::unavailableNetworks(),
721                         $sql_values
722                 );
723                 if (DBA::isResult($stmt)) {
724                         $total = DBA::fetch($stmt)['total'];
725                 }
726                 DBA::close($stmt);
727
728                 $pager = new Pager(DI::l10n(), DI::args()->getQueryString());
729
730                 $sql_values[] = $pager->getStart();
731                 $sql_values[] = $pager->getItemsPerPage();
732
733                 $contacts = [];
734
735                 $stmt = DBA::p("SELECT *
736                         FROM `contact`
737                         WHERE `uid` = ?
738                         AND `self` = 0
739                         AND NOT `deleted`
740                         $sql_extra
741                         ORDER BY `name` ASC
742                         LIMIT ?, ?",
743                         $sql_values
744                 );
745                 while ($contact = DBA::fetch($stmt)) {
746                         $contact['blocked'] = Model\Contact\User::isBlocked($contact['id'], local_user());
747                         $contact['readonly'] = Model\Contact\User::isIgnored($contact['id'], local_user());
748                         $contacts[] = self::getContactTemplateVars($contact);
749                 }
750                 DBA::close($stmt);
751
752                 $tabs = [
753                         [
754                                 'label' => DI::l10n()->t('All Contacts'),
755                                 'url'   => 'contact',
756                                 'sel'   => !$type ? 'active' : '',
757                                 'title' => DI::l10n()->t('Show all contacts'),
758                                 'id'    => 'showall-tab',
759                                 'accesskey' => 'l',
760                         ],
761                         [
762                                 'label' => DI::l10n()->t('Pending'),
763                                 'url'   => 'contact/pending',
764                                 'sel'   => $type == 'pending' ? 'active' : '',
765                                 'title' => DI::l10n()->t('Only show pending contacts'),
766                                 'id'    => 'showpending-tab',
767                                 'accesskey' => 'p',
768                         ],
769                         [
770                                 'label' => DI::l10n()->t('Blocked'),
771                                 'url'   => 'contact/blocked',
772                                 'sel'   => $type == 'blocked' ? 'active' : '',
773                                 'title' => DI::l10n()->t('Only show blocked contacts'),
774                                 'id'    => 'showblocked-tab',
775                                 'accesskey' => 'b',
776                         ],
777                         [
778                                 'label' => DI::l10n()->t('Ignored'),
779                                 'url'   => 'contact/ignored',
780                                 'sel'   => $type == 'ignored' ? 'active' : '',
781                                 'title' => DI::l10n()->t('Only show ignored contacts'),
782                                 'id'    => 'showignored-tab',
783                                 'accesskey' => 'i',
784                         ],
785                         [
786                                 'label' => DI::l10n()->t('Archived'),
787                                 'url'   => 'contact/archived',
788                                 'sel'   => $type == 'archived' ? 'active' : '',
789                                 'title' => DI::l10n()->t('Only show archived contacts'),
790                                 'id'    => 'showarchived-tab',
791                                 'accesskey' => 'y',
792                         ],
793                         [
794                                 'label' => DI::l10n()->t('Hidden'),
795                                 'url'   => 'contact/hidden',
796                                 'sel'   => $type == 'hidden' ? 'active' : '',
797                                 'title' => DI::l10n()->t('Only show hidden contacts'),
798                                 'id'    => 'showhidden-tab',
799                                 'accesskey' => 'h',
800                         ],
801                         [
802                                 'label' => DI::l10n()->t('Groups'),
803                                 'url'   => 'group',
804                                 'sel'   => '',
805                                 'title' => DI::l10n()->t('Organize your contact groups'),
806                                 'id'    => 'contactgroups-tab',
807                                 'accesskey' => 'e',
808                         ],
809                 ];
810
811                 $tabs_tpl = Renderer::getMarkupTemplate('common_tabs.tpl');
812                 $tabs_html = Renderer::replaceMacros($tabs_tpl, ['$tabs' => $tabs]);
813
814                 switch ($rel) {
815                         case 'followers': $header = DI::l10n()->t('Followers'); break;
816                         case 'following': $header = DI::l10n()->t('Following'); break;
817                         case 'mutuals':   $header = DI::l10n()->t('Mutual friends'); break;
818                         default:          $header = DI::l10n()->t('Contacts');
819                 }
820
821                 switch ($type) {
822                         case 'pending':  $header .= ' - ' . DI::l10n()->t('Pending'); break;
823                         case 'blocked':  $header .= ' - ' . DI::l10n()->t('Blocked'); break;
824                         case 'hidden':   $header .= ' - ' . DI::l10n()->t('Hidden'); break;
825                         case 'ignored':  $header .= ' - ' . DI::l10n()->t('Ignored'); break;
826                         case 'archived': $header .= ' - ' . DI::l10n()->t('Archived'); break;
827                 }
828
829                 $header .= $nets ? ' - ' . ContactSelector::networkToName($nets) : '';
830
831                 $tpl = Renderer::getMarkupTemplate('contacts-template.tpl');
832                 $o .= Renderer::replaceMacros($tpl, [
833                         '$header'     => $header,
834                         '$tabs'       => $tabs_html,
835                         '$total'      => $total,
836                         '$search'     => $search_hdr,
837                         '$desc'       => DI::l10n()->t('Search your contacts'),
838                         '$finding'    => $searching ? DI::l10n()->t('Results for: %s', $search) : '',
839                         '$submit'     => DI::l10n()->t('Find'),
840                         '$cmd'        => DI::args()->getCommand(),
841                         '$contacts'   => $contacts,
842                         '$contact_drop_confirm' => DI::l10n()->t('Do you really want to delete this contact?'),
843                         'multiselect' => 1,
844                         '$batch_actions' => [
845                                 'contacts_batch_update'  => DI::l10n()->t('Update'),
846                                 'contacts_batch_block'   => DI::l10n()->t('Block') . '/' . DI::l10n()->t('Unblock'),
847                                 'contacts_batch_ignore'  => DI::l10n()->t('Ignore') . '/' . DI::l10n()->t('Unignore'),
848                                 'contacts_batch_archive' => DI::l10n()->t('Archive') . '/' . DI::l10n()->t('Unarchive'),
849                                 'contacts_batch_drop'    => DI::l10n()->t('Delete'),
850                         ],
851                         '$h_batch_actions' => DI::l10n()->t('Batch Actions'),
852                         '$paginate'   => $pager->renderFull($total),
853                 ]);
854
855                 return $o;
856         }
857
858         /**
859          * List of pages for the Contact TabBar
860          *
861          * Available Pages are 'Status', 'Profile', 'Contacts' and 'Common Friends'
862          *
863          * @param array $contact    The contact array
864          * @param int   $active_tab 1 if tab should be marked as active
865          *
866          * @return string HTML string of the contact page tabs buttons.
867          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
868          * @throws \ImagickException
869          */
870         public static function getTabsHTML(array $contact, int $active_tab)
871         {
872                 $cid = $pcid = $contact['id'];
873                 $data = Model\Contact::getPublicAndUserContactID($contact['id'], local_user());
874                 if (!empty($data['user']) && ($contact['id'] == $data['public'])) {
875                         $cid = $data['user'];
876                 } elseif (!empty($data['public'])) {
877                         $pcid = $data['public'];
878                 }
879
880                 // tabs
881                 $tabs = [
882                         [
883                                 'label' => DI::l10n()->t('Status'),
884                                 'url'   => 'contact/' . $pcid . '/conversations',
885                                 'sel'   => (($active_tab == self::TAB_CONVERSATIONS) ? 'active' : ''),
886                                 'title' => DI::l10n()->t('Conversations started by this contact'),
887                                 'id'    => 'status-tab',
888                                 'accesskey' => 'm',
889                         ],
890                         [
891                                 'label' => DI::l10n()->t('Posts and Comments'),
892                                 'url'   => 'contact/' . $pcid . '/posts',
893                                 'sel'   => (($active_tab == self::TAB_POSTS) ? 'active' : ''),
894                                 'title' => DI::l10n()->t('Status Messages and Posts'),
895                                 'id'    => 'posts-tab',
896                                 'accesskey' => 'p',
897                         ],
898                         [
899                                 'label' => DI::l10n()->t('Profile'),
900                                 'url'   => 'contact/' . $cid,
901                                 'sel'   => (($active_tab == self::TAB_PROFILE) ? 'active' : ''),
902                                 'title' => DI::l10n()->t('Profile Details'),
903                                 'id'    => 'profile-tab',
904                                 'accesskey' => 'o',
905                         ],
906                         ['label' => DI::l10n()->t('Contacts'),
907                                 'url'   => 'contact/' . $pcid . '/contacts',
908                                 'sel'   => (($active_tab == self::TAB_CONTACTS) ? 'active' : ''),
909                                 'title' => DI::l10n()->t('View all known contacts'),
910                                 'id'    => 'contacts-tab',
911                                 'accesskey' => 't'
912                         ],
913                 ];
914
915                 if (!empty($contact['network']) && in_array($contact['network'], [Protocol::FEED, Protocol::MAIL]) && ($cid != $pcid)) {
916                         $tabs[] = ['label' => DI::l10n()->t('Advanced'),
917                                 'url'   => 'contact/' . $cid . '/advanced/',
918                                 'sel'   => (($active_tab == self::TAB_ADVANCED) ? 'active' : ''),
919                                 'title' => DI::l10n()->t('Advanced Contact Settings'),
920                                 'id'    => 'advanced-tab',
921                                 'accesskey' => 'r'
922                         ];
923                 }
924
925                 $tab_tpl = Renderer::getMarkupTemplate('common_tabs.tpl');
926                 $tab_str = Renderer::replaceMacros($tab_tpl, ['$tabs' => $tabs]);
927
928                 return $tab_str;
929         }
930
931         public static function getConversationsHMTL($a, $contact_id, $update, $parent = 0)
932         {
933                 $o = '';
934
935                 if (!$update) {
936                         // We need the editor here to be able to reshare an item.
937                         if (local_user()) {
938                                 $x = [
939                                         'is_owner' => true,
940                                         'allow_location' => $a->user['allow_location'],
941                                         'default_location' => $a->user['default-location'],
942                                         'nickname' => $a->user['nickname'],
943                                         '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'),
944                                         'acl' => ACL::getFullSelectorHTML(DI::page(), $a->user, true),
945                                         'bang' => '',
946                                         'visitor' => 'block',
947                                         'profile_uid' => local_user(),
948                                 ];
949                                 $o = status_editor($a, $x, 0, true);
950                         }
951                 }
952
953                 $contact = DBA::selectFirst('contact', ['uid', 'url', 'id'], ['id' => $contact_id, 'deleted' => false]);
954
955                 if (!$update) {
956                         $o .= self::getTabsHTML($contact, self::TAB_CONVERSATIONS);
957                 }
958
959                 if (DBA::isResult($contact)) {
960                         if (!$update) {
961                                 $profiledata = Model\Contact::getByURLForUser($contact['url'], local_user());
962                                 DI::page()['aside'] = Widget\VCard::getHTML($profiledata);
963                         } else {
964                                 DI::page()['aside'] = '';
965                         }
966
967                         if ($contact['uid'] == 0) {
968                                 $o .= Model\Contact::getPostsFromId($contact['id'], true, $update, $parent);
969                         } else {
970                                 $o .= Model\Contact::getPostsFromUrl($contact['url'], true, $update, $parent);
971                         }
972                 }
973
974                 return $o;
975         }
976
977         private static function getPostsHTML($a, $contact_id)
978         {
979                 $contact = DBA::selectFirst('contact', ['uid', 'url', 'id'], ['id' => $contact_id, 'deleted' => false]);
980
981                 $o = self::getTabsHTML($contact, self::TAB_POSTS);
982
983                 if (DBA::isResult($contact)) {
984                         $profiledata = Model\Contact::getByURLForUser($contact['url'], local_user());
985
986                         if (local_user() && in_array($profiledata['network'], Protocol::FEDERATED)) {
987                                 $profiledata['remoteconnect'] = DI::baseUrl() . '/follow?url=' . urlencode($profiledata['url']);
988                         }
989
990                         DI::page()['aside'] = Widget\VCard::getHTML($profiledata);
991
992                         if ($contact['uid'] == 0) {
993                                 $o .= Model\Contact::getPostsFromId($contact['id']);
994                         } else {
995                                 $o .= Model\Contact::getPostsFromUrl($contact['url']);
996                         }
997                 }
998
999                 return $o;
1000         }
1001
1002         /**
1003          * Return the fields for the contact template
1004          *
1005          * @param array $contact Contact array
1006          * @return array Template fields
1007          */
1008         public static function getContactTemplateVars(array $contact)
1009         {
1010                 $alt_text = '';
1011
1012                 if (!empty($contact['url']) && isset($contact['uid']) && ($contact['uid'] == 0) && local_user()) {
1013                         $personal = Model\Contact::getByURL($contact['url'], false, ['uid', 'rel', 'self'], local_user());
1014                         if (!empty($personal)) {
1015                                 $contact['uid'] = $personal['uid'];
1016                                 $contact['rel'] = $personal['rel'];
1017                                 $contact['self'] = $personal['self'];
1018                         }
1019                 }
1020
1021                 if (!empty($contact['uid']) && !empty($contact['rel']) && local_user() == $contact['uid']) {
1022                         switch ($contact['rel']) {
1023                                 case Model\Contact::FRIEND:
1024                                         $alt_text = DI::l10n()->t('Mutual Friendship');
1025                                         break;
1026
1027                                 case Model\Contact::FOLLOWER;
1028                                         $alt_text = DI::l10n()->t('is a fan of yours');
1029                                         break;
1030
1031                                 case Model\Contact::SHARING;
1032                                         $alt_text = DI::l10n()->t('you are a fan of');
1033                                         break;
1034
1035                                 default:
1036                                         break;
1037                         }
1038                 }
1039
1040                 $url = Model\Contact::magicLinkByContact($contact);
1041
1042                 if (strpos($url, 'redir/') === 0) {
1043                         $sparkle = ' class="sparkle" ';
1044                 } else {
1045                         $sparkle = '';
1046                 }
1047
1048                 if ($contact['pending']) {
1049                         if (in_array($contact['rel'], [Model\Contact::FRIEND, Model\Contact::SHARING])) {
1050                                 $alt_text = DI::l10n()->t('Pending outgoing contact request');
1051                         } else {
1052                                 $alt_text = DI::l10n()->t('Pending incoming contact request');
1053                         }
1054                 }
1055
1056                 if ($contact['self']) {
1057                         $alt_text = DI::l10n()->t('This is you');
1058                         $url = $contact['url'];
1059                         $sparkle = '';
1060                 }
1061
1062                 return [
1063                         'id'           => $contact['id'],
1064                         'url'          => $url,
1065                         'img_hover'    => DI::l10n()->t('Visit %s\'s profile [%s]', $contact['name'], $contact['url']),
1066                         'photo_menu'   => Model\Contact::photoMenu($contact),
1067                         'thumb'        => Model\Contact::getThumb($contact, true),
1068                         'alt_text'     => $alt_text,
1069                         'name'         => $contact['name'],
1070                         'nick'         => $contact['nick'],
1071                         'details'      => $contact['location'],
1072                         'tags'         => $contact['keywords'],
1073                         'about'        => $contact['about'],
1074                         'account_type' => Model\Contact::getAccountType($contact),
1075                         'sparkle'      => $sparkle,
1076                         'itemurl'      => ($contact['addr'] ?? '') ?: $contact['url'],
1077                         'network'      => ContactSelector::networkToName($contact['network'], $contact['url'], $contact['protocol'], $contact['gsid']),
1078                 ];
1079         }
1080
1081         /**
1082          * Gives a array with actions which can performed to a given contact
1083          *
1084          * This includes actions like e.g. 'block', 'hide', 'archive', 'delete' and others
1085          *
1086          * @param array $contact Data about the Contact
1087          * @return array with contact related actions
1088          */
1089         private static function getContactActions($contact)
1090         {
1091                 $poll_enabled = in_array($contact['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::OSTATUS, Protocol::FEED, Protocol::MAIL]);
1092                 $contact_actions = [];
1093
1094                 // Provide friend suggestion only for Friendica contacts
1095                 if ($contact['network'] === Protocol::DFRN) {
1096                         $contact_actions['suggest'] = [
1097                                 'label' => DI::l10n()->t('Suggest friends'),
1098                                 'url'   => 'fsuggest/' . $contact['id'],
1099                                 'title' => '',
1100                                 'sel'   => '',
1101                                 'id'    => 'suggest',
1102                         ];
1103                 }
1104
1105                 if ($poll_enabled) {
1106                         $contact_actions['update'] = [
1107                                 'label' => DI::l10n()->t('Update now'),
1108                                 'url'   => 'contact/' . $contact['id'] . '/update',
1109                                 'title' => '',
1110                                 'sel'   => '',
1111                                 'id'    => 'update',
1112                         ];
1113                 }
1114
1115                 if (in_array($contact['network'], Protocol::NATIVE_SUPPORT)) {
1116                         $contact_actions['updateprofile'] = [
1117                                 'label' => DI::l10n()->t('Refetch contact data'),
1118                                 'url'   => 'contact/' . $contact['id'] . '/updateprofile',
1119                                 'title' => '',
1120                                 'sel'   => '',
1121                                 'id'    => 'updateprofile',
1122                         ];
1123                 }
1124
1125                 $contact_actions['block'] = [
1126                         'label' => (intval($contact['blocked']) ? DI::l10n()->t('Unblock') : DI::l10n()->t('Block')),
1127                         'url'   => 'contact/' . $contact['id'] . '/block',
1128                         'title' => DI::l10n()->t('Toggle Blocked status'),
1129                         'sel'   => (intval($contact['blocked']) ? 'active' : ''),
1130                         'id'    => 'toggle-block',
1131                 ];
1132
1133                 $contact_actions['ignore'] = [
1134                         'label' => (intval($contact['readonly']) ? DI::l10n()->t('Unignore') : DI::l10n()->t('Ignore')),
1135                         'url'   => 'contact/' . $contact['id'] . '/ignore',
1136                         'title' => DI::l10n()->t('Toggle Ignored status'),
1137                         'sel'   => (intval($contact['readonly']) ? 'active' : ''),
1138                         'id'    => 'toggle-ignore',
1139                 ];
1140
1141                 if ($contact['uid'] != 0) {
1142                         $contact_actions['archive'] = [
1143                                 'label' => (intval($contact['archive']) ? DI::l10n()->t('Unarchive') : DI::l10n()->t('Archive')),
1144                                 'url'   => 'contact/' . $contact['id'] . '/archive',
1145                                 'title' => DI::l10n()->t('Toggle Archive status'),
1146                                 'sel'   => (intval($contact['archive']) ? 'active' : ''),
1147                                 'id'    => 'toggle-archive',
1148                         ];
1149
1150                         $contact_actions['delete'] = [
1151                                 'label' => DI::l10n()->t('Delete'),
1152                                 'url'   => 'contact/' . $contact['id'] . '/drop',
1153                                 'title' => DI::l10n()->t('Delete contact'),
1154                                 'sel'   => '',
1155                                 'id'    => 'delete',
1156                         ];
1157                 }
1158
1159                 return $contact_actions;
1160         }
1161 }