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