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