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