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