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