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