]> git.mxchange.org Git - friendica.git/blob - src/Module/Contact.php
Merge pull request #8033 from annando/contact-logging
[friendica.git] / src / Module / Contact.php
1 <?php
2
3 namespace Friendica\Module;
4
5 use Friendica\App;
6 use Friendica\BaseModule;
7 use Friendica\Content\ContactSelector;
8 use Friendica\Content\Nav;
9 use Friendica\Content\Pager;
10 use Friendica\Content\Text\BBCode;
11 use Friendica\Content\Widget;
12 use Friendica\Core\ACL;
13 use Friendica\Core\Hook;
14 use Friendica\Core\L10n;
15 use Friendica\Core\Protocol;
16 use Friendica\Core\Renderer;
17 use Friendica\Core\System;
18 use Friendica\Core\Worker;
19 use Friendica\Database\DBA;
20 use Friendica\DI;
21 use Friendica\Model;
22 use Friendica\Module\Security\Login;
23 use Friendica\Network\HTTPException\BadRequestException;
24 use Friendica\Network\HTTPException\NotFoundException;
25 use Friendica\Util\DateTimeFormat;
26 use Friendica\Util\Proxy as ProxyUtils;
27 use Friendica\Util\Strings;
28
29 /**
30  *  Manages and show Contacts and their content
31  *
32  *  @brief manages contacts
33  */
34 class Contact extends BaseModule
35 {
36         private static function batchActions()
37         {
38                 if (empty($_POST['contact_batch']) || !is_array($_POST['contact_batch'])) {
39                         return;
40                 }
41
42                 $contacts_id = $_POST['contact_batch'];
43
44                 $stmt = DBA::select('contact', ['id', 'archive'], ['id' => $contacts_id, 'uid' => local_user(), 'self' => false, 'deleted' => false]);
45                 $orig_records = DBA::toArray($stmt);
46
47                 $count_actions = 0;
48                 foreach ($orig_records as $orig_record) {
49                         $contact_id = $orig_record['id'];
50                         if (!empty($_POST['contacts_batch_update'])) {
51                                 self::updateContactFromPoll($contact_id);
52                                 $count_actions++;
53                         }
54                         if (!empty($_POST['contacts_batch_block'])) {
55                                 self::blockContact($contact_id);
56                                 $count_actions++;
57                         }
58                         if (!empty($_POST['contacts_batch_ignore'])) {
59                                 self::ignoreContact($contact_id);
60                                 $count_actions++;
61                         }
62                         if (!empty($_POST['contacts_batch_archive'])
63                                 && self::archiveContact($contact_id, $orig_record)
64                         ) {
65                                 $count_actions++;
66                         }
67                         if (!empty($_POST['contacts_batch_drop'])) {
68                                 self::dropContact($orig_record);
69                                 $count_actions++;
70                         }
71                 }
72                 if ($count_actions > 0) {
73                         info(L10n::tt('%d contact edited.', '%d contacts edited.', $count_actions));
74                 }
75
76                 DI::baseUrl()->redirect('contact');
77         }
78
79         public static function post(array $parameters = [])
80         {
81                 $a = DI::app();
82
83                 if (!local_user()) {
84                         return;
85                 }
86
87                 // @TODO: Replace with parameter from router
88                 if ($a->argv[1] === 'batch') {
89                         self::batchActions();
90                         return;
91                 }
92
93                 // @TODO: Replace with parameter from router
94                 $contact_id = intval($a->argv[1]);
95                 if (!$contact_id) {
96                         return;
97                 }
98
99                 if (!DBA::exists('contact', ['id' => $contact_id, 'uid' => local_user(), 'deleted' => false])) {
100                         notice(L10n::t('Could not access contact record.') . EOL);
101                         DI::baseUrl()->redirect('contact');
102                         return; // NOTREACHED
103                 }
104
105                 Hook::callAll('contact_edit_post', $_POST);
106
107                 $profile_id = intval($_POST['profile-assign'] ?? 0);
108                 if ($profile_id) {
109                         if (!DBA::exists('profile', ['id' => $profile_id, 'uid' => local_user()])) {
110                                 notice(L10n::t('Could not locate selected profile.') . EOL);
111                                 return;
112                         }
113                 }
114
115                 $hidden = !empty($_POST['hidden']);
116
117                 $notify = !empty($_POST['notify']);
118
119                 $fetch_further_information = intval($_POST['fetch_further_information'] ?? 0);
120
121                 $ffi_keyword_blacklist = Strings::escapeHtml(trim($_POST['ffi_keyword_blacklist'] ?? ''));
122
123                 $priority = intval($_POST['poll'] ?? 0);
124                 if ($priority > 5 || $priority < 0) {
125                         $priority = 0;
126                 }
127
128                 $info = Strings::escapeHtml(trim($_POST['info'] ?? ''));
129
130                 $r = DBA::update('contact', [
131                         'profile-id' => $profile_id,
132                         'priority'   => $priority,
133                         'info'       => $info,
134                         'hidden'     => $hidden,
135                         'notify_new_posts' => $notify,
136                         'fetch_further_information' => $fetch_further_information,
137                         'ffi_keyword_blacklist'     => $ffi_keyword_blacklist],
138                         ['id' => $contact_id, 'uid' => local_user()]
139                 );
140
141                 if (DBA::isResult($r)) {
142                         info(L10n::t('Contact updated.') . EOL);
143                 } else {
144                         notice(L10n::t('Failed to update contact record.') . EOL);
145                 }
146
147                 $contact = DBA::selectFirst('contact', [], ['id' => $contact_id, 'uid' => local_user(), 'deleted' => false]);
148                 if (DBA::isResult($contact)) {
149                         $a->data['contact'] = $contact;
150                 }
151
152                 return;
153         }
154
155         /* contact actions */
156
157         private static function updateContactFromPoll($contact_id)
158         {
159                 $contact = DBA::selectFirst('contact', ['uid', 'url', 'network'], ['id' => $contact_id, 'uid' => local_user(), 'deleted' => false]);
160                 if (!DBA::isResult($contact)) {
161                         return;
162                 }
163
164                 $uid = $contact['uid'];
165
166                 if ($contact['network'] == Protocol::OSTATUS) {
167                         $result = Model\Contact::createFromProbe($uid, $contact['url'], false, $contact['network']);
168
169                         if ($result['success']) {
170                                 DBA::update('contact', ['subhub' => 1], ['id' => $contact_id]);
171                         }
172                 } else {
173                         // pull feed and consume it, which should subscribe to the hub.
174                         Worker::add(PRIORITY_HIGH, 'OnePoll', $contact_id, 'force');
175                 }
176         }
177
178         private static function updateContactFromProbe($contact_id)
179         {
180                 $contact = DBA::selectFirst('contact', ['url'], ['id' => $contact_id, 'uid' => local_user(), 'deleted' => false]);
181                 if (!DBA::isResult($contact)) {
182                         return;
183                 }
184
185                 // Update the entry in the contact table
186                 Model\Contact::updateFromProbe($contact_id, '', true);
187
188                 // Update the entry in the gcontact table
189                 Model\GContact::updateFromProbe($contact['url']);
190         }
191
192         /**
193          * Toggles the blocked status of a contact identified by id.
194          *
195          * @param $contact_id
196          * @throws \Exception
197          */
198         private static function blockContact($contact_id)
199         {
200                 $blocked = !Model\Contact::isBlockedByUser($contact_id, local_user());
201                 Model\Contact::setBlockedForUser($contact_id, local_user(), $blocked);
202         }
203
204         /**
205          * Toggles the ignored status of a contact identified by id.
206          *
207          * @param $contact_id
208          * @throws \Exception
209          */
210         private static function ignoreContact($contact_id)
211         {
212                 $ignored = !Model\Contact::isIgnoredByUser($contact_id, local_user());
213                 Model\Contact::setIgnoredForUser($contact_id, local_user(), $ignored);
214         }
215
216         /**
217          * Toggles the archived status of a contact identified by id.
218          * If the current status isn't provided, this will always archive the contact.
219          *
220          * @param $contact_id
221          * @param $orig_record
222          * @return bool
223          * @throws \Exception
224          */
225         private static function archiveContact($contact_id, $orig_record)
226         {
227                 $archived = empty($orig_record['archive']);
228                 $r = DBA::update('contact', ['archive' => $archived], ['id' => $contact_id, 'uid' => local_user()]);
229
230                 return DBA::isResult($r);
231         }
232
233         private static function dropContact($orig_record)
234         {
235                 $owner = Model\User::getOwnerDataById(local_user());
236                 if (!DBA::isResult($owner)) {
237                         return;
238                 }
239
240                 Model\Contact::terminateFriendship($owner, $orig_record, true);
241                 Model\Contact::remove($orig_record['id']);
242         }
243
244         public static function content(array $parameters = [], $update = 0)
245         {
246                 if (!local_user()) {
247                         return Login::form($_SERVER['REQUEST_URI']);
248                 }
249
250                 $a = DI::app();
251
252                 $nets = $_GET['nets'] ?? '';
253                 $rel  = $_GET['rel']  ?? '';
254
255                 if (empty($a->page['aside'])) {
256                         $a->page['aside'] = '';
257                 }
258
259                 $contact_id = null;
260                 $contact = null;
261                 // @TODO: Replace with parameter from router
262                 if ($a->argc == 2 && intval($a->argv[1])
263                         || $a->argc == 3 && intval($a->argv[1]) && in_array($a->argv[2], ['posts', 'conversations'])
264                 ) {
265                         $contact_id = intval($a->argv[1]);
266                         $contact = DBA::selectFirst('contact', [], ['id' => $contact_id, 'uid' => local_user(), 'deleted' => false]);
267
268                         if (!DBA::isResult($contact)) {
269                                 $contact = DBA::selectFirst('contact', [], ['id' => $contact_id, 'uid' => 0, 'deleted' => false]);
270                         }
271
272                         // Don't display contacts that are about to be deleted
273                         if ($contact['network'] == Protocol::PHANTOM) {
274                                 $contact = false;
275                         }
276                 }
277
278                 if (DBA::isResult($contact)) {
279                         if ($contact['self']) {
280                                 // @TODO: Replace with parameter from router
281                                 if (($a->argc == 3) && intval($a->argv[1]) && in_array($a->argv[2], ['posts', 'conversations'])) {
282                                         DI::baseUrl()->redirect('profile/' . $contact['nick']);
283                                 } else {
284                                         DI::baseUrl()->redirect('profile/' . $contact['nick'] . '?tab=profile');
285                                 }
286                         }
287
288                         $a->data['contact'] = $contact;
289
290                         if (($contact['network'] != '') && ($contact['network'] != Protocol::DFRN)) {
291                                 $network_link = Strings::formatNetworkName($contact['network'], $contact['url']);
292                         } else {
293                                 $network_link = '';
294                         }
295
296                         $follow_link = '';
297                         $unfollow_link = '';
298                         if (in_array($contact['network'], Protocol::NATIVE_SUPPORT)) {
299                                 if ($contact['uid'] && in_array($contact['rel'], [Model\Contact::SHARING, Model\Contact::FRIEND])) {
300                                         $unfollow_link = 'unfollow?url=' . urlencode($contact['url']);
301                                 } elseif(!$contact['pending']) {
302                                         $follow_link = 'follow?url=' . urlencode($contact['url']);
303                                 }
304                         }
305
306                         $wallmessage_link = '';
307                         if ($contact['uid'] && Model\Contact::canReceivePrivateMessages($contact)) {
308                                 $wallmessage_link = 'message/new/' . $contact['id'];
309                         }
310
311                         $vcard_widget = Renderer::replaceMacros(Renderer::getMarkupTemplate('widget/vcard.tpl'), [
312                                 '$name'         => $contact['name'],
313                                 '$photo'        => $contact['photo'],
314                                 '$url'          => Model\Contact::magicLinkByContact($contact, $contact['url']),
315                                 '$addr'         => $contact['addr'] ?? '',
316                                 '$network_link' => $network_link,
317                                 '$network'      => L10n::t('Network:'),
318                                 '$account_type' => Model\Contact::getAccountType($contact),
319                                 '$follow'       => L10n::t('Follow'),
320                                 '$follow_link'   => $follow_link,
321                                 '$unfollow'     => L10n::t('Unfollow'),
322                                 '$unfollow_link' => $unfollow_link,
323                                 '$wallmessage'  => L10n::t('Message'),
324                                 '$wallmessage_link' => $wallmessage_link,
325                         ]);
326
327                         $findpeople_widget = '';
328                         $follow_widget = '';
329                         $networks_widget = '';
330                         $rel_widget = '';
331                 } else {
332                         $vcard_widget = '';
333                         $findpeople_widget = Widget::findPeople();
334                         if (isset($_GET['add'])) {
335                                 $follow_widget = Widget::follow($_GET['add']);
336                         } else {
337                                 $follow_widget = Widget::follow();
338                         }
339
340                         $networks_widget = Widget::networks($_SERVER['REQUEST_URI'], $nets);
341                         $rel_widget = Widget::contactRels($_SERVER['REQUEST_URI'], $rel);
342                 }
343
344                 if ($contact['uid'] != 0) {
345                         $groups_widget = Model\Group::sidebarWidget('contact', 'group', 'full', 'everyone', $contact_id);
346                 } else {
347                         $groups_widget = null;
348                 }
349
350                 $a->page['aside'] .= $vcard_widget . $findpeople_widget . $follow_widget . $groups_widget . $networks_widget . $rel_widget;
351
352                 $tpl = Renderer::getMarkupTemplate('contacts-head.tpl');
353                 $a->page['htmlhead'] .= Renderer::replaceMacros($tpl, [
354                         '$baseurl' => DI::baseUrl()->get(true),
355                 ]);
356
357                 $sort_type = 0;
358                 $o = '';
359                 Nav::setSelected('contact');
360
361                 if (!local_user()) {
362                         notice(L10n::t('Permission denied.') . EOL);
363                         return Login::form();
364                 }
365
366                 if ($a->argc == 3) {
367                         $contact_id = intval($a->argv[1]);
368                         if (!$contact_id) {
369                                 throw new BadRequestException();
370                         }
371
372                         // @TODO: Replace with parameter from router
373                         $cmd = $a->argv[2];
374
375                         $orig_record = DBA::selectFirst('contact', [], ['id' => $contact_id, 'uid' => [0, local_user()], 'self' => false, 'deleted' => false]);
376                         if (!DBA::isResult($orig_record)) {
377                                 throw new NotFoundException(L10n::t('Contact not found'));
378                         }
379
380                         if ($cmd === 'update' && ($orig_record['uid'] != 0)) {
381                                 self::updateContactFromPoll($contact_id);
382                                 DI::baseUrl()->redirect('contact/' . $contact_id);
383                                 // NOTREACHED
384                         }
385
386                         if ($cmd === 'updateprofile' && ($orig_record['uid'] != 0)) {
387                                 self::updateContactFromProbe($contact_id);
388                                 DI::baseUrl()->redirect('crepair/' . $contact_id);
389                                 // NOTREACHED
390                         }
391
392                         if ($cmd === 'block') {
393                                 self::blockContact($contact_id);
394
395                                 $blocked = Model\Contact::isBlockedByUser($contact_id, local_user());
396                                 info(($blocked ? L10n::t('Contact has been blocked') : L10n::t('Contact has been unblocked')) . EOL);
397
398                                 DI::baseUrl()->redirect('contact/' . $contact_id);
399                                 // NOTREACHED
400                         }
401
402                         if ($cmd === 'ignore') {
403                                 self::ignoreContact($contact_id);
404
405                                 $ignored = Model\Contact::isIgnoredByUser($contact_id, local_user());
406                                 info(($ignored ? L10n::t('Contact has been ignored') : L10n::t('Contact has been unignored')) . EOL);
407
408                                 DI::baseUrl()->redirect('contact/' . $contact_id);
409                                 // NOTREACHED
410                         }
411
412                         if ($cmd === 'archive' && ($orig_record['uid'] != 0)) {
413                                 $r = self::archiveContact($contact_id, $orig_record);
414                                 if ($r) {
415                                         $archived = (($orig_record['archive']) ? 0 : 1);
416                                         info((($archived) ? L10n::t('Contact has been archived') : L10n::t('Contact has been unarchived')) . EOL);
417                                 }
418
419                                 DI::baseUrl()->redirect('contact/' . $contact_id);
420                                 // NOTREACHED
421                         }
422
423                         if ($cmd === 'drop' && ($orig_record['uid'] != 0)) {
424                                 // Check if we should do HTML-based delete confirmation
425                                 if (!empty($_REQUEST['confirm'])) {
426                                         // <form> can't take arguments in its 'action' parameter
427                                         // so add any arguments as hidden inputs
428                                         $query = explode_querystring(DI::args()->getQueryString());
429                                         $inputs = [];
430                                         foreach ($query['args'] as $arg) {
431                                                 if (strpos($arg, 'confirm=') === false) {
432                                                         $arg_parts = explode('=', $arg);
433                                                         $inputs[] = ['name' => $arg_parts[0], 'value' => $arg_parts[1]];
434                                                 }
435                                         }
436
437                                         $a->page['aside'] = '';
438
439                                         return Renderer::replaceMacros(Renderer::getMarkupTemplate('contact_drop_confirm.tpl'), [
440                                                 '$header' => L10n::t('Drop contact'),
441                                                 '$contact' => self::getContactTemplateVars($orig_record),
442                                                 '$method' => 'get',
443                                                 '$message' => L10n::t('Do you really want to delete this contact?'),
444                                                 '$extra_inputs' => $inputs,
445                                                 '$confirm' => L10n::t('Yes'),
446                                                 '$confirm_url' => $query['base'],
447                                                 '$confirm_name' => 'confirmed',
448                                                 '$cancel' => L10n::t('Cancel'),
449                                         ]);
450                                 }
451                                 // Now check how the user responded to the confirmation query
452                                 if (!empty($_REQUEST['canceled'])) {
453                                         DI::baseUrl()->redirect('contact');
454                                 }
455
456                                 self::dropContact($orig_record);
457                                 info(L10n::t('Contact has been removed.') . EOL);
458
459                                 DI::baseUrl()->redirect('contact');
460                                 // NOTREACHED
461                         }
462                         if ($cmd === 'posts') {
463                                 return self::getPostsHTML($a, $contact_id);
464                         }
465                         if ($cmd === 'conversations') {
466                                 return self::getConversationsHMTL($a, $contact_id, $update);
467                         }
468                 }
469
470                 $_SESSION['return_path'] = DI::args()->getQueryString();
471
472                 if (!empty($a->data['contact']) && is_array($a->data['contact'])) {
473                         $contact = $a->data['contact'];
474
475                         $a->page['htmlhead'] .= Renderer::replaceMacros(Renderer::getMarkupTemplate('contact_head.tpl'), [
476                                 '$baseurl' => DI::baseUrl()->get(true),
477                         ]);
478
479                         $contact['blocked']  = Model\Contact::isBlockedByUser($contact['id'], local_user());
480                         $contact['readonly'] = Model\Contact::isIgnoredByUser($contact['id'], local_user());
481
482                         $dir_icon = '';
483                         $relation_text = '';
484                         switch ($contact['rel']) {
485                                 case Model\Contact::FRIEND:
486                                         $dir_icon = 'images/lrarrow.gif';
487                                         $relation_text = L10n::t('You are mutual friends with %s');
488                                         break;
489
490                                 case Model\Contact::FOLLOWER;
491                                         $dir_icon = 'images/larrow.gif';
492                                         $relation_text = L10n::t('You are sharing with %s');
493                                         break;
494
495                                 case Model\Contact::SHARING;
496                                         $dir_icon = 'images/rarrow.gif';
497                                         $relation_text = L10n::t('%s is sharing with you');
498                                         break;
499
500                                 default:
501                                         break;
502                         }
503
504                         if ($contact['uid'] == 0) {
505                                 $relation_text = '';
506                         }
507
508                         if (!in_array($contact['network'], Protocol::FEDERATED)) {
509                                 $relation_text = '';
510                         }
511
512                         $relation_text = sprintf($relation_text, $contact['name']);
513
514                         $url = Model\Contact::magicLink($contact['url']);
515                         if (strpos($url, 'redir/') === 0) {
516                                 $sparkle = ' class="sparkle" ';
517                         } else {
518                                 $sparkle = '';
519                         }
520
521                         $insecure = L10n::t('Private communications are not available for this contact.');
522
523                         $last_update = (($contact['last-update'] <= DBA::NULL_DATETIME) ? L10n::t('Never') : DateTimeFormat::local($contact['last-update'], 'D, j M Y, g:i A'));
524
525                         if ($contact['last-update'] > DBA::NULL_DATETIME) {
526                                 $last_update .= ' ' . (($contact['last-update'] <= $contact['success_update']) ? L10n::t('(Update was successful)') : L10n::t('(Update was not successful)'));
527                         }
528                         $lblsuggest = (($contact['network'] === Protocol::DFRN) ? L10n::t('Suggest friends') : '');
529
530                         $poll_enabled = in_array($contact['network'], [Protocol::DFRN, Protocol::OSTATUS, Protocol::FEED, Protocol::MAIL]);
531
532                         $nettype = L10n::t('Network type: %s', ContactSelector::networkToName($contact['network'], $contact['url'], $contact['protocol']));
533
534                         // tabs
535                         $tab_str = self::getTabsHTML($a, $contact, 3);
536
537                         $lost_contact = (($contact['archive'] && $contact['term-date'] > DBA::NULL_DATETIME && $contact['term-date'] < DateTimeFormat::utcNow()) ? L10n::t('Communications lost with this contact!') : '');
538
539                         $fetch_further_information = null;
540                         if ($contact['network'] == Protocol::FEED) {
541                                 $fetch_further_information = [
542                                         'fetch_further_information',
543                                         L10n::t('Fetch further information for feeds'),
544                                         $contact['fetch_further_information'],
545                                         L10n::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.'),
546                                         [
547                                                 '0' => L10n::t('Disabled'),
548                                                 '1' => L10n::t('Fetch information'),
549                                                 '3' => L10n::t('Fetch keywords'),
550                                                 '2' => L10n::t('Fetch information and keywords')
551                                         ]
552                                 ];
553                         }
554
555                         $poll_interval = null;
556                         if (in_array($contact['network'], [Protocol::FEED, Protocol::MAIL])) {
557                                 $poll_interval = ContactSelector::pollInterval($contact['priority'], !$poll_enabled);
558                         }
559
560                         $profile_select = null;
561                         if ($contact['network'] == Protocol::DFRN) {
562                                 $profile_select = ContactSelector::profileAssign($contact['profile-id'], $contact['network'] !== Protocol::DFRN);
563                         }
564
565                         // Load contactact related actions like hide, suggest, delete and others
566                         $contact_actions = self::getContactActions($contact);
567
568                         if ($contact['uid'] != 0) {
569                                 $lbl_vis1 = L10n::t('Profile Visibility');
570                                 $lbl_info1 = L10n::t('Contact Information / Notes');
571                                 $contact_settings_label = L10n::t('Contact Settings');
572                         } else {
573                                 $lbl_vis1 = null;
574                                 $lbl_info1 = null;
575                                 $contact_settings_label = null;
576                         }
577
578                         $tpl = Renderer::getMarkupTemplate('contact_edit.tpl');
579                         $o .= Renderer::replaceMacros($tpl, [
580                                 '$header'         => L10n::t('Contact'),
581                                 '$tab_str'        => $tab_str,
582                                 '$submit'         => L10n::t('Submit'),
583                                 '$lbl_vis1'       => $lbl_vis1,
584                                 '$lbl_vis2'       => L10n::t('Please choose the profile you would like to display to %s when viewing your profile securely.', $contact['name']),
585                                 '$lbl_info1'      => $lbl_info1,
586                                 '$lbl_info2'      => L10n::t('Their personal note'),
587                                 '$reason'         => trim(Strings::escapeTags($contact['reason'])),
588                                 '$infedit'        => L10n::t('Edit contact notes'),
589                                 '$common_link'    => 'common/loc/' . local_user() . '/' . $contact['id'],
590                                 '$relation_text'  => $relation_text,
591                                 '$visit'          => L10n::t('Visit %s\'s profile [%s]', $contact['name'], $contact['url']),
592                                 '$blockunblock'   => L10n::t('Block/Unblock contact'),
593                                 '$ignorecont'     => L10n::t('Ignore contact'),
594                                 '$lblcrepair'     => L10n::t('Repair URL settings'),
595                                 '$lblrecent'      => L10n::t('View conversations'),
596                                 '$lblsuggest'     => $lblsuggest,
597                                 '$nettype'        => $nettype,
598                                 '$poll_interval'  => $poll_interval,
599                                 '$poll_enabled'   => $poll_enabled,
600                                 '$lastupdtext'    => L10n::t('Last update:'),
601                                 '$lost_contact'   => $lost_contact,
602                                 '$updpub'         => L10n::t('Update public posts'),
603                                 '$last_update'    => $last_update,
604                                 '$udnow'          => L10n::t('Update now'),
605                                 '$profile_select' => $profile_select,
606                                 '$contact_id'     => $contact['id'],
607                                 '$block_text'     => ($contact['blocked'] ? L10n::t('Unblock') : L10n::t('Block')),
608                                 '$ignore_text'    => ($contact['readonly'] ? L10n::t('Unignore') : L10n::t('Ignore')),
609                                 '$insecure'       => (in_array($contact['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::MAIL, Protocol::DIASPORA]) ? '' : $insecure),
610                                 '$info'           => $contact['info'],
611                                 '$cinfo'          => ['info', '', $contact['info'], ''],
612                                 '$blocked'        => ($contact['blocked'] ? L10n::t('Currently blocked') : ''),
613                                 '$ignored'        => ($contact['readonly'] ? L10n::t('Currently ignored') : ''),
614                                 '$archived'       => ($contact['archive'] ? L10n::t('Currently archived') : ''),
615                                 '$pending'        => ($contact['pending'] ? L10n::t('Awaiting connection acknowledge') : ''),
616                                 '$hidden'         => ['hidden', L10n::t('Hide this contact from others'), ($contact['hidden'] == 1), L10n::t('Replies/likes to your public posts <strong>may</strong> still be visible')],
617                                 '$notify'         => ['notify', L10n::t('Notification for new posts'), ($contact['notify_new_posts'] == 1), L10n::t('Send a notification of every new post of this contact')],
618                                 '$fetch_further_information' => $fetch_further_information,
619                                 '$ffi_keyword_blacklist' => ['ffi_keyword_blacklist', L10n::t('Blacklisted keywords'), $contact['ffi_keyword_blacklist'], L10n::t('Comma separated list of keywords that should not be converted to hashtags, when "Fetch information and keywords" is selected')],
620                                 '$photo'          => $contact['photo'],
621                                 '$name'           => $contact['name'],
622                                 '$dir_icon'       => $dir_icon,
623                                 '$sparkle'        => $sparkle,
624                                 '$url'            => $url,
625                                 '$profileurllabel'=> L10n::t('Profile URL'),
626                                 '$profileurl'     => $contact['url'],
627                                 '$account_type'   => Model\Contact::getAccountType($contact),
628                                 '$location'       => BBCode::convert($contact['location']),
629                                 '$location_label' => L10n::t('Location:'),
630                                 '$xmpp'           => BBCode::convert($contact['xmpp']),
631                                 '$xmpp_label'     => L10n::t('XMPP:'),
632                                 '$about'          => BBCode::convert($contact['about'], false),
633                                 '$about_label'    => L10n::t('About:'),
634                                 '$keywords'       => $contact['keywords'],
635                                 '$keywords_label' => L10n::t('Tags:'),
636                                 '$contact_action_button' => L10n::t('Actions'),
637                                 '$contact_actions'=> $contact_actions,
638                                 '$contact_status' => L10n::t('Status'),
639                                 '$contact_settings_label' => $contact_settings_label,
640                                 '$contact_profile_label' => L10n::t('Profile'),
641                         ]);
642
643                         $arr = ['contact' => $contact, 'output' => $o];
644
645                         Hook::callAll('contact_edit', $arr);
646
647                         return $arr['output'];
648                 }
649
650                 $select_uid = local_user();
651
652                 // @TODO: Replace with parameter from router
653                 $type = $a->argv[1] ?? '';
654
655                 switch ($type) {
656                         case 'blocked':
657                                 $sql_extra = sprintf(" AND EXISTS(SELECT `id` from `user-contact` WHERE `contact`.`id` = `user-contact`.`cid` and `user-contact`.`uid` = %d and `user-contact`.`blocked`)", intval(local_user()));
658                                 $select_uid = 0;
659                                 break;
660                         case 'hidden':
661                                 $sql_extra = " AND `hidden` AND NOT `blocked` AND NOT `pending`";
662                                 break;
663                         case 'ignored':
664                                 $sql_extra = sprintf(" AND EXISTS(SELECT `id` from `user-contact` WHERE `contact`.`id` = `user-contact`.`cid` and `user-contact`.`uid` = %d and `user-contact`.`ignored`)", intval(local_user()));
665                                 $select_uid = 0;
666                                 break;
667                         case 'archived':
668                                 $sql_extra = " AND `archive` AND NOT `blocked` AND NOT `pending`";
669                                 break;
670                         case 'pending':
671                                 $sql_extra = sprintf(" AND `pending` AND NOT `archive` AND ((`rel` = %d)
672                                         OR EXISTS (SELECT `id` FROM `intro` WHERE `contact-id` = `contact`.`id` AND NOT `ignore`))", Model\Contact::SHARING);
673                                 break;
674                         default:
675                                 $sql_extra = " AND NOT `archive` AND NOT `blocked` AND NOT `pending`";
676                 }
677
678                 $sql_extra .= sprintf(" AND `network` != '%s' ", Protocol::PHANTOM);
679
680                 $search = Strings::escapeTags(trim($_GET['search'] ?? ''));
681                 $nets   = Strings::escapeTags(trim($_GET['nets']   ?? ''));
682                 $rel    = Strings::escapeTags(trim($_GET['rel']    ?? ''));
683
684                 $tabs = [
685                         [
686                                 'label' => L10n::t('All Contacts'),
687                                 'url'   => 'contact',
688                                 'sel'   => !$type ? 'active' : '',
689                                 'title' => L10n::t('Show all contacts'),
690                                 'id'    => 'showall-tab',
691                                 'accesskey' => 'l',
692                         ],
693                         [
694                                 'label' => L10n::t('Pending'),
695                                 'url'   => 'contact/pending',
696                                 'sel'   => $type == 'pending' ? 'active' : '',
697                                 'title' => L10n::t('Only show pending contacts'),
698                                 'id'    => 'showpending-tab',
699                                 'accesskey' => 'p',
700                         ],
701                         [
702                                 'label' => L10n::t('Blocked'),
703                                 'url'   => 'contact/blocked',
704                                 'sel'   => $type == 'blocked' ? 'active' : '',
705                                 'title' => L10n::t('Only show blocked contacts'),
706                                 'id'    => 'showblocked-tab',
707                                 'accesskey' => 'b',
708                         ],
709                         [
710                                 'label' => L10n::t('Ignored'),
711                                 'url'   => 'contact/ignored',
712                                 'sel'   => $type == 'ignored' ? 'active' : '',
713                                 'title' => L10n::t('Only show ignored contacts'),
714                                 'id'    => 'showignored-tab',
715                                 'accesskey' => 'i',
716                         ],
717                         [
718                                 'label' => L10n::t('Archived'),
719                                 'url'   => 'contact/archived',
720                                 'sel'   => $type == 'archived' ? 'active' : '',
721                                 'title' => L10n::t('Only show archived contacts'),
722                                 'id'    => 'showarchived-tab',
723                                 'accesskey' => 'y',
724                         ],
725                         [
726                                 'label' => L10n::t('Hidden'),
727                                 'url'   => 'contact/hidden',
728                                 'sel'   => $type == 'hidden' ? 'active' : '',
729                                 'title' => L10n::t('Only show hidden contacts'),
730                                 'id'    => 'showhidden-tab',
731                                 'accesskey' => 'h',
732                         ],
733                         [
734                                 'label' => L10n::t('Groups'),
735                                 'url'   => 'group',
736                                 'sel'   => '',
737                                 'title' => L10n::t('Organize your contact groups'),
738                                 'id'    => 'contactgroups-tab',
739                                 'accesskey' => 'e',
740                         ],
741                 ];
742
743                 $tab_tpl = Renderer::getMarkupTemplate('common_tabs.tpl');
744                 $t = Renderer::replaceMacros($tab_tpl, ['$tabs' => $tabs]);
745
746                 $total = 0;
747                 $searching = false;
748                 $search_hdr = null;
749                 if ($search) {
750                         $searching = true;
751                         $search_hdr = $search;
752                         $search_txt = DBA::escape(Strings::protectSprintf(preg_quote($search)));
753                         $sql_extra .= " AND (name REGEXP '$search_txt' OR url REGEXP '$search_txt'  OR nick REGEXP '$search_txt') ";
754                 }
755
756                 if ($nets) {
757                         $sql_extra .= sprintf(" AND network = '%s' ", DBA::escape($nets));
758                 }
759
760                 switch ($rel) {
761                         case 'followers': $sql_extra .= " AND `rel` IN (1, 3)"; break;
762                         case 'following': $sql_extra .= " AND `rel` IN (2, 3)"; break;
763                         case 'mutuals': $sql_extra .= " AND `rel` = 3"; break;
764                 }
765
766                 $sql_extra .=  " AND NOT `deleted` ";
767
768                 $sql_extra2 = ((($sort_type > 0) && ($sort_type <= Model\Contact::FRIEND)) ? sprintf(" AND `rel` = %d ", intval($sort_type)) : '');
769
770                 $sql_extra3 = Widget::unavailableNetworks();
771
772                 $r = q("SELECT COUNT(*) AS `total` FROM `contact`
773                         WHERE `uid` = %d AND `self` = 0 $sql_extra $sql_extra2 $sql_extra3",
774                         intval($select_uid)
775                 );
776                 if (DBA::isResult($r)) {
777                         $total = $r[0]['total'];
778                 }
779                 $pager = new Pager(DI::args()->getQueryString());
780
781                 $contacts = [];
782
783                 $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `self` = 0 $sql_extra $sql_extra2 $sql_extra3 ORDER BY `name` ASC LIMIT %d , %d ",
784                         intval($select_uid),
785                         $pager->getStart(),
786                         $pager->getItemsPerPage()
787                 );
788                 if (DBA::isResult($r)) {
789                         foreach ($r as $rr) {
790                                 $rr['blocked'] = Model\Contact::isBlockedByUser($rr['id'], local_user());
791                                 $rr['readonly'] = Model\Contact::isIgnoredByUser($rr['id'], local_user());
792                                 $contacts[] = self::getContactTemplateVars($rr);
793                         }
794                 }
795
796                 switch ($rel) {
797                         case 'followers': $header = L10n::t('Followers'); break;
798                         case 'following': $header = L10n::t('Following'); break;
799                         case 'mutuals':   $header = L10n::t('Mutual friends'); break;
800                         default:          $header = L10n::t('Contacts');
801                 }
802
803                 switch ($type) {
804                         case 'pending':  $header .= ' - ' . L10n::t('Pending'); break;
805                         case 'blocked':  $header .= ' - ' . L10n::t('Blocked'); break;
806                         case 'hidden':   $header .= ' - ' . L10n::t('Hidden'); break;
807                         case 'ignored':  $header .= ' - ' . L10n::t('Ignored'); break;
808                         case 'archived': $header .= ' - ' . L10n::t('Archived'); break;
809                 }
810
811                 $header .= $nets ? ' - ' . ContactSelector::networkToName($nets) : '';
812
813                 $tpl = Renderer::getMarkupTemplate('contacts-template.tpl');
814                 $o .= Renderer::replaceMacros($tpl, [
815                         '$header'     => $header,
816                         '$tabs'       => $t,
817                         '$total'      => $total,
818                         '$search'     => $search_hdr,
819                         '$desc'       => L10n::t('Search your contacts'),
820                         '$finding'    => $searching ? L10n::t('Results for: %s', $search) : '',
821                         '$submit'     => L10n::t('Find'),
822                         '$cmd'        => DI::args()->getCommand(),
823                         '$contacts'   => $contacts,
824                         '$contact_drop_confirm' => L10n::t('Do you really want to delete this contact?'),
825                         'multiselect' => 1,
826                         '$batch_actions' => [
827                                 'contacts_batch_update'  => L10n::t('Update'),
828                                 'contacts_batch_block'   => L10n::t('Block') . '/' . L10n::t('Unblock'),
829                                 'contacts_batch_ignore'  => L10n::t('Ignore') . '/' . L10n::t('Unignore'),
830                                 'contacts_batch_archive' => L10n::t('Archive') . '/' . L10n::t('Unarchive'),
831                                 'contacts_batch_drop'    => L10n::t('Delete'),
832                         ],
833                         '$h_batch_actions' => L10n::t('Batch Actions'),
834                         '$paginate'   => $pager->renderFull($total),
835                 ]);
836
837                 return $o;
838         }
839
840         /**
841          * @brief List of pages for the Contact TabBar
842          *
843          * Available Pages are 'Status', 'Profile', 'Contacts' and 'Common Friends'
844          *
845          * @param App   $a
846          * @param array $contact    The contact array
847          * @param int   $active_tab 1 if tab should be marked as active
848          *
849          * @return string HTML string of the contact page tabs buttons.
850          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
851          */
852         public static function getTabsHTML($a, $contact, $active_tab)
853         {
854                 // tabs
855                 $tabs = [
856                         [
857                                 'label' => L10n::t('Status'),
858                                 'url'   => "contact/" . $contact['id'] . "/conversations",
859                                 'sel'   => (($active_tab == 1) ? 'active' : ''),
860                                 'title' => L10n::t('Conversations started by this contact'),
861                                 'id'    => 'status-tab',
862                                 'accesskey' => 'm',
863                         ],
864                         [
865                                 'label' => L10n::t('Posts and Comments'),
866                                 'url'   => "contact/" . $contact['id'] . "/posts",
867                                 'sel'   => (($active_tab == 2) ? 'active' : ''),
868                                 'title' => L10n::t('Status Messages and Posts'),
869                                 'id'    => 'posts-tab',
870                                 'accesskey' => 'p',
871                         ],
872                         [
873                                 'label' => L10n::t('Profile'),
874                                 'url'   => "contact/" . $contact['id'],
875                                 'sel'   => (($active_tab == 3) ? 'active' : ''),
876                                 'title' => L10n::t('Profile Details'),
877                                 'id'    => 'profile-tab',
878                                 'accesskey' => 'o',
879                         ]
880                 ];
881
882                 // Show this tab only if there is visible friend list
883                 $x = Model\GContact::countAllFriends(local_user(), $contact['id']);
884                 if ($x) {
885                         $tabs[] = ['label' => L10n::t('Contacts'),
886                                 'url'   => "allfriends/" . $contact['id'],
887                                 'sel'   => (($active_tab == 4) ? 'active' : ''),
888                                 'title' => L10n::t('View all contacts'),
889                                 'id'    => 'allfriends-tab',
890                                 'accesskey' => 't'];
891                 }
892
893                 // Show this tab only if there is visible common friend list
894                 $common = Model\GContact::countCommonFriends(local_user(), $contact['id']);
895                 if ($common) {
896                         $tabs[] = ['label' => L10n::t('Common Friends'),
897                                 'url'   => "common/loc/" . local_user() . "/" . $contact['id'],
898                                 'sel'   => (($active_tab == 5) ? 'active' : ''),
899                                 'title' => L10n::t('View all common friends'),
900                                 'id'    => 'common-loc-tab',
901                                 'accesskey' => 'd'
902                         ];
903                 }
904
905                 if (!empty($contact['uid'])) {
906                         $tabs[] = ['label' => L10n::t('Advanced'),
907                                 'url'   => 'crepair/' . $contact['id'],
908                                 'sel'   => (($active_tab == 6) ? 'active' : ''),
909                                 'title' => L10n::t('Advanced Contact Settings'),
910                                 'id'    => 'advanced-tab',
911                                 'accesskey' => 'r'
912                         ];
913                 }
914
915                 $tab_tpl = Renderer::getMarkupTemplate('common_tabs.tpl');
916                 $tab_str = Renderer::replaceMacros($tab_tpl, ['$tabs' => $tabs]);
917
918                 return $tab_str;
919         }
920
921         private static function getConversationsHMTL($a, $contact_id, $update)
922         {
923                 $o = '';
924
925                 if (!$update) {
926                         // We need the editor here to be able to reshare an item.
927                         if (local_user()) {
928                                 $x = [
929                                         'is_owner' => true,
930                                         'allow_location' => $a->user['allow_location'],
931                                         'default_location' => $a->user['default-location'],
932                                         'nickname' => $a->user['nickname'],
933                                         'lockstate' => (is_array($a->user) && (strlen($a->user['allow_cid']) || strlen($a->user['allow_gid']) || strlen($a->user['deny_cid']) || strlen($a->user['deny_gid'])) ? 'lock' : 'unlock'),
934                                         'acl' => ACL::getFullSelectorHTML($a->page, $a->user, true),
935                                         'bang' => '',
936                                         'visitor' => 'block',
937                                         'profile_uid' => local_user(),
938                                 ];
939                                 $o = status_editor($a, $x, 0, true);
940                         }
941                 }
942
943                 $contact = DBA::selectFirst('contact', ['uid', 'url', 'id'], ['id' => $contact_id, 'deleted' => false]);
944
945                 if (!$update) {
946                         $o .= self::getTabsHTML($a, $contact, 1);
947                 }
948
949                 if (DBA::isResult($contact)) {
950                         $a->page['aside'] = '';
951
952                         $profiledata = Model\Contact::getDetailsByURL($contact['url']);
953
954                         Model\Profile::load($a, '', 0, $profiledata, true);
955                         $o .= Model\Contact::getPostsFromUrl($contact['url'], true, $update);
956                 }
957
958                 return $o;
959         }
960
961         private static function getPostsHTML($a, $contact_id)
962         {
963                 $contact = DBA::selectFirst('contact', ['uid', 'url', 'id'], ['id' => $contact_id, 'deleted' => false]);
964
965                 $o = self::getTabsHTML($a, $contact, 2);
966
967                 if (DBA::isResult($contact)) {
968                         $a->page['aside'] = '';
969
970                         $profiledata = Model\Contact::getDetailsByURL($contact['url']);
971
972                         if (local_user() && in_array($profiledata['network'], Protocol::FEDERATED)) {
973                                 $profiledata['remoteconnect'] = System::baseUrl() . '/follow?url=' . urlencode($profiledata['url']);
974                         }
975
976                         Model\Profile::load($a, '', 0, $profiledata, true);
977                         $o .= Model\Contact::getPostsFromUrl($contact['url']);
978                 }
979
980                 return $o;
981         }
982
983         public static function getContactTemplateVars(array $rr)
984         {
985                 $dir_icon = '';
986                 $alt_text = '';
987
988                 if (!empty($rr['uid']) && !empty($rr['rel'])) {
989                         switch ($rr['rel']) {
990                                 case Model\Contact::FRIEND:
991                                         $dir_icon = 'images/lrarrow.gif';
992                                         $alt_text = L10n::t('Mutual Friendship');
993                                         break;
994
995                                 case Model\Contact::FOLLOWER;
996                                         $dir_icon = 'images/larrow.gif';
997                                         $alt_text = L10n::t('is a fan of yours');
998                                         break;
999
1000                                 case Model\Contact::SHARING;
1001                                         $dir_icon = 'images/rarrow.gif';
1002                                         $alt_text = L10n::t('you are a fan of');
1003                                         break;
1004
1005                                 default:
1006                                         break;
1007                         }
1008                 }
1009
1010                 $url = Model\Contact::magicLink($rr['url']);
1011
1012                 if (strpos($url, 'redir/') === 0) {
1013                         $sparkle = ' class="sparkle" ';
1014                 } else {
1015                         $sparkle = '';
1016                 }
1017
1018                 if ($rr['pending']) {
1019                         if (in_array($rr['rel'], [Model\Contact::FRIEND, Model\Contact::SHARING])) {
1020                                 $alt_text = L10n::t('Pending outgoing contact request');
1021                         } else {
1022                                 $alt_text = L10n::t('Pending incoming contact request');
1023                         }
1024                 }
1025
1026                 if ($rr['self']) {
1027                         $dir_icon = 'images/larrow.gif';
1028                         $alt_text = L10n::t('This is you');
1029                         $url = $rr['url'];
1030                         $sparkle = '';
1031                 }
1032
1033                 return [
1034                         'img_hover' => L10n::t('Visit %s\'s profile [%s]', $rr['name'], $rr['url']),
1035                         'edit_hover'=> L10n::t('Edit contact'),
1036                         'photo_menu'=> Model\Contact::photoMenu($rr),
1037                         'id'        => $rr['id'],
1038                         'alt_text'  => $alt_text,
1039                         'dir_icon'  => $dir_icon,
1040                         'thumb'     => ProxyUtils::proxifyUrl($rr['thumb'], false, ProxyUtils::SIZE_THUMB),
1041                         'name'      => $rr['name'],
1042                         'username'  => $rr['name'],
1043                         'account_type' => Model\Contact::getAccountType($rr),
1044                         'sparkle'   => $sparkle,
1045                         'itemurl'   => ($rr['addr'] ?? '') ?: $rr['url'],
1046                         'url'       => $url,
1047                         'network'   => ContactSelector::networkToName($rr['network'], $rr['url'], $rr['protocol']),
1048                         'nick'      => $rr['nick'],
1049                 ];
1050         }
1051
1052         /**
1053          * @brief Gives a array with actions which can performed to a given contact
1054          *
1055          * This includes actions like e.g. 'block', 'hide', 'archive', 'delete' and others
1056          *
1057          * @param array $contact Data about the Contact
1058          * @return array with contact related actions
1059          */
1060         private static function getContactActions($contact)
1061         {
1062                 $poll_enabled = in_array($contact['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::OSTATUS, Protocol::FEED, Protocol::MAIL]);
1063                 $contact_actions = [];
1064
1065                 // Provide friend suggestion only for Friendica contacts
1066                 if ($contact['network'] === Protocol::DFRN) {
1067                         $contact_actions['suggest'] = [
1068                                 'label' => L10n::t('Suggest friends'),
1069                                 'url'   => 'fsuggest/' . $contact['id'],
1070                                 'title' => '',
1071                                 'sel'   => '',
1072                                 'id'    => 'suggest',
1073                         ];
1074                 }
1075
1076                 if ($poll_enabled) {
1077                         $contact_actions['update'] = [
1078                                 'label' => L10n::t('Update now'),
1079                                 'url'   => 'contact/' . $contact['id'] . '/update',
1080                                 'title' => '',
1081                                 'sel'   => '',
1082                                 'id'    => 'update',
1083                         ];
1084                 }
1085
1086                 $contact_actions['block'] = [
1087                         'label' => (intval($contact['blocked']) ? L10n::t('Unblock') : L10n::t('Block')),
1088                         'url'   => 'contact/' . $contact['id'] . '/block',
1089                         'title' => L10n::t('Toggle Blocked status'),
1090                         'sel'   => (intval($contact['blocked']) ? 'active' : ''),
1091                         'id'    => 'toggle-block',
1092                 ];
1093
1094                 $contact_actions['ignore'] = [
1095                         'label' => (intval($contact['readonly']) ? L10n::t('Unignore') : L10n::t('Ignore')),
1096                         'url'   => 'contact/' . $contact['id'] . '/ignore',
1097                         'title' => L10n::t('Toggle Ignored status'),
1098                         'sel'   => (intval($contact['readonly']) ? 'active' : ''),
1099                         'id'    => 'toggle-ignore',
1100                 ];
1101
1102                 if ($contact['uid'] != 0) {
1103                         $contact_actions['archive'] = [
1104                                 'label' => (intval($contact['archive']) ? L10n::t('Unarchive') : L10n::t('Archive')),
1105                                 'url'   => 'contact/' . $contact['id'] . '/archive',
1106                                 'title' => L10n::t('Toggle Archive status'),
1107                                 'sel'   => (intval($contact['archive']) ? 'active' : ''),
1108                                 'id'    => 'toggle-archive',
1109                         ];
1110
1111                         $contact_actions['delete'] = [
1112                                 'label' => L10n::t('Delete'),
1113                                 'url'   => 'contact/' . $contact['id'] . '/drop',
1114                                 'title' => L10n::t('Delete contact'),
1115                                 'sel'   => '',
1116                                 'id'    => 'delete',
1117                         ];
1118                 }
1119
1120                 return $contact_actions;
1121         }
1122 }