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