]> git.mxchange.org Git - friendica.git/blob - src/Module/Contact.php
Remove duplicate $baseurl template variable
[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                         $cmd = $a->argv[2];
371
372                         $orig_record = DBA::selectFirst('contact', [], ['id' => $contact_id, 'uid' => [0, local_user()], 'self' => false, 'deleted' => false]);
373                         if (!DBA::isResult($orig_record)) {
374                                 notice(L10n::t('Could not access contact record.') . EOL);
375                                 $a->internalRedirect('contact');
376                                 return; // NOTREACHED
377                         }
378
379                         if ($cmd === 'update' && ($orig_record['uid'] != 0)) {
380                                 self::updateContactFromPoll($contact_id);
381                                 $a->internalRedirect('contact/' . $contact_id);
382                                 // NOTREACHED
383                         }
384
385                         if ($cmd === 'updateprofile' && ($orig_record['uid'] != 0)) {
386                                 self::updateContactFromProbe($contact_id);
387                                 $a->internalRedirect('crepair/' . $contact_id);
388                                 // NOTREACHED
389                         }
390
391                         if ($cmd === 'block') {
392                                 self::blockContact($contact_id);
393
394                                 $blocked = Model\Contact::isBlockedByUser($contact_id, local_user());
395                                 info(($blocked ? L10n::t('Contact has been blocked') : L10n::t('Contact has been unblocked')) . EOL);
396
397                                 $a->internalRedirect('contact/' . $contact_id);
398                                 return; // NOTREACHED
399                         }
400
401                         if ($cmd === 'ignore') {
402                                 self::ignoreContact($contact_id);
403
404                                 $ignored = Model\Contact::isIgnoredByUser($contact_id, local_user());
405                                 info(($ignored ? L10n::t('Contact has been ignored') : L10n::t('Contact has been unignored')) . EOL);
406
407                                 $a->internalRedirect('contact/' . $contact_id);
408                                 return; // NOTREACHED
409                         }
410
411                         if ($cmd === 'archive' && ($orig_record['uid'] != 0)) {
412                                 $r = self::archiveContact($contact_id, $orig_record);
413                                 if ($r) {
414                                         $archived = (($orig_record['archive']) ? 0 : 1);
415                                         info((($archived) ? L10n::t('Contact has been archived') : L10n::t('Contact has been unarchived')) . EOL);
416                                 }
417
418                                 $a->internalRedirect('contact/' . $contact_id);
419                                 return; // NOTREACHED
420                         }
421
422                         if ($cmd === 'drop' && ($orig_record['uid'] != 0)) {
423                                 // Check if we should do HTML-based delete confirmation
424                                 if (!empty($_REQUEST['confirm'])) {
425                                         // <form> can't take arguments in its 'action' parameter
426                                         // so add any arguments as hidden inputs
427                                         $query = explode_querystring($a->query_string);
428                                         $inputs = [];
429                                         foreach ($query['args'] as $arg) {
430                                                 if (strpos($arg, 'confirm=') === false) {
431                                                         $arg_parts = explode('=', $arg);
432                                                         $inputs[] = ['name' => $arg_parts[0], 'value' => $arg_parts[1]];
433                                                 }
434                                         }
435
436                                         $a->page['aside'] = '';
437
438                                         return Renderer::replaceMacros(Renderer::getMarkupTemplate('contact_drop_confirm.tpl'), [
439                                                 '$header' => L10n::t('Drop contact'),
440                                                 '$contact' => self::getContactTemplateVars($orig_record),
441                                                 '$method' => 'get',
442                                                 '$message' => L10n::t('Do you really want to delete this contact?'),
443                                                 '$extra_inputs' => $inputs,
444                                                 '$confirm' => L10n::t('Yes'),
445                                                 '$confirm_url' => $query['base'],
446                                                 '$confirm_name' => 'confirmed',
447                                                 '$cancel' => L10n::t('Cancel'),
448                                         ]);
449                                 }
450                                 // Now check how the user responded to the confirmation query
451                                 if (!empty($_REQUEST['canceled'])) {
452                                         $a->internalRedirect('contact');
453                                 }
454
455                                 self::dropContact($orig_record);
456                                 info(L10n::t('Contact has been removed.') . EOL);
457
458                                 $a->internalRedirect('contact');
459                                 return; // NOTREACHED
460                         }
461                         if ($cmd === 'posts') {
462                                 return self::getPostsHTML($a, $contact_id);
463                         }
464                         if ($cmd === 'conversations') {
465                                 return self::getConversationsHMTL($a, $contact_id, $update);
466                         }
467                 }
468
469                 $_SESSION['return_path'] = $a->query_string;
470
471                 if (!empty($a->data['contact']) && is_array($a->data['contact'])) {
472                         $contact = $a->data['contact'];
473
474                         $a->page['htmlhead'] .= Renderer::replaceMacros(Renderer::getMarkupTemplate('contact_head.tpl'), [
475                                 '$baseurl' => $a->getBaseURL(true),
476                         ]);
477
478                         $contact['blocked']  = Model\Contact::isBlockedByUser($contact['id'], local_user());
479                         $contact['readonly'] = Model\Contact::isIgnoredByUser($contact['id'], local_user());
480
481                         $dir_icon = '';
482                         $relation_text = '';
483                         switch ($contact['rel']) {
484                                 case Model\Contact::FRIEND:
485                                         $dir_icon = 'images/lrarrow.gif';
486                                         $relation_text = L10n::t('You are mutual friends with %s');
487                                         break;
488
489                                 case Model\Contact::FOLLOWER;
490                                         $dir_icon = 'images/larrow.gif';
491                                         $relation_text = L10n::t('You are sharing with %s');
492                                         break;
493
494                                 case Model\Contact::SHARING;
495                                         $dir_icon = 'images/rarrow.gif';
496                                         $relation_text = L10n::t('%s is sharing with you');
497                                         break;
498
499                                 default:
500                                         break;
501                         }
502
503                         if ($contact['uid'] == 0) {
504                                 $relation_text = '';
505                         }
506
507                         if (!in_array($contact['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::OSTATUS, Protocol::DIASPORA])) {
508                                 $relation_text = '';
509                         }
510
511                         $relation_text = sprintf($relation_text, $contact['name']);
512
513                         $url = Model\Contact::magicLink($contact['url']);
514                         if (strpos($url, 'redir/') === 0) {
515                                 $sparkle = ' class="sparkle" ';
516                         } else {
517                                 $sparkle = '';
518                         }
519
520                         $insecure = L10n::t('Private communications are not available for this contact.');
521
522                         $last_update = (($contact['last-update'] <= DBA::NULL_DATETIME) ? L10n::t('Never') : DateTimeFormat::local($contact['last-update'], 'D, j M Y, g:i A'));
523
524                         if ($contact['last-update'] > DBA::NULL_DATETIME) {
525                                 $last_update .= ' ' . (($contact['last-update'] <= $contact['success_update']) ? L10n::t('(Update was successful)') : L10n::t('(Update was not successful)'));
526                         }
527                         $lblsuggest = (($contact['network'] === Protocol::DFRN) ? L10n::t('Suggest friends') : '');
528
529                         $poll_enabled = in_array($contact['network'], [Protocol::DFRN, Protocol::OSTATUS, Protocol::FEED, Protocol::MAIL]);
530
531                         $nettype = L10n::t('Network type: %s', ContactSelector::networkToName($contact['network'], $contact['url']));
532
533                         // tabs
534                         $tab_str = self::getTabsHTML($a, $contact, 3);
535
536                         $lost_contact = (($contact['archive'] && $contact['term-date'] > DBA::NULL_DATETIME && $contact['term-date'] < DateTimeFormat::utcNow()) ? L10n::t('Communications lost with this contact!') : '');
537
538                         $fetch_further_information = null;
539                         if ($contact['network'] == Protocol::FEED) {
540                                 $fetch_further_information = [
541                                         'fetch_further_information',
542                                         L10n::t('Fetch further information for feeds'),
543                                         $contact['fetch_further_information'],
544                                         L10n::t('Fetch information like preview pictures, title and teaser from the feed item. You can activate this if the feed doesn\'t contain much text. Keywords are taken from the meta header in the feed item and are posted as hash tags.'),
545                                         [
546                                                 '0' => L10n::t('Disabled'),
547                                                 '1' => L10n::t('Fetch information'),
548                                                 '3' => L10n::t('Fetch keywords'),
549                                                 '2' => L10n::t('Fetch information and keywords')
550                                         ]
551                                 ];
552                         }
553
554                         $poll_interval = null;
555                         if (in_array($contact['network'], [Protocol::FEED, Protocol::MAIL])) {
556                                 $poll_interval = ContactSelector::pollInterval($contact['priority'], !$poll_enabled);
557                         }
558
559                         $profile_select = null;
560                         if ($contact['network'] == Protocol::DFRN) {
561                                 $profile_select = ContactSelector::profileAssign($contact['profile-id'], $contact['network'] !== Protocol::DFRN);
562                         }
563
564                         /// @todo Only show the following link with DFRN when the remote version supports it
565                         $follow = '';
566                         $follow_text = '';
567                         if ($contact['uid'] && in_array($contact['rel'], [Model\Contact::FRIEND, Model\Contact::SHARING])) {
568                                 if (in_array($contact['network'], Protocol::NATIVE_SUPPORT)) {
569                                         $follow = $a->getBaseURL(true) . '/unfollow?url=' . urlencode($contact['url']);
570                                         $follow_text = L10n::t('Disconnect/Unfollow');
571                                 }
572                         } elseif(!$contact['pending']) {
573                                 $follow = $a->getBaseURL(true) . '/follow?url=' . urlencode($contact['url']);
574                                 $follow_text = L10n::t('Connect/Follow');
575                         }
576
577                         // Load contactact related actions like hide, suggest, delete and others
578                         $contact_actions = self::getContactActions($contact);
579
580                         if ($contact['uid'] != 0) {
581                                 $lbl_vis1 = L10n::t('Profile Visibility');
582                                 $lbl_info1 = L10n::t('Contact Information / Notes');
583                                 $contact_settings_label = L10n::t('Contact Settings');
584                         } else {
585                                 $lbl_vis1 = null;
586                                 $lbl_info1 = null;
587                                 $contact_settings_label = null;
588                         }
589
590                         $tpl = Renderer::getMarkupTemplate('contact_edit.tpl');
591                         $o .= Renderer::replaceMacros($tpl, [
592                                 '$header'         => L10n::t('Contact'),
593                                 '$tab_str'        => $tab_str,
594                                 '$submit'         => L10n::t('Submit'),
595                                 '$lbl_vis1'       => $lbl_vis1,
596                                 '$lbl_vis2'       => L10n::t('Please choose the profile you would like to display to %s when viewing your profile securely.', $contact['name']),
597                                 '$lbl_info1'      => $lbl_info1,
598                                 '$lbl_info2'      => L10n::t('Their personal note'),
599                                 '$reason'         => trim(Strings::escapeTags($contact['reason'])),
600                                 '$infedit'        => L10n::t('Edit contact notes'),
601                                 '$common_link'    => 'common/loc/' . local_user() . '/' . $contact['id'],
602                                 '$relation_text'  => $relation_text,
603                                 '$visit'          => L10n::t('Visit %s\'s profile [%s]', $contact['name'], $contact['url']),
604                                 '$blockunblock'   => L10n::t('Block/Unblock contact'),
605                                 '$ignorecont'     => L10n::t('Ignore contact'),
606                                 '$lblcrepair'     => L10n::t('Repair URL settings'),
607                                 '$lblrecent'      => L10n::t('View conversations'),
608                                 '$lblsuggest'     => $lblsuggest,
609                                 '$nettype'        => $nettype,
610                                 '$poll_interval'  => $poll_interval,
611                                 '$poll_enabled'   => $poll_enabled,
612                                 '$lastupdtext'    => L10n::t('Last update:'),
613                                 '$lost_contact'   => $lost_contact,
614                                 '$updpub'         => L10n::t('Update public posts'),
615                                 '$last_update'    => $last_update,
616                                 '$udnow'          => L10n::t('Update now'),
617                                 '$follow'         => $follow,
618                                 '$follow_text'    => $follow_text,
619                                 '$profile_select' => $profile_select,
620                                 '$contact_id'     => $contact['id'],
621                                 '$block_text'     => ($contact['blocked'] ? L10n::t('Unblock') : L10n::t('Block')),
622                                 '$ignore_text'    => ($contact['readonly'] ? L10n::t('Unignore') : L10n::t('Ignore')),
623                                 '$insecure'       => (in_array($contact['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::MAIL, Protocol::DIASPORA]) ? '' : $insecure),
624                                 '$info'           => $contact['info'],
625                                 '$cinfo'          => ['info', '', $contact['info'], ''],
626                                 '$blocked'        => ($contact['blocked'] ? L10n::t('Currently blocked') : ''),
627                                 '$ignored'        => ($contact['readonly'] ? L10n::t('Currently ignored') : ''),
628                                 '$archived'       => ($contact['archive'] ? L10n::t('Currently archived') : ''),
629                                 '$pending'        => ($contact['pending'] ? L10n::t('Awaiting connection acknowledge') : ''),
630                                 '$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')],
631                                 '$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')],
632                                 '$fetch_further_information' => $fetch_further_information,
633                                 '$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')],
634                                 '$photo'          => $contact['photo'],
635                                 '$name'           => $contact['name'],
636                                 '$dir_icon'       => $dir_icon,
637                                 '$sparkle'        => $sparkle,
638                                 '$url'            => $url,
639                                 '$profileurllabel'=> L10n::t('Profile URL'),
640                                 '$profileurl'     => $contact['url'],
641                                 '$account_type'   => Model\Contact::getAccountType($contact),
642                                 '$location'       => BBCode::convert($contact['location']),
643                                 '$location_label' => L10n::t('Location:'),
644                                 '$xmpp'           => BBCode::convert($contact['xmpp']),
645                                 '$xmpp_label'     => L10n::t('XMPP:'),
646                                 '$about'          => BBCode::convert($contact['about'], false),
647                                 '$about_label'    => L10n::t('About:'),
648                                 '$keywords'       => $contact['keywords'],
649                                 '$keywords_label' => L10n::t('Tags:'),
650                                 '$contact_action_button' => L10n::t('Actions'),
651                                 '$contact_actions'=> $contact_actions,
652                                 '$contact_status' => L10n::t('Status'),
653                                 '$contact_settings_label' => $contact_settings_label,
654                                 '$contact_profile_label' => L10n::t('Profile'),
655                         ]);
656
657                         $arr = ['contact' => $contact, 'output' => $o];
658
659                         Hook::callAll('contact_edit', $arr);
660
661                         return $arr['output'];
662                 }
663
664                 $blocked = false;
665                 $hidden = false;
666                 $ignored = false;
667                 $archived = false;
668                 $all = false;
669
670                 if (($a->argc == 2) && ($a->argv[1] === 'all')) {
671                         $sql_extra = '';
672                         $all = true;
673                 } elseif (($a->argc == 2) && ($a->argv[1] === 'blocked')) {
674                         $sql_extra = " AND `blocked` = 1 ";
675                         $blocked = true;
676                 } elseif (($a->argc == 2) && ($a->argv[1] === 'hidden')) {
677                         $sql_extra = " AND `hidden` = 1 ";
678                         $hidden = true;
679                 } elseif (($a->argc == 2) && ($a->argv[1] === 'ignored')) {
680                         $sql_extra = " AND `readonly` = 1 ";
681                         $ignored = true;
682                 } elseif (($a->argc == 2) && ($a->argv[1] === 'archived')) {
683                         $sql_extra = " AND `archive` = 1 ";
684                         $archived = true;
685                 } else {
686                         $sql_extra = " AND `blocked` = 0 ";
687                 }
688
689                 $sql_extra .= sprintf(" AND `network` != '%s' ", Protocol::PHANTOM);
690
691                 $search = Strings::escapeTags(trim(defaults($_GET, 'search', '')));
692                 $nets   = Strings::escapeTags(trim(defaults($_GET, 'nets'  , '')));
693
694                 $tabs = [
695                         [
696                                 'label' => L10n::t('Suggestions'),
697                                 'url'   => 'suggest',
698                                 'sel'   => '',
699                                 'title' => L10n::t('Suggest potential friends'),
700                                 'id'    => 'suggestions-tab',
701                                 'accesskey' => 'g',
702                         ],
703                         [
704                                 'label' => L10n::t('All Contacts'),
705                                 'url'   => 'contact/all',
706                                 'sel'   => ($all) ? 'active' : '',
707                                 'title' => L10n::t('Show all contacts'),
708                                 'id'    => 'showall-tab',
709                                 'accesskey' => 'l',
710                         ],
711                         [
712                                 'label' => L10n::t('Unblocked'),
713                                 'url'   => 'contact',
714                                 'sel'   => ((!$all) && (!$blocked) && (!$hidden) && (!$search) && (!$nets) && (!$ignored) && (!$archived)) ? 'active' : '',
715                                 'title' => L10n::t('Only show unblocked contacts'),
716                                 'id'    => 'showunblocked-tab',
717                                 'accesskey' => 'o',
718                         ],
719                         [
720                                 'label' => L10n::t('Blocked'),
721                                 'url'   => 'contact/blocked',
722                                 'sel'   => ($blocked) ? 'active' : '',
723                                 'title' => L10n::t('Only show blocked contacts'),
724                                 'id'    => 'showblocked-tab',
725                                 'accesskey' => 'b',
726                         ],
727                         [
728                                 'label' => L10n::t('Ignored'),
729                                 'url'   => 'contact/ignored',
730                                 'sel'   => ($ignored) ? 'active' : '',
731                                 'title' => L10n::t('Only show ignored contacts'),
732                                 'id'    => 'showignored-tab',
733                                 'accesskey' => 'i',
734                         ],
735                         [
736                                 'label' => L10n::t('Archived'),
737                                 'url'   => 'contact/archived',
738                                 'sel'   => ($archived) ? 'active' : '',
739                                 'title' => L10n::t('Only show archived contacts'),
740                                 'id'    => 'showarchived-tab',
741                                 'accesskey' => 'y',
742                         ],
743                         [
744                                 'label' => L10n::t('Hidden'),
745                                 'url'   => 'contact/hidden',
746                                 'sel'   => ($hidden) ? 'active' : '',
747                                 'title' => L10n::t('Only show hidden contacts'),
748                                 'id'    => 'showhidden-tab',
749                                 'accesskey' => 'h',
750                         ],
751                         [
752                                 'label' => L10n::t('Groups'),
753                                 'url'   => 'group',
754                                 'sel'   => ($hidden) ? 'active' : '',
755                                 'title' => L10n::t('Organize your contact groups'),
756                                 'id'    => 'contactgroups-tab',
757                                 'accesskey' => 'e',
758                         ],
759                 ];
760
761                 $tab_tpl = Renderer::getMarkupTemplate('common_tabs.tpl');
762                 $t = Renderer::replaceMacros($tab_tpl, ['$tabs' => $tabs]);
763
764                 $total = 0;
765                 $searching = false;
766                 $search_hdr = null;
767                 if ($search) {
768                         $searching = true;
769                         $search_hdr = $search;
770                         $search_txt = DBA::escape(Strings::protectSprintf(preg_quote($search)));
771                         $sql_extra .= " AND (name REGEXP '$search_txt' OR url REGEXP '$search_txt'  OR nick REGEXP '$search_txt') ";
772                 }
773
774                 if ($nets) {
775                         $sql_extra .= sprintf(" AND network = '%s' ", DBA::escape($nets));
776                 }
777
778                 $sql_extra .=  " AND NOT `deleted` ";
779
780                 $sql_extra2 = ((($sort_type > 0) && ($sort_type <= Model\Contact::FRIEND)) ? sprintf(" AND `rel` = %d ", intval($sort_type)) : '');
781
782                 $r = q("SELECT COUNT(*) AS `total` FROM `contact`
783                         WHERE `uid` = %d AND `self` = 0 AND `pending` = 0 $sql_extra $sql_extra2 ",
784                         intval($_SESSION['uid'])
785                 );
786                 if (DBA::isResult($r)) {
787                         $total = $r[0]['total'];
788                 }
789                 $pager = new Pager($a->query_string);
790
791                 $sql_extra3 = Widget::unavailableNetworks();
792
793                 $contacts = [];
794
795                 $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 ",
796                         intval($_SESSION['uid']),
797                         $pager->getStart(),
798                         $pager->getItemsPerPage()
799                 );
800                 if (DBA::isResult($r)) {
801                         foreach ($r as $rr) {
802                                 $rr['blocked'] = Model\Contact::isBlockedByUser($rr['id'], local_user());
803                                 $rr['readonly'] = Model\Contact::isIgnoredByUser($rr['id'], local_user());
804                                 $contacts[] = self::getContactTemplateVars($rr);
805                         }
806                 }
807
808                 $tpl = Renderer::getMarkupTemplate('contacts-template.tpl');
809                 $o .= Renderer::replaceMacros($tpl, [
810                         '$header'     => L10n::t('Contacts') . (($nets) ? ' - ' . ContactSelector::networkToName($nets) : ''),
811                         '$tabs'       => $t,
812                         '$total'      => $total,
813                         '$search'     => $search_hdr,
814                         '$desc'       => L10n::t('Search your contacts'),
815                         '$finding'    => $searching ? L10n::t('Results for: %s', $search) : '',
816                         '$submit'     => L10n::t('Find'),
817                         '$cmd'        => $a->cmd,
818                         '$contacts'   => $contacts,
819                         '$contact_drop_confirm' => L10n::t('Do you really want to delete this contact?'),
820                         'multiselect' => 1,
821                         '$batch_actions' => [
822                                 'contacts_batch_update'  => L10n::t('Update'),
823                                 'contacts_batch_block'   => L10n::t('Block') . '/' . L10n::t('Unblock'),
824                                 'contacts_batch_ignore'  => L10n::t('Ignore') . '/' . L10n::t('Unignore'),
825                                 'contacts_batch_archive' => L10n::t('Archive') . '/' . L10n::t('Unarchive'),
826                                 'contacts_batch_drop'    => L10n::t('Delete'),
827                         ],
828                         '$h_batch_actions' => L10n::t('Batch Actions'),
829                         '$paginate'   => $pager->renderFull($total),
830                 ]);
831
832                 return $o;
833         }
834
835         /**
836          * @brief List of pages for the Contact TabBar
837          *
838          * Available Pages are 'Status', 'Profile', 'Contacts' and 'Common Friends'
839          *
840          * @param App   $a
841          * @param array $contact    The contact array
842          * @param int   $active_tab 1 if tab should be marked as active
843          *
844          * @return string HTML string of the contact page tabs buttons.
845          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
846          */
847         public static function getTabsHTML($a, $contact, $active_tab)
848         {
849                 // tabs
850                 $tabs = [
851                         [
852                                 'label' => L10n::t('Status'),
853                                 'url'   => "contact/" . $contact['id'] . "/conversations",
854                                 'sel'   => (($active_tab == 1) ? 'active' : ''),
855                                 'title' => L10n::t('Conversations started by this contact'),
856                                 'id'    => 'status-tab',
857                                 'accesskey' => 'm',
858                         ],
859                         [
860                                 'label' => L10n::t('Posts and Comments'),
861                                 'url'   => "contact/" . $contact['id'] . "/posts",
862                                 'sel'   => (($active_tab == 2) ? 'active' : ''),
863                                 'title' => L10n::t('Status Messages and Posts'),
864                                 'id'    => 'posts-tab',
865                                 'accesskey' => 'p',
866                         ],
867                         [
868                                 'label' => L10n::t('Profile'),
869                                 'url'   => "contact/" . $contact['id'],
870                                 'sel'   => (($active_tab == 3) ? 'active' : ''),
871                                 'title' => L10n::t('Profile Details'),
872                                 'id'    => 'profile-tab',
873                                 'accesskey' => 'o',
874                         ]
875                 ];
876
877                 // Show this tab only if there is visible friend list
878                 $x = Model\GContact::countAllFriends(local_user(), $contact['id']);
879                 if ($x) {
880                         $tabs[] = ['label' => L10n::t('Contacts'),
881                                 'url'   => "allfriends/" . $contact['id'],
882                                 'sel'   => (($active_tab == 4) ? 'active' : ''),
883                                 'title' => L10n::t('View all contacts'),
884                                 'id'    => 'allfriends-tab',
885                                 'accesskey' => 't'];
886                 }
887
888                 // Show this tab only if there is visible common friend list
889                 $common = Model\GContact::countCommonFriends(local_user(), $contact['id']);
890                 if ($common) {
891                         $tabs[] = ['label' => L10n::t('Common Friends'),
892                                 'url'   => "common/loc/" . local_user() . "/" . $contact['id'],
893                                 'sel'   => (($active_tab == 5) ? 'active' : ''),
894                                 'title' => L10n::t('View all common friends'),
895                                 'id'    => 'common-loc-tab',
896                                 'accesskey' => 'd'
897                         ];
898                 }
899
900                 if (!empty($contact['uid'])) {
901                         $tabs[] = ['label' => L10n::t('Advanced'),
902                                 'url'   => 'crepair/' . $contact['id'],
903                                 'sel'   => (($active_tab == 6) ? 'active' : ''),
904                                 'title' => L10n::t('Advanced Contact Settings'),
905                                 'id'    => 'advanced-tab',
906                                 'accesskey' => 'r'
907                         ];
908                 }
909
910                 $tab_tpl = Renderer::getMarkupTemplate('common_tabs.tpl');
911                 $tab_str = Renderer::replaceMacros($tab_tpl, ['$tabs' => $tabs]);
912
913                 return $tab_str;
914         }
915
916         private static function getConversationsHMTL($a, $contact_id, $update)
917         {
918                 $o = '';
919
920                 if (!$update) {
921                         // We need the editor here to be able to reshare an item.
922                         if (local_user()) {
923                                 $x = [
924                                         'is_owner' => true,
925                                         'allow_location' => $a->user['allow_location'],
926                                         'default_location' => $a->user['default-location'],
927                                         'nickname' => $a->user['nickname'],
928                                         'lockstate' => (is_array($a->user) && (strlen($a->user['allow_cid']) || strlen($a->user['allow_gid']) || strlen($a->user['deny_cid']) || strlen($a->user['deny_gid'])) ? 'lock' : 'unlock'),
929                                         'acl' => ACL::getFullSelectorHTML($a->user, true),
930                                         'bang' => '',
931                                         'visitor' => 'block',
932                                         'profile_uid' => local_user(),
933                                 ];
934                                 $o = status_editor($a, $x, 0, true);
935                         }
936                 }
937
938                 $contact = DBA::selectFirst('contact', ['uid', 'url', 'id'], ['id' => $contact_id, 'deleted' => false]);
939
940                 if (!$update) {
941                         $o .= self::getTabsHTML($a, $contact, 1);
942                 }
943
944                 if (DBA::isResult($contact)) {
945                         $a->page['aside'] = '';
946
947                         $profiledata = Model\Contact::getDetailsByURL($contact['url']);
948
949                         if (local_user() && in_array($profiledata['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS])) {
950                                 $profiledata['remoteconnect'] = System::baseUrl() . '/follow?url=' . urlencode($profiledata['url']);
951                         }
952
953                         Model\Profile::load($a, '', 0, $profiledata, true);
954                         $o .= Model\Contact::getPostsFromUrl($contact['url'], true, $update);
955                 }
956
957                 return $o;
958         }
959
960         private static function getPostsHTML($a, $contact_id)
961         {
962                 $contact = DBA::selectFirst('contact', ['uid', 'url', 'id'], ['id' => $contact_id, 'deleted' => false]);
963
964                 $o = self::getTabsHTML($a, $contact, 2);
965
966                 if (DBA::isResult($contact)) {
967                         $a->page['aside'] = '';
968
969                         $profiledata = Model\Contact::getDetailsByURL($contact['url']);
970
971                         if (local_user() && in_array($profiledata['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS])) {
972                                 $profiledata['remoteconnect'] = System::baseUrl() . '/follow?url=' . urlencode($profiledata['url']);
973                         }
974
975                         Model\Profile::load($a, '', 0, $profiledata, true);
976                         $o .= Model\Contact::getPostsFromUrl($contact['url']);
977                 }
978
979                 return $o;
980         }
981
982         public static function getContactTemplateVars(array $rr)
983         {
984                 $dir_icon = '';
985                 $alt_text = '';
986
987                 switch ($rr['rel']) {
988                         case Model\Contact::FRIEND:
989                                 $dir_icon = 'images/lrarrow.gif';
990                                 $alt_text = L10n::t('Mutual Friendship');
991                                 break;
992
993                         case Model\Contact::FOLLOWER;
994                                 $dir_icon = 'images/larrow.gif';
995                                 $alt_text = L10n::t('is a fan of yours');
996                                 break;
997
998                         case Model\Contact::SHARING;
999                                 $dir_icon = 'images/rarrow.gif';
1000                                 $alt_text = L10n::t('you are a fan of');
1001                                 break;
1002
1003                         default:
1004                                 break;
1005                 }
1006
1007                 $url = Model\Contact::magicLink($rr['url']);
1008
1009                 if (strpos($url, 'redir/') === 0) {
1010                         $sparkle = ' class="sparkle" ';
1011                 } else {
1012                         $sparkle = '';
1013                 }
1014
1015                 if ($rr['self']) {
1016                         $dir_icon = 'images/larrow.gif';
1017                         $alt_text = L10n::t('This is you');
1018                         $url = $rr['url'];
1019                         $sparkle = '';
1020                 }
1021
1022                 return [
1023                         'img_hover' => L10n::t('Visit %s\'s profile [%s]', $rr['name'], $rr['url']),
1024                         'edit_hover'=> L10n::t('Edit contact'),
1025                         'photo_menu'=> Model\Contact::photoMenu($rr),
1026                         'id'        => $rr['id'],
1027                         'alt_text'  => $alt_text,
1028                         'dir_icon'  => $dir_icon,
1029                         'thumb'     => ProxyUtils::proxifyUrl($rr['thumb'], false, ProxyUtils::SIZE_THUMB),
1030                         'name'      => $rr['name'],
1031                         'username'  => $rr['name'],
1032                         'account_type' => Model\Contact::getAccountType($rr),
1033                         'sparkle'   => $sparkle,
1034                         'itemurl'   => defaults($rr, 'addr', $rr['url']),
1035                         'url'       => $url,
1036                         'network'   => ContactSelector::networkToName($rr['network'], $rr['url']),
1037                         'nick'      => $rr['nick'],
1038                 ];
1039         }
1040
1041         /**
1042          * @brief Gives a array with actions which can performed to a given contact
1043          *
1044          * This includes actions like e.g. 'block', 'hide', 'archive', 'delete' and others
1045          *
1046          * @param array $contact Data about the Contact
1047          * @return array with contact related actions
1048          */
1049         private static function getContactActions($contact)
1050         {
1051                 $poll_enabled = in_array($contact['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::OSTATUS, Protocol::FEED, Protocol::MAIL]);
1052                 $contact_actions = [];
1053
1054                 // Provide friend suggestion only for Friendica contacts
1055                 if ($contact['network'] === Protocol::DFRN) {
1056                         $contact_actions['suggest'] = [
1057                                 'label' => L10n::t('Suggest friends'),
1058                                 'url'   => 'fsuggest/' . $contact['id'],
1059                                 'title' => '',
1060                                 'sel'   => '',
1061                                 'id'    => 'suggest',
1062                         ];
1063                 }
1064
1065                 if ($poll_enabled) {
1066                         $contact_actions['update'] = [
1067                                 'label' => L10n::t('Update now'),
1068                                 'url'   => 'contact/' . $contact['id'] . '/update',
1069                                 'title' => '',
1070                                 'sel'   => '',
1071                                 'id'    => 'update',
1072                         ];
1073                 }
1074
1075                 $contact_actions['block'] = [
1076                         'label' => (intval($contact['blocked']) ? L10n::t('Unblock') : L10n::t('Block')),
1077                         'url'   => 'contact/' . $contact['id'] . '/block',
1078                         'title' => L10n::t('Toggle Blocked status'),
1079                         'sel'   => (intval($contact['blocked']) ? 'active' : ''),
1080                         'id'    => 'toggle-block',
1081                 ];
1082
1083                 $contact_actions['ignore'] = [
1084                         'label' => (intval($contact['readonly']) ? L10n::t('Unignore') : L10n::t('Ignore')),
1085                         'url'   => 'contact/' . $contact['id'] . '/ignore',
1086                         'title' => L10n::t('Toggle Ignored status'),
1087                         'sel'   => (intval($contact['readonly']) ? 'active' : ''),
1088                         'id'    => 'toggle-ignore',
1089                 ];
1090
1091                 if ($contact['uid'] != 0) {
1092                         $contact_actions['archive'] = [
1093                                 'label' => (intval($contact['archive']) ? L10n::t('Unarchive') : L10n::t('Archive')),
1094                                 'url'   => 'contact/' . $contact['id'] . '/archive',
1095                                 'title' => L10n::t('Toggle Archive status'),
1096                                 'sel'   => (intval($contact['archive']) ? 'active' : ''),
1097                                 'id'    => 'toggle-archive',
1098                         ];
1099
1100                         $contact_actions['delete'] = [
1101                                 'label' => L10n::t('Delete'),
1102                                 'url'   => 'contact/' . $contact['id'] . '/drop',
1103                                 'title' => L10n::t('Delete contact'),
1104                                 'sel'   => '',
1105                                 'id'    => 'delete',
1106                         ];
1107                 }
1108
1109                 return $contact_actions;
1110         }
1111 }