]> git.mxchange.org Git - friendica.git/blob - src/Module/Contact/Profile.php
Apply suggestions from code review
[friendica.git] / src / Module / Contact / Profile.php
1 <?php
2 /**
3  * @copyright Copyright (C) 2010-2023, 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\Contact;
23
24 use Friendica\App;
25 use Friendica\BaseModule;
26 use Friendica\Contact\LocalRelationship;
27 use Friendica\Content\ContactSelector;
28 use Friendica\Content\Nav;
29 use Friendica\Content\Text\BBCode;
30 use Friendica\Content\Widget;
31 use Friendica\Core\Config\Capability\IManageConfigValues;
32 use Friendica\Core\Hook;
33 use Friendica\Core\L10n;
34 use Friendica\Core\Protocol;
35 use Friendica\Core\Renderer;
36 use Friendica\Core\Session\Capability\IHandleUserSessions;
37 use Friendica\Database\Database;
38 use Friendica\Database\DBA;
39 use Friendica\Model\Circle;
40 use Friendica\Model\Contact;
41 use Friendica\Module;
42 use Friendica\Module\Response;
43 use Friendica\Navigation\SystemMessages;
44 use Friendica\Network\HTTPException;
45 use Friendica\User\Settings;
46 use Friendica\Util\DateTimeFormat;
47 use Friendica\Util\Profiler;
48 use Psr\Log\LoggerInterface;
49
50 /**
51  *  Show a contact profile
52  */
53 class Profile extends BaseModule
54 {
55         /** @var LocalRelationship\Repository\LocalRelationship */
56         private $localRelationship;
57         /** @var App\Page */
58         private $page;
59         /** @var IManageConfigValues */
60         private $config;
61         /** @var IHandleUserSessions */
62         private $session;
63         /** @var SystemMessages */
64         private $systemMessages;
65         /** @var Database */
66         private $db;
67         /** @var Settings\Repository\UserGServer */
68         private $userGServer;
69
70         public function __construct(Settings\Repository\UserGServer $userGServer, Database $db, SystemMessages $systemMessages, IHandleUserSessions $session, L10n $l10n, LocalRelationship\Repository\LocalRelationship $localRelationship, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, App\Page $page, IManageConfigValues $config, array $server, array $parameters = [])
71         {
72                 parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
73
74                 $this->localRelationship = $localRelationship;
75                 $this->page              = $page;
76                 $this->config            = $config;
77                 $this->session           = $session;
78                 $this->systemMessages    = $systemMessages;
79                 $this->db                = $db;
80                 $this->userGServer       = $userGServer;
81         }
82
83         protected function post(array $request = [])
84         {
85                 if (!$this->session->getLocalUserId()) {
86                         return;
87                 }
88
89                 $contact_id = $this->parameters['id'];
90
91                 // Backward compatibility: The update still needs a user-specific contact ID
92                 // Change to user-contact table check by version 2022.03
93                 $cdata = Contact::getPublicAndUserContactID($contact_id, $this->session->getLocalUserId());
94                 if (empty($cdata['user']) || !$this->db->exists('contact', ['id' => $cdata['user'], 'deleted' => false])) {
95                         return;
96                 }
97
98                 Hook::callAll('contact_edit_post', $_POST);
99
100                 $fields = [];
101
102                 if (isset($_POST['hidden'])) {
103                         $fields['hidden'] = !empty($_POST['hidden']);
104                 }
105
106                 if (isset($_POST['notify_new_posts'])) {
107                         $fields['notify_new_posts'] = !empty($_POST['notify_new_posts']);
108                 }
109
110                 if (isset($_POST['fetch_further_information'])) {
111                         $fields['fetch_further_information'] = intval($_POST['fetch_further_information']);
112                 }
113
114                 if (isset($_POST['remote_self'])) {
115                         $fields['remote_self'] = intval($_POST['remote_self']);
116                 }
117
118                 if (isset($_POST['ffi_keyword_denylist'])) {
119                         $fields['ffi_keyword_denylist'] = $_POST['ffi_keyword_denylist'];
120                 }
121
122                 if (isset($_POST['poll'])) {
123                         $priority = intval($_POST['poll']);
124                         if ($priority > 5 || $priority < 0) {
125                                 $priority = 0;
126                         }
127
128                         $fields['priority'] = $priority;
129                 }
130
131                 if (isset($_POST['info'])) {
132                         $fields['info'] = $_POST['info'];
133                 }
134
135                 if (isset($_POST['channel_visibility'])) {
136                         Contact\User::setChannelVisibility($cdata['user'], $this->session->getLocalUserId(), $_POST['channel_visibility']);
137                 }
138
139                 if (!Contact::update($fields, ['id' => $cdata['user'], 'uid' => $this->session->getLocalUserId()])) {
140                         $this->systemMessages->addNotice($this->t('Failed to update contact record.'));
141                 }
142         }
143
144         protected function content(array $request = []): string
145         {
146                 if (!$this->session->getLocalUserId()) {
147                         return Module\Security\Login::form($_SERVER['REQUEST_URI']);
148                 }
149
150                 // Backward compatibility: Ensure to use the public contact when the user contact is provided
151                 // Remove by version 2022.03
152                 $data = Contact::getPublicAndUserContactID(intval($this->parameters['id']), $this->session->getLocalUserId());
153                 if (empty($data)) {
154                         throw new HTTPException\NotFoundException($this->t('Contact not found.'));
155                 }
156
157                 $contact = Contact::getById($data['public']);
158                 if (!$this->db->isResult($contact)) {
159                         throw new HTTPException\NotFoundException($this->t('Contact not found.'));
160                 }
161
162                 // Don't display contacts that are about to be deleted
163                 if ($this->db->isResult($contact) && (!empty($contact['deleted']) || !empty($contact['network']) && $contact['network'] == Protocol::PHANTOM)) {
164                         throw new HTTPException\NotFoundException($this->t('Contact not found.'));
165                 }
166
167                 $localRelationship = $this->localRelationship->getForUserContact($this->session->getLocalUserId(), $contact['id']);
168
169                 if ($localRelationship->rel === Contact::SELF) {
170                         $this->baseUrl->redirect('profile/' . $contact['nick'] . '/profile');
171                 }
172
173                 if (isset($this->parameters['action'])) {
174                         self::checkFormSecurityTokenRedirectOnError('contact/' . $contact['id'], 'contact_action', 't');
175
176                         $cmd = $this->parameters['action'];
177                         if ($cmd === 'update' && $localRelationship->rel !== Contact::NOTHING) {
178                                 Module\Contact::updateContactFromPoll($contact['id']);
179                         }
180
181                         if ($cmd === 'updateprofile') {
182                                 $this->updateContactFromProbe($contact['id']);
183                         }
184
185                         if ($cmd === 'block') {
186                                 if ($localRelationship->blocked) {
187                                         // @TODO Backward compatibility, replace with $localRelationship->unblock()
188                                         Contact\User::setBlocked($contact['id'], $this->session->getLocalUserId(), false);
189
190                                         $message = $this->t('Contact has been unblocked');
191                                 } else {
192                                         // @TODO Backward compatibility, replace with $localRelationship->block()
193                                         Contact\User::setBlocked($contact['id'], $this->session->getLocalUserId(), true);
194                                         $message = $this->t('Contact has been blocked');
195                                 }
196
197                                 // @TODO: add $this->localRelationship->save($localRelationship);
198                                 $this->systemMessages->addInfo($message);
199                         }
200
201                         if ($cmd === 'ignore') {
202                                 if ($localRelationship->ignored) {
203                                         // @TODO Backward compatibility, replace with $localRelationship->unblock()
204                                         Contact\User::setIgnored($contact['id'], $this->session->getLocalUserId(), false);
205
206                                         $message = $this->t('Contact has been unignored');
207                                 } else {
208                                         // @TODO Backward compatibility, replace with $localRelationship->block()
209                                         Contact\User::setIgnored($contact['id'], $this->session->getLocalUserId(), true);
210                                         $message = $this->t('Contact has been ignored');
211                                 }
212
213                                 // @TODO: add $this->localRelationship->save($localRelationship);
214                                 $this->systemMessages->addInfo($message);
215                         }
216
217                         if ($cmd === 'collapse') {
218                                 if ($localRelationship->collapsed) {
219                                         // @TODO Backward compatibility, replace with $localRelationship->unblock()
220                                         Contact\User::setCollapsed($contact['id'], $this->session->getLocalUserId(), false);
221
222                                         $message = $this->t('Contact has been uncollapsed');
223                                 } else {
224                                         // @TODO Backward compatibility, replace with $localRelationship->block()
225                                         Contact\User::setCollapsed($contact['id'], $this->session->getLocalUserId(), true);
226                                         $message = $this->t('Contact has been collapsed');
227                                 }
228
229                                 // @TODO: add $this->localRelationship->save($localRelationship);
230                                 $this->systemMessages->addInfo($message);
231                         }
232
233                         $this->baseUrl->redirect('contact/' . $contact['id']);
234                 }
235
236                 $vcard_widget  = Widget\VCard::getHTML($contact);
237                 $circles_widget = '';
238
239                 if (!in_array($localRelationship->rel, [Contact::NOTHING, Contact::SELF])) {
240                         $circles_widget = Circle::sidebarWidget('contact', 'circle', 'full', 'everyone', $data['user']);
241                 }
242
243                 $this->page['aside'] .= $vcard_widget . $circles_widget;
244
245                 $o = '';
246                 Nav::setSelected('contact');
247
248                 $_SESSION['return_path'] = $this->args->getQueryString();
249
250                 $this->page['htmlhead'] .= Renderer::replaceMacros(Renderer::getMarkupTemplate('contact_head.tpl'), [
251                 ]);
252
253                 switch ($localRelationship->rel) {
254                         case Contact::FRIEND:   $relation_text = $this->t('You are mutual friends with %s', $contact['name']); break;
255                         case Contact::FOLLOWER: $relation_text = $this->t('You are sharing with %s', $contact['name']); break;
256                         case Contact::SHARING:  $relation_text = $this->t('%s is sharing with you', $contact['name']); break;
257                         default:
258                                 $relation_text = '';
259                 }
260
261                 if (!Protocol::supportsFollow($contact['network'])) {
262                         $relation_text = '';
263                 }
264
265                 $url = Contact::magicLinkByContact($contact);
266                 if (strpos($url, 'contact/redir/') === 0) {
267                         $sparkle = ' class="sparkle" ';
268                 } else {
269                         $sparkle = '';
270                 }
271
272                 $insecure = $this->t('Private communications are not available for this contact.');
273
274                 // @TODO: Figure out why gsid can be empty
275                 if (empty($contact['gsid'])) {
276                         $this->logger->notice('Empty gsid for contact', ['contact' => $contact]);
277                 }
278
279                 $serverIgnored =
280                         $contact['gsid'] &&
281                         $this->userGServer->isIgnoredByUser($this->session->getLocalUserId(), $contact['gsid']) ?
282                                 $this->t('This contact is on a server you ignored.')
283                                 : '';
284
285                 $last_update = (($contact['last-update'] <= DBA::NULL_DATETIME) ? $this->t('Never') : DateTimeFormat::local($contact['last-update'], 'D, j M Y, g:i A'));
286
287                 if ($contact['last-update'] > DBA::NULL_DATETIME) {
288                         $last_update .= ' ' . ($contact['failed'] ? $this->t('(Update was not successful)') : $this->t('(Update was successful)'));
289                 }
290                 $lblsuggest = (($contact['network'] === Protocol::DFRN) ? $this->t('Suggest friends') : '');
291
292                 $poll_enabled = in_array($contact['network'], [Protocol::DFRN, Protocol::OSTATUS, Protocol::FEED, Protocol::MAIL]);
293
294                 $nettype = $this->t('Network type: %s', ContactSelector::networkToName($contact['network'], $contact['url'], $contact['protocol'], $contact['gsid']));
295
296                 // tabs
297                 $tab_str = Module\Contact::getTabsHTML($contact, Module\Contact::TAB_PROFILE);
298
299                 $lost_contact = (($contact['archive'] && $contact['term-date'] > DBA::NULL_DATETIME && $contact['term-date'] < DateTimeFormat::utcNow()) ? $this->t('Communications lost with this contact!') : '');
300
301                 $fetch_further_information = null;
302                 if ($contact['network'] == Protocol::FEED) {
303                         $fetch_further_information = [
304                                 'fetch_further_information',
305                                 $this->t('Fetch further information for feeds'),
306                                 $localRelationship->fetchFurtherInformation,
307                                 $this->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.'),
308                                 [
309                                         LocalRelationship\Entity\LocalRelationship::FFI_NONE        => $this->t('Disabled'),
310                                         LocalRelationship\Entity\LocalRelationship::FFI_INFORMATION => $this->t('Fetch information'),
311                                         LocalRelationship\Entity\LocalRelationship::FFI_KEYWORD     => $this->t('Fetch keywords'),
312                                         LocalRelationship\Entity\LocalRelationship::FFI_BOTH        => $this->t('Fetch information and keywords')
313                                 ]
314                         ];
315                 }
316
317                 $allow_remote_self = in_array($contact['network'], [Protocol::ACTIVITYPUB, Protocol::FEED, Protocol::DFRN, Protocol::DIASPORA, Protocol::TWITTER])
318                         && $this->config->get('system', 'allow_users_remote_self');
319
320                 if ($contact['network'] == Protocol::FEED) {
321                         $remote_self_options = [
322                                 Contact::MIRROR_DEACTIVATED => $this->t('No mirroring'),
323                                 Contact::MIRROR_OWN_POST    => $this->t('Mirror as my own posting')
324                         ];
325                 } elseif ($contact['network'] == Protocol::ACTIVITYPUB) {
326                         $remote_self_options = [
327                                 Contact::MIRROR_DEACTIVATED    => $this->t('No mirroring'),
328                                 Contact::MIRROR_NATIVE_RESHARE => $this->t('Native reshare')
329                         ];
330                 } elseif ($contact['network'] == Protocol::DFRN) {
331                         $remote_self_options = [
332                                 Contact::MIRROR_DEACTIVATED    => $this->t('No mirroring'),
333                                 Contact::MIRROR_OWN_POST       => $this->t('Mirror as my own posting'),
334                                 Contact::MIRROR_NATIVE_RESHARE => $this->t('Native reshare')
335                         ];
336                 } else {
337                         $remote_self_options = [
338                                 Contact::MIRROR_DEACTIVATED => $this->t('No mirroring'),
339                                 Contact::MIRROR_OWN_POST    => $this->t('Mirror as my own posting')
340                         ];
341                 }
342
343                 if (in_array($contact['network'], Protocol::FEDERATED)) {
344                         $channel_settings_label = $this->t('Channel Settings');
345                         $channel_visibility     = Contact\User::getChannelVisibility($contact['id'], $this->session->getLocalUserId());
346                         $channel_visibilities   = [
347                                 Contact\User::VISIBILITY_DEFAULT => $this->t('Default visibility'),
348                                 Contact\User::VISIBILITY_ALWAYS  => $this->t('Display all posts of this contact'),
349                                 Contact\User::VISIBILITY_REDUCED => $this->t('Display only few posts'),
350                                 Contact\User::VISIBILITY_NEVER   => $this->t('Never display posts from this contact'),
351                         ];
352                 } else {
353                         $channel_settings_label = null;
354                         $channel_visibility     = null;
355                         $channel_visibilities   = null;
356                 }
357
358                 $poll_interval = null;
359                 if ((($contact['network'] == Protocol::FEED) && !$this->config->get('system', 'adjust_poll_frequency')) || ($contact['network'] == Protocol::MAIL)) {
360                         $poll_interval = ContactSelector::pollInterval($localRelationship->priority, !$poll_enabled);
361                 }
362
363                 $contact_actions = $this->getContactActions($contact, $localRelationship);
364
365                 if ($localRelationship->rel !== Contact::NOTHING) {
366                         $lbl_info1              = $this->t('Contact Information / Notes');
367                         $contact_settings_label = $this->t('Contact Settings');
368                 } else {
369                         $lbl_info1              = null;
370                         $contact_settings_label = null;
371                 }
372
373                 $tpl = Renderer::getMarkupTemplate('contact_edit.tpl');
374                 $o .= Renderer::replaceMacros($tpl, [
375                         '$header'                    => $this->t('Contact'),
376                         '$tab_str'                   => $tab_str,
377                         '$submit'                    => $this->t('Submit'),
378                         '$lbl_info1'                 => $lbl_info1,
379                         '$lbl_info2'                 => $this->t('Their personal note'),
380                         '$reason'                    => trim($contact['reason'] ?? ''),
381                         '$infedit'                   => $this->t('Edit contact notes'),
382                         '$common_link'               => 'contact/' . $contact['id'] . '/contacts/common',
383                         '$relation_text'             => $relation_text,
384                         '$visit'                     => $this->t('Visit %s\'s profile [%s]', $contact['name'], $contact['url']),
385                         '$blockunblock'              => $this->t('Block/Unblock contact'),
386                         '$ignorecont'                => $this->t('Ignore contact'),
387                         '$lblrecent'                 => $this->t('View conversations'),
388                         '$lblsuggest'                => $lblsuggest,
389                         '$nettype'                   => $nettype,
390                         '$poll_interval'             => $poll_interval,
391                         '$poll_enabled'              => $poll_enabled,
392                         '$lastupdtext'               => $this->t('Last update:'),
393                         '$lost_contact'              => $lost_contact,
394                         '$updpub'                    => $this->t('Update public posts'),
395                         '$last_update'               => $last_update,
396                         '$udnow'                     => $this->t('Update now'),
397                         '$contact_id'                => $contact['id'],
398                         '$pending'                   => $localRelationship->pending   ? $this->t('Awaiting connection acknowledge') : '',
399                         '$blocked'                   => $localRelationship->blocked   ? $this->t('Currently blocked') : '',
400                         '$ignored'                   => $localRelationship->ignored   ? $this->t('Currently ignored') : '',
401                         '$collapsed'                 => $localRelationship->collapsed ? $this->t('Currently collapsed') : '',
402                         '$archived'                  => ($contact['archive'] ? $this->t('Currently archived') : ''),
403                         '$insecure'                  => (in_array($contact['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::MAIL, Protocol::DIASPORA]) ? '' : $insecure),
404                         '$serverIgnored'             => $serverIgnored,
405                         '$manageServers'             => $this->t('Manage remote servers'),
406                         '$cinfo'                     => ['info', '', $localRelationship->info, ''],
407                         '$hidden'                    => ['hidden', $this->t('Hide this contact from others'), $localRelationship->hidden, $this->t('Replies/likes to your public posts <strong>may</strong> still be visible')],
408                         '$notify_new_posts'          => ['notify_new_posts', $this->t('Notification for new posts'), ($localRelationship->notifyNewPosts), $this->t('Send a notification of every new post of this contact')],
409                         '$fetch_further_information' => $fetch_further_information,
410                         '$ffi_keyword_denylist'      => ['ffi_keyword_denylist', $this->t('Keyword Deny List'), $localRelationship->ffiKeywordDenylist, $this->t('Comma separated list of keywords that should not be converted to hashtags, when "Fetch information and keywords" is selected')],
411                         '$photo'                     => Contact::getPhoto($contact),
412                         '$name'                      => $contact['name'],
413                         '$sparkle'                   => $sparkle,
414                         '$url'                       => $url,
415                         '$profileurllabel'           => $this->t('Profile URL'),
416                         '$profileurl'                => $contact['url'],
417                         '$account_type'              => Contact::getAccountType($contact['contact-type']),
418                         '$location'                  => BBCode::convertForUriId($contact['uri-id'] ?? 0, $contact['location']),
419                         '$location_label'            => $this->t('Location:'),
420                         '$xmpp'                      => BBCode::convertForUriId($contact['uri-id'] ?? 0, $contact['xmpp']),
421                         '$xmpp_label'                => $this->t('XMPP:'),
422                         '$matrix'                    => BBCode::convertForUriId($contact['uri-id'] ?? 0, $contact['matrix']),
423                         '$matrix_label'              => $this->t('Matrix:'),
424                         '$about'                     => BBCode::convertForUriId($contact['uri-id'] ?? 0, $contact['about'], BBCode::EXTERNAL),
425                         '$about_label'               => $this->t('About:'),
426                         '$keywords'                  => $contact['keywords'],
427                         '$keywords_label'            => $this->t('Tags:'),
428                         '$contact_action_button'     => $this->t('Actions'),
429                         '$contact_actions'           => $contact_actions,
430                         '$contact_status'            => $this->t('Status'),
431                         '$contact_settings_label'    => $contact_settings_label,
432                         '$contact_profile_label'     => $this->t('Profile'),
433                         '$allow_remote_self'         => $allow_remote_self,
434                         '$remote_self'               => [
435                                 'remote_self',
436                                 $this->t('Mirror postings from this contact'),
437                                 $localRelationship->remoteSelf,
438                                 $this->t('Mark this contact as remote_self, this will cause friendica to repost new entries from this contact.'),
439                                 $remote_self_options
440                         ],
441                         '$channel_settings_label' => $channel_settings_label,
442                         '$channel_visibility'     => [
443                                 'channel_visibility',
444                                 $this->t('Visibility of this contact in appropriate channels'),
445                                 $channel_visibility,
446                                 $this->t('Depending on the type of the channel not all posts from contacts are displayed by default. They for example need to have a certain amount of comments to be displayed. On the other hand there can be contacts who flood the channel, so you might want to see only some of their posts. Or you don\'t want to see their content at all, but you don\'t want to block or hide the contact completely.'),
447                                 $channel_visibilities
448                         ],
449                         '$visibility_label'       => $this->t('Frequency of this contact in relevant channels'),
450                         '$visibility_description' => $this->t("Depending on the type of the channel not all posts from this contact are displayed. By default, posts need to have a minimum amount of interactions (comments, likes) to show in your channels. On the other hand there can be contacts who flood the channel, so you might want to see only some of their posts. Or you don't want to see their content at all, but you don't want to block or hide the contact completely."),
451                         '$visibility_default'     => ['channel_visibility', $this->t('Default visibility'), Contact\User::VISIBILITY_DEFAULT, $this->t('When activated, posts by this contact are displayed in the "for you" channel, if either you interact often with this contact or if a post reached some level of interaction.'), $channel_visibility == Contact\User::VISIBILITY_DEFAULT],
452                         '$visibility_always'      => ['channel_visibility', $this->t('Display all posts of this contact'), Contact\User::VISIBILITY_ALWAYS, $this->t('If you follow this contact, then every post of this contact will appear on the "for you" channel'), $channel_visibility == Contact\User::VISIBILITY_ALWAYS],
453                         '$visibility_reduced'     => ['channel_visibility', $this->t('Display only few posts'), Contact\User::VISIBILITY_REDUCED, $this->t('When a contact creates a lot of posts in a short period, this setting reduces the number of displayed posts in every channel.'), $channel_visibility == Contact\User::VISIBILITY_REDUCED],
454                         '$visibility_never'       => ['channel_visibility', $this->t('Never display posts from this contact'), Contact\User::VISIBILITY_NEVER, $this->t('Posts from this contact will never be displayed in any channel'), $channel_visibility == Contact\User::VISIBILITY_NEVER],
455         ]);
456
457                 $arr = ['contact' => $contact, 'output' => $o];
458
459                 Hook::callAll('contact_edit', $arr);
460
461                 return $arr['output'];
462         }
463
464         /**
465          * Returns the list of available actions that can performed on the provided contact
466          *
467          * This includes actions like e.g. 'block', 'hide', 'delete' and others
468          *
469          * @param array                    $contact           Public contact row
470          * @param LocalRelationship\Entity\LocalRelationship $localRelationship
471          * @return array with contact related actions
472          * @throws HTTPException\InternalServerErrorException
473          */
474         private function getContactActions(array $contact, LocalRelationship\Entity\LocalRelationship $localRelationship): array
475         {
476                 $poll_enabled    = in_array($contact['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::OSTATUS, Protocol::FEED, Protocol::MAIL]);
477                 $contact_actions = [];
478
479                 $formSecurityToken = self::getFormSecurityToken('contact_action');
480
481                 if ($localRelationship->rel & Contact::SHARING) {
482                         $contact_actions['unfollow'] = [
483                                 'label' => $this->t('Unfollow'),
484                                 'url'   => 'contact/unfollow?url=' . urlencode($contact['url']) . '&auto=1',
485                                 'title' => '',
486                                 'sel'   => '',
487                                 'id'    => 'unfollow',
488                         ];
489                 } else {
490                         $contact_actions['follow'] = [
491                                 'label' => $this->t('Follow'),
492                                 'url'   => 'contact/follow?url=' . urlencode($contact['url']) . '&auto=1',
493                                 'title' => '',
494                                 'sel'   => '',
495                                 'id'    => 'follow',
496                         ];
497                 }
498
499                 // Provide friend suggestion only for Friendica contacts
500                 if ($contact['network'] === Protocol::DFRN) {
501                         $contact_actions['suggest'] = [
502                                 'label' => $this->t('Suggest friends'),
503                                 'url'   => 'fsuggest/' . $contact['id'],
504                                 'title' => '',
505                                 'sel'   => '',
506                                 'id'    => 'suggest',
507                         ];
508                 }
509
510                 if ($poll_enabled) {
511                         $contact_actions['update'] = [
512                                 'label' => $this->t('Update now'),
513                                 'url'   => 'contact/' . $contact['id'] . '/update?t=' . $formSecurityToken,
514                                 'title' => '',
515                                 'sel'   => '',
516                                 'id'    => 'update',
517                         ];
518                 }
519
520                 if (Protocol::supportsProbe($contact['network'])) {
521                         $contact_actions['updateprofile'] = [
522                                 'label' => $this->t('Refetch contact data'),
523                                 'url'   => 'contact/' . $contact['id'] . '/updateprofile?t=' . $formSecurityToken,
524                                 'title' => '',
525                                 'sel'   => '',
526                                 'id'    => 'updateprofile',
527                         ];
528                 }
529
530                 $contact_actions['block'] = [
531                         'label' => $localRelationship->blocked ? $this->t('Unblock') : $this->t('Block'),
532                         'url'   => 'contact/' . $contact['id'] . '/block?t=' . $formSecurityToken,
533                         'title' => $this->t('Toggle Blocked status'),
534                         'sel'   => $localRelationship->blocked ? 'active' : '',
535                         'id'    => 'toggle-block',
536                 ];
537
538                 $contact_actions['ignore'] = [
539                         'label' => $localRelationship->ignored ? $this->t('Unignore') : $this->t('Ignore'),
540                         'url'   => 'contact/' . $contact['id'] . '/ignore?t=' . $formSecurityToken,
541                         'title' => $this->t('Toggle Ignored status'),
542                         'sel'   => $localRelationship->ignored ? 'active' : '',
543                         'id'    => 'toggle-ignore',
544                 ];
545
546                 $contact_actions['collapse'] = [
547                         'label' => $localRelationship->collapsed ? $this->t('Uncollapse') : $this->t('Collapse'),
548                         'url'   => 'contact/' . $contact['id'] . '/collapse?t=' . $formSecurityToken,
549                         'title' => $this->t('Toggle Collapsed status'),
550                         'sel'   => $localRelationship->collapsed ? 'active' : '',
551                         'id'    => 'toggle-collapse',
552                 ];
553
554                 if (Protocol::supportsRevokeFollow($contact['network']) && in_array($localRelationship->rel, [Contact::FOLLOWER, Contact::FRIEND])) {
555                         $contact_actions['revoke_follow'] = [
556                                 'label' => $this->t('Revoke Follow'),
557                                 'url'   => 'contact/' . $contact['id'] . '/revoke',
558                                 'title' => $this->t('Revoke the follow from this contact'),
559                                 'sel'   => '',
560                                 'id'    => 'revoke_follow',
561                         ];
562                 }
563
564                 return $contact_actions;
565         }
566
567         /**
568          * Updates contact from probing
569          *
570          * @param int $contact_id Id of the contact with uid != 0
571          * @return void
572          * @throws HTTPException\InternalServerErrorException
573          * @throws \ImagickException
574          */
575         private function updateContactFromProbe(int $contact_id)
576         {
577                 if (!$this->db->exists('contact', ['id' => $contact_id, 'uid' => [0, $this->session->getLocalUserId()], 'deleted' => false])) {
578                         return;
579                 }
580
581                 // Update the entry in the contact table
582                 Contact::updateFromProbe($contact_id);
583         }
584 }