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