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