]> git.mxchange.org Git - friendica.git/blob - src/Module/Contacts.php
eb21b3b3e17e1ef8bb41d0a2d57b7eba465fb9dd
[friendica.git] / src / Module / Contacts.php
1 <?php
2
3 namespace Friendica\Module;
4
5 use Friendica\App;
6 use Friendica\BaseModule;
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 /**
28  *  Manages and show Contacts and their content
29  *
30  *  @brief manages contacts
31  */
32 class Contact extends BaseModule 
33 {
34         public static function init()
35         {
36                 $a = self::getApp();
37
38                 if (!local_user()) {
39                         return;
40                 }
41
42                 $nets = defaults($_GET, 'nets', '');
43                 if ($nets == "all") {
44                         $nets = "";
45                 }
46
47                 if (!x($a->page, 'aside')) {
48                         $a->page['aside'] = '';
49                 }
50
51                 $contact_id = null;
52                 $contact = null;
53                 if ((($a->argc == 2) && intval($a->argv[1])) || (($a->argc == 3) && intval($a->argv[1]) && in_array($a->argv[2], ['posts', 'conversations']))) {
54                         $contact_id = intval($a->argv[1]);
55                         $contact = DBA::selectFirst('contact', [], ['id' => $contact_id, 'uid' => local_user()]);
56
57                         if (!DBA::isResult($contact)) {
58                                 $contact = DBA::selectFirst('contact', [], ['id' => $contact_id, 'uid' => 0]);
59                         }
60
61                         // Don't display contacts that are about to be deleted
62                         if ($contact['network'] == Protocol::PHANTOM) {
63                                 $contact = false;
64                         }
65                 }
66
67                 if (DBA::isResult($contact)) {
68                         if ($contact['self']) {
69                                 if (($a->argc == 3) && intval($a->argv[1]) && in_array($a->argv[2], ['posts', 'conversations'])) {
70                                         goaway('profile/' . $contact['nick']);
71                                 } else {
72                                         goaway('profile/' . $contact['nick'] . '?tab=profile');
73                                 }
74                         }
75
76                         $a->data['contact'] = $contact;
77
78                         if (($a->data['contact']['network'] != "") && ($a->data['contact']['network'] != Protocol::DFRN)) {
79                                 $networkname = format_network_name($a->data['contact']['network'], $a->data['contact']['url']);
80                         } else {
81                                 $networkname = '';
82                         }
83
84                         /// @TODO Add nice spaces
85                         $vcard_widget = replace_macros(get_markup_template("vcard-widget.tpl"), [
86                                 '$name' => htmlentities($a->data['contact']['name']),
87                                 '$photo' => $a->data['contact']['photo'],
88                                 '$url' => Contact::MagicLink($a->data['contact']['url']),
89                                 '$addr' => (($a->data['contact']['addr'] != "") ? ($a->data['contact']['addr']) : ""),
90                                 '$network_name' => $networkname,
91                                 '$network' => L10n::t('Network:'),
92                                 '$account_type' => Contact::getAccountType($a->data['contact'])
93                         ]);
94
95                         $findpeople_widget = '';
96                         $follow_widget = '';
97                         $networks_widget = '';
98                 } else {
99                         $vcard_widget = '';
100                         $networks_widget = Widget::networks('contacts', $nets);
101                         if (isset($_GET['add'])) {
102                                 $follow_widget = Widget::follow($_GET['add']);
103                         } else {
104                                 $follow_widget = Widget::follow();
105                         }
106
107                         $findpeople_widget = Widget::findPeople();
108                 }
109
110                 if ($contact['uid'] != 0) {
111                         $groups_widget = Group::sidebarWidget('contacts', 'group', 'full', 'everyone', $contact_id);
112                 } else {
113                         $groups_widget = null;
114                 }
115
116                 $a->page['aside'] .= replace_macros(get_markup_template("contacts-widget-sidebar.tpl"), [
117                         '$vcard_widget' => $vcard_widget,
118                         '$findpeople_widget' => $findpeople_widget,
119                         '$follow_widget' => $follow_widget,
120                         '$groups_widget' => $groups_widget,
121                         '$networks_widget' => $networks_widget
122                 ]);
123
124                 $base = System::baseUrl();
125                 $tpl = get_markup_template("contacts-head.tpl");
126                 $a->page['htmlhead'] .= replace_macros($tpl, [
127                         '$baseurl' => System::baseUrl(true),
128                         '$base' => $base
129                 ]);
130         }
131
132         private static function batchActions(App $a)
133         {
134                 if (empty($_POST['contact_batch']) || !is_array($_POST['contact_batch'])) {
135                         return;
136                 }
137
138                 $contacts_id = $_POST['contact_batch'];
139
140                 $orig_records = q("SELECT * FROM `contact` WHERE `id` IN (%s) AND `uid` = %d AND `self` = 0",
141                         implode(",", $contacts_id),
142                         intval(local_user())
143                 );
144
145                 $count_actions = 0;
146                 foreach ($orig_records as $orig_record) {
147                         $contact_id = $orig_record['id'];
148                         if (x($_POST, 'contacts_batch_update')) {
149                                 self::updateContactFromPoll($contact_id);
150                                 $count_actions++;
151                         }
152                         if (x($_POST, 'contacts_batch_block')) {
153                                 self::blockContact($contact_id);
154                                 $count_actions++;
155                         }
156                         if (x($_POST, 'contacts_batch_ignore')) {
157                                 self::ignoreContact($contact_id);
158                                 $count_actions++;
159                         }
160                         if (x($_POST, 'contacts_batch_archive')) {
161                                 $r = self::archiveContact($contact_id, $orig_record);
162                                 if ($r) {
163                                         $count_actions++;
164                                 }
165                         }
166                         if (x($_POST, 'contacts_batch_drop')) {
167                                 self::dropContact($orig_record);
168                                 $count_actions++;
169                         }
170                 }
171                 if ($count_actions > 0) {
172                         info(L10n::tt("%d contact edited.", "%d contacts edited.", $count_actions));
173                 }
174
175                 goaway('contacts');
176         }
177
178         public static function post()
179         {
180                 $a = self::getApp();
181
182                 if (!local_user()) {
183                         return;
184                 }
185
186                 if ($a->argv[1] === "batch") {
187                         self::batchActions($a);
188                         return;
189                 }
190
191                 $contact_id = intval($a->argv[1]);
192                 if (!$contact_id) {
193                         return;
194                 }
195
196                 if (!DBA::exists('contact', ['id' => $contact_id, 'uid' => local_user()])) {
197                         notice(L10n::t('Could not access contact record.') . EOL);
198                         goaway('contacts');
199                         return; // NOTREACHED
200                 }
201
202                 Addon::callHooks('contact_edit_post', $_POST);
203
204                 $profile_id = intval(defaults($_POST, 'profile-assign', 0));
205                 if ($profile_id) {
206                         if (!DBA::exists('profile', ['id' => $profile_id, 'uid' => local_user()])) {
207                                 notice(L10n::t('Could not locate selected profile.') . EOL);
208                                 return;
209                         }
210                 }
211
212                 $hidden = intval($_POST['hidden']);
213
214                 $notify = intval($_POST['notify']);
215
216                 $fetch_further_information = intval(defaults($_POST, 'fetch_further_information', 0));
217
218                 $ffi_keyword_blacklist = escape_tags(trim(defaults($_POST, 'ffi_keyword_blacklist', '')));
219
220                 $priority = intval(defaults($_POST, 'poll', 0));
221                 if ($priority > 5 || $priority < 0) {
222                         $priority = 0;
223                 }
224
225                 $info = escape_tags(trim($_POST['info']));
226
227                 $r = q("UPDATE `contact` SET `profile-id` = %d, `priority` = %d , `info` = '%s',
228                         `hidden` = %d, `notify_new_posts` = %d, `fetch_further_information` = %d,
229                         `ffi_keyword_blacklist` = '%s' WHERE `id` = %d AND `uid` = %d",
230                         intval($profile_id),
231                         intval($priority),
232                         DBA::escape($info),
233                         intval($hidden),
234                         intval($notify),
235                         intval($fetch_further_information),
236                         DBA::escape($ffi_keyword_blacklist),
237                         intval($contact_id),
238                         intval(local_user())
239                 );
240                 if (DBA::isResult($r)) {
241                         info(L10n::t('Contact updated.') . EOL);
242                 } else {
243                         notice(L10n::t('Failed to update contact record.') . EOL);
244                 }
245
246                 $contact = DBA::selectFirst('contact', [], ['id' => $contact_id, 'uid' => local_user()]);
247                 if (DBA::isResult($contact)) {
248                         $a->data['contact'] = $contact;
249                 }
250
251                 return;
252         }
253
254         /* contact actions */
255
256         private static function updateContactFromPoll($contact_id)
257         {
258                 $contact = DBA::selectFirst('contact', ['uid', 'url', 'network'], ['id' => $contact_id, 'uid' => local_user()]);
259                 if (!DBA::isResult($contact)) {
260                         return;
261                 }
262
263                 $uid = $contact["uid"];
264
265                 if ($contact["network"] == Protocol::OSTATUS) {
266                         $result = Contact::createFromProbe($uid, $contact["url"], false, $contact["network"]);
267
268                         if ($result['success']) {
269                                 q("UPDATE `contact` SET `subhub` = 1 WHERE `id` = %d", intval($contact_id));
270                         }
271                 } else {
272                         // pull feed and consume it, which should subscribe to the hub.
273                         Worker::add(PRIORITY_HIGH, "OnePoll", $contact_id, "force");
274                 }
275         }
276
277         private static function updateContactFromProbe($contact_id)
278         {
279                 $contact = DBA::selectFirst('contact', ['uid', 'url', 'network'], ['id' => $contact_id, 'uid' => local_user()]);
280                 if (!DBA::isResult($contact)) {
281                         return;
282                 }
283
284                 $uid = $contact["uid"];
285
286                 $data = Probe::uri($contact["url"], "", 0, false);
287
288                 // "Feed" or "Unknown" is mostly a sign of communication problems
289                 if ((in_array($data["network"], [Protocol::FEED, Protocol::PHANTOM])) && ($data["network"] != $contact["network"])) {
290                         return;
291                 }
292
293                 $updatefields = ["name", "nick", "url", "addr", "batch", "notify", "poll", "request", "confirm",
294                         "poco", "network", "alias"];
295                 $update = [];
296
297                 if ($data["network"] == Protocol::OSTATUS) {
298                         $result = Contact::createFromProbe($uid, $data["url"], false);
299
300                         if ($result['success']) {
301                                 $update["subhub"] = true;
302                         }
303                 }
304
305                 foreach ($updatefields AS $field) {
306                         if (isset($data[$field]) && ($data[$field] != "")) {
307                                 $update[$field] = $data[$field];
308                         }
309                 }
310
311                 $update["nurl"] = normalise_link($data["url"]);
312
313                 $query = "";
314
315                 if (isset($data["priority"]) && ($data["priority"] != 0)) {
316                         $query = "`priority` = " . intval($data["priority"]);
317                 }
318
319                 foreach ($update AS $key => $value) {
320                         if ($query != "") {
321                                 $query .= ", ";
322                         }
323
324                         $query .= "`" . $key . "` = '" . DBA::escape($value) . "'";
325                 }
326
327                 if ($query == "") {
328                         return;
329                 }
330
331                 $r = q("UPDATE `contact` SET $query WHERE `id` = %d AND `uid` = %d",
332                         intval($contact_id),
333                         intval(local_user())
334                 );
335
336                 // Update the entry in the contact table
337                 Contact::updateAvatar($data['photo'], local_user(), $contact_id, true);
338
339                 // Update the entry in the gcontact table
340                 GContact::updateFromProbe($data["url"]);
341         }
342
343         private static function blockContact($contact_id)
344         {
345                 $blocked = !Contact::isBlockedByUser($contact_id, local_user());
346                 Contact::setBlockedForUser($contact_id, local_user(), $blocked);
347         }
348
349         private static function ignoreContact($contact_id)
350         {
351                 $ignored = !Contact::isIgnoredByUser($contact_id, local_user());
352                 Contact::setIgnoredForUser($contact_id, local_user(), $ignored);
353         }
354
355         private static function archiveContact($contact_id, $orig_record)
356         {
357                 $archived = (($orig_record['archive']) ? 0 : 1);
358                 $r = q("UPDATE `contact` SET `archive` = %d WHERE `id` = %d AND `uid` = %d",
359                         intval($archived),
360                         intval($contact_id),
361                         intval(local_user())
362                 );
363                 return DBA::isResult($r);
364         }
365
366         private static function dropContact($orig_record)
367         {
368                 $a = get_app();
369
370                 $r = q("SELECT `contact`.*, `user`.* FROM `contact` INNER JOIN `user` ON `contact`.`uid` = `user`.`uid`
371                         WHERE `user`.`uid` = %d AND `contact`.`self` LIMIT 1",
372                         intval($a->user['uid'])
373                 );
374                 if (!DBA::isResult($r)) {
375                         return;
376                 }
377
378                 Contact::terminateFriendship($r[0], $orig_record, true);
379                 Contact::remove($orig_record['id']);
380         }
381
382         public static function content($update = 0)
383         {
384                 $a = self::getApp();
385                 $sort_type = 0;
386                 $o = '';
387                 Nav::setSelected('contacts');
388
389                 if (!local_user()) {
390                         notice(L10n::t('Permission denied.') . EOL);
391                         return Login::form();
392                 }
393
394                 if ($a->argc == 3) {
395                         $contact_id = intval($a->argv[1]);
396                         if (!$contact_id) {
397                                 return;
398                         }
399
400                         $cmd = $a->argv[2];
401
402                         $orig_record = DBA::selectFirst('contact', [], ['id' => $contact_id, 'uid' => [0, local_user()], 'self' => false]);
403                         if (!DBA::isResult($orig_record)) {
404                                 notice(L10n::t('Could not access contact record.') . EOL);
405                                 goaway('contacts');
406                                 return; // NOTREACHED
407                         }
408
409                         if ($cmd === 'update' && ($orig_record['uid'] != 0)) {
410                                 self::updateContactFromPoll($contact_id);
411                                 goaway('contacts/' . $contact_id);
412                                 // NOTREACHED
413                         }
414
415                         if ($cmd === 'updateprofile' && ($orig_record['uid'] != 0)) {
416                                 self::updateContactFromProbe($contact_id);
417                                 goaway('crepair/' . $contact_id);
418                                 // NOTREACHED
419                         }
420
421                         if ($cmd === 'block') {
422                                 self::blockContact($contact_id);
423
424                                 $blocked = Contact::isBlockedByUser($contact_id, local_user());
425                                 info(($blocked ? L10n::t('Contact has been blocked') : L10n::t('Contact has been unblocked')) . EOL);
426
427                                 goaway('contacts/' . $contact_id);
428                                 return; // NOTREACHED
429                         }
430
431                         if ($cmd === 'ignore') {
432                                 self::ignoreContact($contact_id);
433
434                                 $ignored = Contact::isIgnoredByUser($contact_id, local_user());
435                                 info(($ignored ? L10n::t('Contact has been ignored') : L10n::t('Contact has been unignored')) . EOL);
436
437                                 goaway('contacts/' . $contact_id);
438                                 return; // NOTREACHED
439                         }
440
441                         if ($cmd === 'archive' && ($orig_record['uid'] != 0)) {
442                                 $r = self::archiveContact($contact_id, $orig_record);
443                                 if ($r) {
444                                         $archived = (($orig_record['archive']) ? 0 : 1);
445                                         info((($archived) ? L10n::t('Contact has been archived') : L10n::t('Contact has been unarchived')) . EOL);
446                                 }
447
448                                 goaway('contacts/' . $contact_id);
449                                 return; // NOTREACHED
450                         }
451
452                         if ($cmd === 'drop' && ($orig_record['uid'] != 0)) {
453                                 // Check if we should do HTML-based delete confirmation
454                                 if (x($_REQUEST, 'confirm')) {
455                                         // <form> can't take arguments in its "action" parameter
456                                         // so add any arguments as hidden inputs
457                                         $query = explode_querystring($a->query_string);
458                                         $inputs = [];
459                                         foreach ($query['args'] as $arg) {
460                                                 if (strpos($arg, 'confirm=') === false) {
461                                                         $arg_parts = explode('=', $arg);
462                                                         $inputs[] = ['name' => $arg_parts[0], 'value' => $arg_parts[1]];
463                                                 }
464                                         }
465
466                                         $a->page['aside'] = '';
467
468                                         return replace_macros(get_markup_template('contact_drop_confirm.tpl'), [
469                                                 '$header' => L10n::t('Drop contact'),
470                                                 '$contact' => self::getContactTemplateVars($orig_record),
471                                                 '$method' => 'get',
472                                                 '$message' => L10n::t('Do you really want to delete this contact?'),
473                                                 '$extra_inputs' => $inputs,
474                                                 '$confirm' => L10n::t('Yes'),
475                                                 '$confirm_url' => $query['base'],
476                                                 '$confirm_name' => 'confirmed',
477                                                 '$cancel' => L10n::t('Cancel'),
478                                         ]);
479                                 }
480                                 // Now check how the user responded to the confirmation query
481                                 if (x($_REQUEST, 'canceled')) {
482                                         goaway('contacts');
483                                 }
484
485                                 self::dropContact($orig_record);
486                                 info(L10n::t('Contact has been removed.') . EOL);
487
488                                 goaway('contacts');
489                                 return; // NOTREACHED
490                         }
491                         if ($cmd === 'posts') {
492                                 return self::getPostsHTML($a, $contact_id);
493                         }
494                         if ($cmd === 'conversations') {
495                                 return self::getConversationsHMTL($a, $contact_id, $update);
496                         }
497                 }
498
499                 $_SESSION['return_url'] = $a->query_string;
500
501                 if ((x($a->data, 'contact')) && (is_array($a->data['contact']))) {
502                         $contact_id = $a->data['contact']['id'];
503                         $contact = $a->data['contact'];
504
505                         $a->page['htmlhead'] .= replace_macros(get_markup_template('contact_head.tpl'), [
506                                 '$baseurl' => System::baseUrl(true),
507                         ]);
508
509                         $contact['blocked'] = Contact::isBlockedByUser($contact['id'], local_user());
510                         $contact['readonly'] = Contact::isIgnoredByUser($contact['id'], local_user());
511
512                         $dir_icon = '';
513                         $relation_text = '';
514                         switch ($contact['rel']) {
515                                 case Contact::FRIEND:
516                                         $dir_icon = 'images/lrarrow.gif';
517                                         $relation_text = L10n::t('You are mutual friends with %s');
518                                         break;
519
520                                 case Contact::FOLLOWER;
521                                         $dir_icon = 'images/larrow.gif';
522                                         $relation_text = L10n::t('You are sharing with %s');
523                                         break;
524
525                                 case Contact::SHARING;
526                                         $dir_icon = 'images/rarrow.gif';
527                                         $relation_text = L10n::t('%s is sharing with you');
528                                         break;
529
530                                 default:
531                                         break;
532                         }
533
534                         if ($contact['uid'] == 0) {
535                                 $relation_text = '';
536                         }
537
538                         if (!in_array($contact['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::OSTATUS, Protocol::DIASPORA])) {
539                                 $relation_text = "";
540                         }
541
542                         $relation_text = sprintf($relation_text, htmlentities($contact['name']));
543
544                         $url = Contact::magicLink($contact['url']);
545                         if (strpos($url, 'redir/') === 0) {
546                                 $sparkle = ' class="sparkle" ';
547                         } else {
548                                 $sparkle = '';
549                         }
550
551                         $insecure = L10n::t('Private communications are not available for this contact.');
552
553                         $last_update = (($contact['last-update'] <= NULL_DATE) ? L10n::t('Never') : DateTimeFormat::local($contact['last-update'], 'D, j M Y, g:i A'));
554
555                         if ($contact['last-update'] > NULL_DATE) {
556                                 $last_update .= ' ' . (($contact['last-update'] <= $contact['success_update']) ? L10n::t("\x28Update was successful\x29") : L10n::t("\x28Update was not successful\x29"));
557                         }
558                         $lblsuggest = (($contact['network'] === Protocol::DFRN) ? L10n::t('Suggest friends') : '');
559
560                         $poll_enabled = in_array($contact['network'], [Protocol::DFRN, Protocol::OSTATUS, Protocol::FEED, Protocol::MAIL]);
561
562                         $nettype = L10n::t('Network type: %s', ContactSelector::networkToName($contact['network'], $contact["url"]));
563
564                         // tabs
565                         $tab_str = self::getTabsHTML($a, $contact, 3);
566
567                         $lost_contact = (($contact['archive'] && $contact['term-date'] > NULL_DATE && $contact['term-date'] < DateTimeFormat::utcNow()) ? L10n::t('Communications lost with this contact!') : '');
568
569                         $fetch_further_information = null;
570                         if ($contact['network'] == Protocol::FEED) {
571                                 $fetch_further_information = [
572                                         'fetch_further_information',
573                                         L10n::t('Fetch further information for feeds'),
574                                         $contact['fetch_further_information'],
575                                         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."),
576                                         ['0' => L10n::t('Disabled'),
577                                                 '1' => L10n::t('Fetch information'),
578                                                 '3' => L10n::t('Fetch keywords'),
579                                                 '2' => L10n::t('Fetch information and keywords')
580                                         ]
581                                 ];
582                         }
583
584                         $poll_interval = null;
585                         if (in_array($contact['network'], [Protocol::FEED, Protocol::MAIL])) {
586                                 $poll_interval = ContactSelector::pollInterval($contact['priority'], (!$poll_enabled));
587                         }
588
589                         $profile_select = null;
590                         if ($contact['network'] == Protocol::DFRN) {
591                                 $profile_select = ContactSelector::profileAssign($contact['profile-id'], (($contact['network'] !== Protocol::DFRN) ? true : false));
592                         }
593
594                         /// @todo Only show the following link with DFRN when the remote version supports it
595                         $follow = '';
596                         $follow_text = '';
597                         if (in_array($contact['rel'], [Contact::FRIEND, Contact::SHARING])) {
598                                 if (in_array($contact['network'], Protocol::NATIVE_SUPPORT)) {
599                                         $follow = System::baseUrl(true) . "/unfollow?url=" . urlencode($contact["url"]);
600                                         $follow_text = L10n::t("Disconnect/Unfollow");
601                                 }
602                         } else {
603                                 $follow = System::baseUrl(true) . "/follow?url=" . urlencode($contact["url"]);
604                                 $follow_text = L10n::t("Connect/Follow");
605                         }
606
607                         // Load contactact related actions like hide, suggest, delete and others
608                         $contact_actions = self::getContactActions($contact);
609
610                         if ($contact['uid'] != 0) {
611                                 $lbl_vis1 = L10n::t('Profile Visibility');
612                                 $lbl_info1 = L10n::t('Contact Information / Notes');
613                                 $contact_settings_label = L10n::t('Contact Settings');
614                         } else {
615                                 $lbl_vis1 = null;
616                                 $lbl_info1 = null;
617                                 $contact_settings_label = null;
618                         }
619
620                         $tpl = get_markup_template("contact_edit.tpl");
621                         $o .= replace_macros($tpl, [
622                                 '$header' => L10n::t("Contact"),
623                                 '$tab_str' => $tab_str,
624                                 '$submit' => L10n::t('Submit'),
625                                 '$lbl_vis1' => $lbl_vis1,
626                                 '$lbl_vis2' => L10n::t('Please choose the profile you would like to display to %s when viewing your profile securely.', $contact['name']),
627                                 '$lbl_info1' => $lbl_info1,
628                                 '$lbl_info2' => L10n::t('Their personal note'),
629                                 '$reason' => trim(notags($contact['reason'])),
630                                 '$infedit' => L10n::t('Edit contact notes'),
631                                 '$common_link' => 'common/loc/' . local_user() . '/' . $contact['id'],
632                                 '$relation_text' => $relation_text,
633                                 '$visit' => L10n::t('Visit %s\'s profile [%s]', $contact['name'], $contact['url']),
634                                 '$blockunblock' => L10n::t('Block/Unblock contact'),
635                                 '$ignorecont' => L10n::t('Ignore contact'),
636                                 '$lblcrepair' => L10n::t("Repair URL settings"),
637                                 '$lblrecent' => L10n::t('View conversations'),
638                                 '$lblsuggest' => $lblsuggest,
639                                 '$nettype' => $nettype,
640                                 '$poll_interval' => $poll_interval,
641                                 '$poll_enabled' => $poll_enabled,
642                                 '$lastupdtext' => L10n::t('Last update:'),
643                                 '$lost_contact' => $lost_contact,
644                                 '$updpub' => L10n::t('Update public posts'),
645                                 '$last_update' => $last_update,
646                                 '$udnow' => L10n::t('Update now'),
647                                 '$follow' => $follow,
648                                 '$follow_text' => $follow_text,
649                                 '$profile_select' => $profile_select,
650                                 '$contact_id' => $contact['id'],
651                                 '$block_text' => ($contact['blocked'] ? L10n::t('Unblock') : L10n::t('Block')),
652                                 '$ignore_text' => ($contact['readonly'] ? L10n::t('Unignore') : L10n::t('Ignore')),
653                                 '$insecure' => (in_array($contact['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::MAIL, Protocol::DIASPORA]) ? '' : $insecure),
654                                 '$info' => $contact['info'],
655                                 '$cinfo' => ['info', '', $contact['info'], ''],
656                                 '$blocked' => ($contact['blocked'] ? L10n::t('Currently blocked') : ''),
657                                 '$ignored' => ($contact['readonly'] ? L10n::t('Currently ignored') : ''),
658                                 '$archived' => ($contact['archive'] ? L10n::t('Currently archived') : ''),
659                                 '$pending' => ($contact['pending'] ? L10n::t('Awaiting connection acknowledge') : ''),
660                                 '$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')],
661                                 '$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')],
662                                 '$fetch_further_information' => $fetch_further_information,
663                                 '$ffi_keyword_blacklist' => $contact['ffi_keyword_blacklist'],
664                                 '$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')],
665                                 '$photo' => $contact['photo'],
666                                 '$name' => htmlentities($contact['name']),
667                                 '$dir_icon' => $dir_icon,
668                                 '$sparkle' => $sparkle,
669                                 '$url' => $url,
670                                 '$profileurllabel' => L10n::t('Profile URL'),
671                                 '$profileurl' => $contact['url'],
672                                 '$account_type' => Contact::getAccountType($contact),
673                                 '$location' => BBCode::convert($contact["location"]),
674                                 '$location_label' => L10n::t("Location:"),
675                                 '$xmpp' => BBCode::convert($contact["xmpp"]),
676                                 '$xmpp_label' => L10n::t("XMPP:"),
677                                 '$about' => BBCode::convert($contact["about"], false),
678                                 '$about_label' => L10n::t("About:"),
679                                 '$keywords' => $contact["keywords"],
680                                 '$keywords_label' => L10n::t("Tags:"),
681                                 '$contact_action_button' => L10n::t("Actions"),
682                                 '$contact_actions' => $contact_actions,
683                                 '$contact_status' => L10n::t("Status"),
684                                 '$contact_settings_label' => $contact_settings_label,
685                                 '$contact_profile_label' => L10n::t("Profile"),
686                         ]);
687
688                         $arr = ['contact' => $contact, 'output' => $o];
689
690                         Addon::callHooks('contact_edit', $arr);
691
692                         return $arr['output'];
693                 }
694
695                 $blocked = false;
696                 $hidden = false;
697                 $ignored = false;
698                 $archived = false;
699                 $all = false;
700
701                 if (($a->argc == 2) && ($a->argv[1] === 'all')) {
702                         $sql_extra = '';
703                         $all = true;
704                 } elseif (($a->argc == 2) && ($a->argv[1] === 'blocked')) {
705                         $sql_extra = " AND `blocked` = 1 ";
706                         $blocked = true;
707                 } elseif (($a->argc == 2) && ($a->argv[1] === 'hidden')) {
708                         $sql_extra = " AND `hidden` = 1 ";
709                         $hidden = true;
710                 } elseif (($a->argc == 2) && ($a->argv[1] === 'ignored')) {
711                         $sql_extra = " AND `readonly` = 1 ";
712                         $ignored = true;
713                 } elseif (($a->argc == 2) && ($a->argv[1] === 'archived')) {
714                         $sql_extra = " AND `archive` = 1 ";
715                         $archived = true;
716                 } else {
717                         $sql_extra = " AND `blocked` = 0 ";
718                 }
719
720                 $sql_extra .= sprintf(" AND `network` != '%s' ", Protocol::PHANTOM);
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->setPagerTotal($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[] = self::getContactTemplateVars($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         public static function getTabsHTML($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         private static function getConversationsHMTL($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 .= self::getTabsHTML($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::ACTIVITYPUB, 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         private static function getPostsHTML($a, $contact_id)
984         {
985                 $contact = DBA::selectFirst('contact', ['uid', 'url', 'id'], ['id' => $contact_id]);
986
987                 $o = self::getTabsHTML($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::ACTIVITYPUB, 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         public static function getContactTemplateVars(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         private static function getContactActions($contact)
1075         {
1076                 $poll_enabled = in_array($contact['network'], [Protocol::ACTIVITYPUB, 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         }
1136 }