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