]> git.mxchange.org Git - friendica.git/blob - src/Module/Contact.php
36ec950519b2422985685466b59e49984d20fe79
[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(array $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(array $parameters = [], $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                 $select_uid = $_SESSION['uid'];
650
651                 // @TODO: Replace with parameter from router
652                 $type = $a->argv[1] ?? '';
653
654                 switch ($type) {
655                         case 'blocked':
656                                 $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($_SESSION['uid']));
657                                 $select_uid = 0;
658                                 break;
659                         case 'hidden':
660                                 $sql_extra = " AND `hidden` AND NOT `blocked` AND NOT `pending`";
661                                 break;
662                         case 'ignored':
663                                 $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($_SESSION['uid']));
664                                 $select_uid = 0;
665                                 break;
666                         case 'archived':
667                                 $sql_extra = " AND `archive` AND NOT `blocked` AND NOT `pending`";
668                                 break;
669                         case 'pending':
670                                 $sql_extra = sprintf(" AND `pending` AND NOT `archive` AND ((`rel` = %d)
671                                         OR EXISTS (SELECT `id` FROM `intro` WHERE `contact-id` = `contact`.`id` AND NOT `ignore`))", Model\Contact::SHARING);
672                                 break;
673                         default:
674                                 $sql_extra = " AND NOT `archive` AND NOT `blocked` AND NOT `pending`";
675                 }
676
677                 $sql_extra .= sprintf(" AND `network` != '%s' ", Protocol::PHANTOM);
678
679                 $search = Strings::escapeTags(trim($_GET['search'] ?? ''));
680                 $nets   = Strings::escapeTags(trim($_GET['nets']   ?? ''));
681                 $rel    = Strings::escapeTags(trim($_GET['rel']    ?? ''));
682
683                 $tabs = [
684                         [
685                                 'label' => L10n::t('All Contacts'),
686                                 'url'   => 'contact',
687                                 'sel'   => !$type ? 'active' : '',
688                                 'title' => L10n::t('Show all contacts'),
689                                 'id'    => 'showall-tab',
690                                 'accesskey' => 'l',
691                         ],
692                         [
693                                 'label' => L10n::t('Pending'),
694                                 'url'   => 'contact/pending',
695                                 'sel'   => $type == 'pending' ? 'active' : '',
696                                 'title' => L10n::t('Only show pending contacts'),
697                                 'id'    => 'showpending-tab',
698                                 'accesskey' => 'p',
699                         ],
700                         [
701                                 'label' => L10n::t('Blocked'),
702                                 'url'   => 'contact/blocked',
703                                 'sel'   => $type == 'blocked' ? 'active' : '',
704                                 'title' => L10n::t('Only show blocked contacts'),
705                                 'id'    => 'showblocked-tab',
706                                 'accesskey' => 'b',
707                         ],
708                         [
709                                 'label' => L10n::t('Ignored'),
710                                 'url'   => 'contact/ignored',
711                                 'sel'   => $type == 'ignored' ? 'active' : '',
712                                 'title' => L10n::t('Only show ignored contacts'),
713                                 'id'    => 'showignored-tab',
714                                 'accesskey' => 'i',
715                         ],
716                         [
717                                 'label' => L10n::t('Archived'),
718                                 'url'   => 'contact/archived',
719                                 'sel'   => $type == 'archived' ? 'active' : '',
720                                 'title' => L10n::t('Only show archived contacts'),
721                                 'id'    => 'showarchived-tab',
722                                 'accesskey' => 'y',
723                         ],
724                         [
725                                 'label' => L10n::t('Hidden'),
726                                 'url'   => 'contact/hidden',
727                                 'sel'   => $type == 'hidden' ? 'active' : '',
728                                 'title' => L10n::t('Only show hidden contacts'),
729                                 'id'    => 'showhidden-tab',
730                                 'accesskey' => 'h',
731                         ],
732                         [
733                                 'label' => L10n::t('Groups'),
734                                 'url'   => 'group',
735                                 'sel'   => '',
736                                 'title' => L10n::t('Organize your contact groups'),
737                                 'id'    => 'contactgroups-tab',
738                                 'accesskey' => 'e',
739                         ],
740                 ];
741
742                 $tab_tpl = Renderer::getMarkupTemplate('common_tabs.tpl');
743                 $t = Renderer::replaceMacros($tab_tpl, ['$tabs' => $tabs]);
744
745                 $total = 0;
746                 $searching = false;
747                 $search_hdr = null;
748                 if ($search) {
749                         $searching = true;
750                         $search_hdr = $search;
751                         $search_txt = DBA::escape(Strings::protectSprintf(preg_quote($search)));
752                         $sql_extra .= " AND (name REGEXP '$search_txt' OR url REGEXP '$search_txt'  OR nick REGEXP '$search_txt') ";
753                 }
754
755                 if ($nets) {
756                         $sql_extra .= sprintf(" AND network = '%s' ", DBA::escape($nets));
757                 }
758
759                 switch ($rel) {
760                         case 'followers': $sql_extra .= " AND `rel` IN (1, 3)"; break;
761                         case 'following': $sql_extra .= " AND `rel` IN (2, 3)"; break;
762                         case 'mutuals': $sql_extra .= " AND `rel` = 3"; break;
763                 }
764
765                 $sql_extra .=  " AND NOT `deleted` ";
766
767                 $sql_extra2 = ((($sort_type > 0) && ($sort_type <= Model\Contact::FRIEND)) ? sprintf(" AND `rel` = %d ", intval($sort_type)) : '');
768
769                 $sql_extra3 = Widget::unavailableNetworks();
770
771                 $r = q("SELECT COUNT(*) AS `total` FROM `contact`
772                         WHERE `uid` = %d AND `self` = 0 $sql_extra $sql_extra2 $sql_extra3",
773                         intval($select_uid)
774                 );
775                 if (DBA::isResult($r)) {
776                         $total = $r[0]['total'];
777                 }
778                 $pager = new Pager($a->query_string);
779
780                 $contacts = [];
781
782                 $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `self` = 0 $sql_extra $sql_extra2 $sql_extra3 ORDER BY `name` ASC LIMIT %d , %d ",
783                         intval($select_uid),
784                         $pager->getStart(),
785                         $pager->getItemsPerPage()
786                 );
787                 if (DBA::isResult($r)) {
788                         foreach ($r as $rr) {
789                                 $rr['blocked'] = Model\Contact::isBlockedByUser($rr['id'], local_user());
790                                 $rr['readonly'] = Model\Contact::isIgnoredByUser($rr['id'], local_user());
791                                 $contacts[] = self::getContactTemplateVars($rr);
792                         }
793                 }
794
795                 switch ($rel) {
796                         case 'followers': $header = L10n::t('Followers'); break;
797                         case 'following': $header = L10n::t('Following'); break;
798                         case 'mutuals':   $header = L10n::t('Mutual friends'); break;
799                         default:          $header = L10n::t('Contacts');
800                 }
801
802                 switch ($type) {
803                         case 'pending':  $header .= ' - ' . L10n::t('Pending'); break;
804                         case 'blocked':  $header .= ' - ' . L10n::t('Blocked'); break;
805                         case 'hidden':   $header .= ' - ' . L10n::t('Hidden'); break;
806                         case 'ignored':  $header .= ' - ' . L10n::t('Ignored'); break;
807                         case 'archived': $header .= ' - ' . L10n::t('Archived'); break;
808                 }
809
810                 $header .= $nets ? ' - ' . ContactSelector::networkToName($nets) : '';
811
812                 $tpl = Renderer::getMarkupTemplate('contacts-template.tpl');
813                 $o .= Renderer::replaceMacros($tpl, [
814                         '$header'     => $header,
815                         '$tabs'       => $t,
816                         '$total'      => $total,
817                         '$search'     => $search_hdr,
818                         '$desc'       => L10n::t('Search your contacts'),
819                         '$finding'    => $searching ? L10n::t('Results for: %s', $search) : '',
820                         '$submit'     => L10n::t('Find'),
821                         '$cmd'        => $a->cmd,
822                         '$contacts'   => $contacts,
823                         '$contact_drop_confirm' => L10n::t('Do you really want to delete this contact?'),
824                         'multiselect' => 1,
825                         '$batch_actions' => [
826                                 'contacts_batch_update'  => L10n::t('Update'),
827                                 'contacts_batch_block'   => L10n::t('Block') . '/' . L10n::t('Unblock'),
828                                 'contacts_batch_ignore'  => L10n::t('Ignore') . '/' . L10n::t('Unignore'),
829                                 'contacts_batch_archive' => L10n::t('Archive') . '/' . L10n::t('Unarchive'),
830                                 'contacts_batch_drop'    => L10n::t('Delete'),
831                         ],
832                         '$h_batch_actions' => L10n::t('Batch Actions'),
833                         '$paginate'   => $pager->renderFull($total),
834                 ]);
835
836                 return $o;
837         }
838
839         /**
840          * @brief List of pages for the Contact TabBar
841          *
842          * Available Pages are 'Status', 'Profile', 'Contacts' and 'Common Friends'
843          *
844          * @param App   $a
845          * @param array $contact    The contact array
846          * @param int   $active_tab 1 if tab should be marked as active
847          *
848          * @return string HTML string of the contact page tabs buttons.
849          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
850          */
851         public static function getTabsHTML($a, $contact, $active_tab)
852         {
853                 // tabs
854                 $tabs = [
855                         [
856                                 'label' => L10n::t('Status'),
857                                 'url'   => "contact/" . $contact['id'] . "/conversations",
858                                 'sel'   => (($active_tab == 1) ? 'active' : ''),
859                                 'title' => L10n::t('Conversations started by this contact'),
860                                 'id'    => 'status-tab',
861                                 'accesskey' => 'm',
862                         ],
863                         [
864                                 'label' => L10n::t('Posts and Comments'),
865                                 'url'   => "contact/" . $contact['id'] . "/posts",
866                                 'sel'   => (($active_tab == 2) ? 'active' : ''),
867                                 'title' => L10n::t('Status Messages and Posts'),
868                                 'id'    => 'posts-tab',
869                                 'accesskey' => 'p',
870                         ],
871                         [
872                                 'label' => L10n::t('Profile'),
873                                 'url'   => "contact/" . $contact['id'],
874                                 'sel'   => (($active_tab == 3) ? 'active' : ''),
875                                 'title' => L10n::t('Profile Details'),
876                                 'id'    => 'profile-tab',
877                                 'accesskey' => 'o',
878                         ]
879                 ];
880
881                 // Show this tab only if there is visible friend list
882                 $x = Model\GContact::countAllFriends(local_user(), $contact['id']);
883                 if ($x) {
884                         $tabs[] = ['label' => L10n::t('Contacts'),
885                                 'url'   => "allfriends/" . $contact['id'],
886                                 'sel'   => (($active_tab == 4) ? 'active' : ''),
887                                 'title' => L10n::t('View all contacts'),
888                                 'id'    => 'allfriends-tab',
889                                 'accesskey' => 't'];
890                 }
891
892                 // Show this tab only if there is visible common friend list
893                 $common = Model\GContact::countCommonFriends(local_user(), $contact['id']);
894                 if ($common) {
895                         $tabs[] = ['label' => L10n::t('Common Friends'),
896                                 'url'   => "common/loc/" . local_user() . "/" . $contact['id'],
897                                 'sel'   => (($active_tab == 5) ? 'active' : ''),
898                                 'title' => L10n::t('View all common friends'),
899                                 'id'    => 'common-loc-tab',
900                                 'accesskey' => 'd'
901                         ];
902                 }
903
904                 if (!empty($contact['uid'])) {
905                         $tabs[] = ['label' => L10n::t('Advanced'),
906                                 'url'   => 'crepair/' . $contact['id'],
907                                 'sel'   => (($active_tab == 6) ? 'active' : ''),
908                                 'title' => L10n::t('Advanced Contact Settings'),
909                                 'id'    => 'advanced-tab',
910                                 'accesskey' => 'r'
911                         ];
912                 }
913
914                 $tab_tpl = Renderer::getMarkupTemplate('common_tabs.tpl');
915                 $tab_str = Renderer::replaceMacros($tab_tpl, ['$tabs' => $tabs]);
916
917                 return $tab_str;
918         }
919
920         private static function getConversationsHMTL($a, $contact_id, $update)
921         {
922                 $o = '';
923
924                 if (!$update) {
925                         // We need the editor here to be able to reshare an item.
926                         if (local_user()) {
927                                 $x = [
928                                         'is_owner' => true,
929                                         'allow_location' => $a->user['allow_location'],
930                                         'default_location' => $a->user['default-location'],
931                                         'nickname' => $a->user['nickname'],
932                                         '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'),
933                                         'acl' => ACL::getFullSelectorHTML($a->page, $a->user, true),
934                                         'bang' => '',
935                                         'visitor' => 'block',
936                                         'profile_uid' => local_user(),
937                                 ];
938                                 $o = status_editor($a, $x, 0, true);
939                         }
940                 }
941
942                 $contact = DBA::selectFirst('contact', ['uid', 'url', 'id'], ['id' => $contact_id, 'deleted' => false]);
943
944                 if (!$update) {
945                         $o .= self::getTabsHTML($a, $contact, 1);
946                 }
947
948                 if (DBA::isResult($contact)) {
949                         $a->page['aside'] = '';
950
951                         $profiledata = Model\Contact::getDetailsByURL($contact['url']);
952
953                         Model\Profile::load($a, '', 0, $profiledata, true);
954                         $o .= Model\Contact::getPostsFromUrl($contact['url'], true, $update);
955                 }
956
957                 return $o;
958         }
959
960         private static function getPostsHTML($a, $contact_id)
961         {
962                 $contact = DBA::selectFirst('contact', ['uid', 'url', 'id'], ['id' => $contact_id, 'deleted' => false]);
963
964                 $o = self::getTabsHTML($a, $contact, 2);
965
966                 if (DBA::isResult($contact)) {
967                         $a->page['aside'] = '';
968
969                         $profiledata = Model\Contact::getDetailsByURL($contact['url']);
970
971                         if (local_user() && in_array($profiledata['network'], Protocol::FEDERATED)) {
972                                 $profiledata['remoteconnect'] = System::baseUrl() . '/follow?url=' . urlencode($profiledata['url']);
973                         }
974
975                         Model\Profile::load($a, '', 0, $profiledata, true);
976                         $o .= Model\Contact::getPostsFromUrl($contact['url']);
977                 }
978
979                 return $o;
980         }
981
982         public static function getContactTemplateVars(array $rr)
983         {
984                 $dir_icon = '';
985                 $alt_text = '';
986
987                 if (!empty($rr['uid']) && !empty($rr['rel'])) {
988                         switch ($rr['rel']) {
989                                 case Model\Contact::FRIEND:
990                                         $dir_icon = 'images/lrarrow.gif';
991                                         $alt_text = L10n::t('Mutual Friendship');
992                                         break;
993
994                                 case Model\Contact::FOLLOWER;
995                                         $dir_icon = 'images/larrow.gif';
996                                         $alt_text = L10n::t('is a fan of yours');
997                                         break;
998
999                                 case Model\Contact::SHARING;
1000                                         $dir_icon = 'images/rarrow.gif';
1001                                         $alt_text = L10n::t('you are a fan of');
1002                                         break;
1003
1004                                 default:
1005                                         break;
1006                         }
1007                 }
1008
1009                 $url = Model\Contact::magicLink($rr['url']);
1010
1011                 if (strpos($url, 'redir/') === 0) {
1012                         $sparkle = ' class="sparkle" ';
1013                 } else {
1014                         $sparkle = '';
1015                 }
1016
1017                 if ($rr['pending']) {
1018                         if (in_array($rr['rel'], [Model\Contact::FRIEND, Model\Contact::SHARING])) {
1019                                 $alt_text = L10n::t('Pending outgoing contact request');
1020                         } else {
1021                                 $alt_text = L10n::t('Pending incoming contact request');
1022                         }
1023                 }
1024
1025                 if ($rr['self']) {
1026                         $dir_icon = 'images/larrow.gif';
1027                         $alt_text = L10n::t('This is you');
1028                         $url = $rr['url'];
1029                         $sparkle = '';
1030                 }
1031
1032                 return [
1033                         'img_hover' => L10n::t('Visit %s\'s profile [%s]', $rr['name'], $rr['url']),
1034                         'edit_hover'=> L10n::t('Edit contact'),
1035                         'photo_menu'=> Model\Contact::photoMenu($rr),
1036                         'id'        => $rr['id'],
1037                         'alt_text'  => $alt_text,
1038                         'dir_icon'  => $dir_icon,
1039                         'thumb'     => ProxyUtils::proxifyUrl($rr['thumb'], false, ProxyUtils::SIZE_THUMB),
1040                         'name'      => $rr['name'],
1041                         'username'  => $rr['name'],
1042                         'account_type' => Model\Contact::getAccountType($rr),
1043                         'sparkle'   => $sparkle,
1044                         'itemurl'   => ($rr['addr'] ?? '') ?: $rr['url'],
1045                         'url'       => $url,
1046                         'network'   => ContactSelector::networkToName($rr['network'], $rr['url']),
1047                         'nick'      => $rr['nick'],
1048                 ];
1049         }
1050
1051         /**
1052          * @brief Gives a array with actions which can performed to a given contact
1053          *
1054          * This includes actions like e.g. 'block', 'hide', 'archive', 'delete' and others
1055          *
1056          * @param array $contact Data about the Contact
1057          * @return array with contact related actions
1058          */
1059         private static function getContactActions($contact)
1060         {
1061                 $poll_enabled = in_array($contact['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::OSTATUS, Protocol::FEED, Protocol::MAIL]);
1062                 $contact_actions = [];
1063
1064                 // Provide friend suggestion only for Friendica contacts
1065                 if ($contact['network'] === Protocol::DFRN) {
1066                         $contact_actions['suggest'] = [
1067                                 'label' => L10n::t('Suggest friends'),
1068                                 'url'   => 'fsuggest/' . $contact['id'],
1069                                 'title' => '',
1070                                 'sel'   => '',
1071                                 'id'    => 'suggest',
1072                         ];
1073                 }
1074
1075                 if ($poll_enabled) {
1076                         $contact_actions['update'] = [
1077                                 'label' => L10n::t('Update now'),
1078                                 'url'   => 'contact/' . $contact['id'] . '/update',
1079                                 'title' => '',
1080                                 'sel'   => '',
1081                                 'id'    => 'update',
1082                         ];
1083                 }
1084
1085                 $contact_actions['block'] = [
1086                         'label' => (intval($contact['blocked']) ? L10n::t('Unblock') : L10n::t('Block')),
1087                         'url'   => 'contact/' . $contact['id'] . '/block',
1088                         'title' => L10n::t('Toggle Blocked status'),
1089                         'sel'   => (intval($contact['blocked']) ? 'active' : ''),
1090                         'id'    => 'toggle-block',
1091                 ];
1092
1093                 $contact_actions['ignore'] = [
1094                         'label' => (intval($contact['readonly']) ? L10n::t('Unignore') : L10n::t('Ignore')),
1095                         'url'   => 'contact/' . $contact['id'] . '/ignore',
1096                         'title' => L10n::t('Toggle Ignored status'),
1097                         'sel'   => (intval($contact['readonly']) ? 'active' : ''),
1098                         'id'    => 'toggle-ignore',
1099                 ];
1100
1101                 if ($contact['uid'] != 0) {
1102                         $contact_actions['archive'] = [
1103                                 'label' => (intval($contact['archive']) ? L10n::t('Unarchive') : L10n::t('Archive')),
1104                                 'url'   => 'contact/' . $contact['id'] . '/archive',
1105                                 'title' => L10n::t('Toggle Archive status'),
1106                                 'sel'   => (intval($contact['archive']) ? 'active' : ''),
1107                                 'id'    => 'toggle-archive',
1108                         ];
1109
1110                         $contact_actions['delete'] = [
1111                                 'label' => L10n::t('Delete'),
1112                                 'url'   => 'contact/' . $contact['id'] . '/drop',
1113                                 'title' => L10n::t('Delete contact'),
1114                                 'sel'   => '',
1115                                 'id'    => 'delete',
1116                         ];
1117                 }
1118
1119                 return $contact_actions;
1120         }
1121 }