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