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