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