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