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