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