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