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