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