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