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