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