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