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