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