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