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