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