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