3 * @copyright Copyright (C) 2010-2023, the Friendica project
5 * @license GNU AGPL version 3 or any later version
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.
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.
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/>.
22 namespace Friendica\Module\Contact;
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;
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;
51 * Show a contact profile
53 class Profile extends BaseModule
55 /** @var LocalRelationship\Repository\LocalRelationship */
56 private $localRelationship;
59 /** @var IManageConfigValues */
61 /** @var IHandleUserSessions */
63 /** @var SystemMessages */
64 private $systemMessages;
67 /** @var Settings\Repository\UserGServer */
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 = [])
72 parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
74 $this->localRelationship = $localRelationship;
76 $this->config = $config;
77 $this->session = $session;
78 $this->systemMessages = $systemMessages;
80 $this->userGServer = $userGServer;
83 protected function post(array $request = [])
85 if (!$this->session->getLocalUserId()) {
89 $contact_id = $this->parameters['id'];
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])) {
98 Hook::callAll('contact_edit_post', $_POST);
102 if (isset($_POST['hidden'])) {
103 $fields['hidden'] = !empty($_POST['hidden']);
106 if (isset($_POST['notify_new_posts'])) {
107 $fields['notify_new_posts'] = !empty($_POST['notify_new_posts']);
110 if (isset($_POST['fetch_further_information'])) {
111 $fields['fetch_further_information'] = intval($_POST['fetch_further_information']);
114 if (isset($_POST['remote_self'])) {
115 $fields['remote_self'] = intval($_POST['remote_self']);
118 if (isset($_POST['ffi_keyword_denylist'])) {
119 $fields['ffi_keyword_denylist'] = $_POST['ffi_keyword_denylist'];
122 if (isset($_POST['poll'])) {
123 $priority = intval($_POST['poll']);
124 if ($priority > 5 || $priority < 0) {
128 $fields['priority'] = $priority;
131 if (isset($_POST['info'])) {
132 $fields['info'] = $_POST['info'];
135 if (isset($_POST['channel_visibility'])) {
136 Contact\User::setChannelVisibility($cdata['user'], $this->session->getLocalUserId(), $_POST['channel_visibility']);
139 if (!Contact::update($fields, ['id' => $cdata['user'], 'uid' => $this->session->getLocalUserId()])) {
140 $this->systemMessages->addNotice($this->t('Failed to update contact record.'));
144 protected function content(array $request = []): string
146 if (!$this->session->getLocalUserId()) {
147 return Module\Security\Login::form($_SERVER['REQUEST_URI']);
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());
154 throw new HTTPException\NotFoundException($this->t('Contact not found.'));
157 $contact = Contact::getById($data['public']);
158 if (!$this->db->isResult($contact)) {
159 throw new HTTPException\NotFoundException($this->t('Contact not found.'));
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.'));
167 $localRelationship = $this->localRelationship->getForUserContact($this->session->getLocalUserId(), $contact['id']);
169 if ($localRelationship->rel === Contact::SELF) {
170 $this->baseUrl->redirect('profile/' . $contact['nick'] . '/profile');
173 if (isset($this->parameters['action'])) {
174 self::checkFormSecurityTokenRedirectOnError('contact/' . $contact['id'], 'contact_action', 't');
176 $cmd = $this->parameters['action'];
177 if ($cmd === 'update' && $localRelationship->rel !== Contact::NOTHING) {
178 Module\Contact::updateContactFromPoll($contact['id']);
181 if ($cmd === 'updateprofile') {
182 $this->updateContactFromProbe($contact['id']);
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);
190 $message = $this->t('Contact has been unblocked');
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');
197 // @TODO: add $this->localRelationship->save($localRelationship);
198 $this->systemMessages->addInfo($message);
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);
206 $message = $this->t('Contact has been unignored');
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');
213 // @TODO: add $this->localRelationship->save($localRelationship);
214 $this->systemMessages->addInfo($message);
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);
222 $message = $this->t('Contact has been uncollapsed');
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');
229 // @TODO: add $this->localRelationship->save($localRelationship);
230 $this->systemMessages->addInfo($message);
233 $this->baseUrl->redirect('contact/' . $contact['id']);
236 $vcard_widget = Widget\VCard::getHTML($contact);
237 $circles_widget = '';
239 if (!in_array($localRelationship->rel, [Contact::NOTHING, Contact::SELF])) {
240 $circles_widget = Circle::sidebarWidget('contact', 'circle', 'full', 'everyone', $data['user']);
243 $this->page['aside'] .= $vcard_widget . $circles_widget;
246 Nav::setSelected('contact');
248 $_SESSION['return_path'] = $this->args->getQueryString();
250 $this->page['htmlhead'] .= Renderer::replaceMacros(Renderer::getMarkupTemplate('contact_head.tpl'), [
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;
261 if (!Protocol::supportsFollow($contact['network'])) {
265 $url = Contact::magicLinkByContact($contact);
266 if (strpos($url, 'contact/redir/') === 0) {
267 $sparkle = ' class="sparkle" ';
272 $insecure = $this->t('Private communications are not available for this contact.');
274 // @TODO: Figure out why gsid can be empty
275 if (empty($contact['gsid'])) {
276 $this->logger->notice('Empty gsid for contact', ['contact' => $contact]);
281 $this->userGServer->isIgnoredByUser($this->session->getLocalUserId(), $contact['gsid']) ?
282 $this->t('This contact is on a server you ignored.')
285 $last_update = (($contact['last-update'] <= DBA::NULL_DATETIME) ? $this->t('Never') : DateTimeFormat::local($contact['last-update'], 'D, j M Y, g:i A'));
287 if ($contact['last-update'] > DBA::NULL_DATETIME) {
288 $last_update .= ' ' . ($contact['failed'] ? $this->t('(Update was not successful)') : $this->t('(Update was successful)'));
290 $lblsuggest = (($contact['network'] === Protocol::DFRN) ? $this->t('Suggest friends') : '');
292 $poll_enabled = in_array($contact['network'], [Protocol::DFRN, Protocol::OSTATUS, Protocol::FEED, Protocol::MAIL]);
294 $nettype = $this->t('Network type: %s', ContactSelector::networkToName($contact['network'], $contact['url'], $contact['protocol'], $contact['gsid']));
297 $tab_str = Module\Contact::getTabsHTML($contact, Module\Contact::TAB_PROFILE);
299 $lost_contact = (($contact['archive'] && $contact['term-date'] > DBA::NULL_DATETIME && $contact['term-date'] < DateTimeFormat::utcNow()) ? $this->t('Communications lost with this contact!') : '');
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.'),
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')
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');
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')
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')
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')
337 $remote_self_options = [
338 Contact::MIRROR_DEACTIVATED => $this->t('No mirroring'),
339 Contact::MIRROR_OWN_POST => $this->t('Mirror as my own posting')
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'),
353 $channel_settings_label = null;
354 $channel_visibility = null;
355 $channel_visibilities = null;
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);
363 $contact_actions = $this->getContactActions($contact, $localRelationship);
365 if ($localRelationship->rel !== Contact::NOTHING) {
366 $lbl_info1 = $this->t('Contact Information / Notes');
367 $contact_settings_label = $this->t('Contact Settings');
370 $contact_settings_label = null;
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,
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,
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.'),
441 '$channel_settings_label' => $channel_settings_label,
442 '$channel_visibility' => [
443 'channel_visibility',
444 $this->t('Visibility of this contact in appropriate channels'),
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
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 frequency'), Contact\User::VISIBILITY_DEFAULT, $this->t('Posts by this contact are displayed in the "for you" channel if 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, all their posts 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'), Contact\User::VISIBILITY_NEVER, $this->t('Posts from this contact will never be displayed in any channel'), $channel_visibility == Contact\User::VISIBILITY_NEVER],
457 $arr = ['contact' => $contact, 'output' => $o];
459 Hook::callAll('contact_edit', $arr);
461 return $arr['output'];
465 * Returns the list of available actions that can performed on the provided contact
467 * This includes actions like e.g. 'block', 'hide', 'delete' and others
469 * @param array $contact Public contact row
470 * @param LocalRelationship\Entity\LocalRelationship $localRelationship
471 * @return array with contact related actions
472 * @throws HTTPException\InternalServerErrorException
474 private function getContactActions(array $contact, LocalRelationship\Entity\LocalRelationship $localRelationship): array
476 $poll_enabled = in_array($contact['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::OSTATUS, Protocol::FEED, Protocol::MAIL]);
477 $contact_actions = [];
479 $formSecurityToken = self::getFormSecurityToken('contact_action');
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',
490 $contact_actions['follow'] = [
491 'label' => $this->t('Follow'),
492 'url' => 'contact/follow?url=' . urlencode($contact['url']) . '&auto=1',
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'],
511 $contact_actions['update'] = [
512 'label' => $this->t('Update now'),
513 'url' => 'contact/' . $contact['id'] . '/update?t=' . $formSecurityToken,
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,
526 'id' => 'updateprofile',
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',
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',
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',
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'),
560 'id' => 'revoke_follow',
564 return $contact_actions;
568 * Updates contact from probing
570 * @param int $contact_id Id of the contact with uid != 0
572 * @throws HTTPException\InternalServerErrorException
573 * @throws \ImagickException
575 private function updateContactFromProbe(int $contact_id)
577 if (!$this->db->exists('contact', ['id' => $contact_id, 'uid' => [0, $this->session->getLocalUserId()], 'deleted' => false])) {
581 // Update the entry in the contact table
582 Contact::updateFromProbe($contact_id);