]> git.mxchange.org Git - friendica.git/blob - src/Module/Contact.php
[frio] Add jotnet fields to compose page
[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\Pager;
10 use Friendica\Content\Text\BBCode;
11 use Friendica\Content\Widget;
12 use Friendica\Core\ACL;
13 use Friendica\Core\Hook;
14 use Friendica\Core\L10n;
15 use Friendica\Core\Protocol;
16 use Friendica\Core\Renderer;
17 use Friendica\Core\System;
18 use Friendica\Core\Worker;
19 use Friendica\Database\DBA;
20 use Friendica\Model;
21 use Friendica\Network\HTTPException\BadRequestException;
22 use Friendica\Network\HTTPException\NotFoundException;
23 use Friendica\Network\Probe;
24 use Friendica\Util\DateTimeFormat;
25 use Friendica\Util\Proxy as ProxyUtils;
26 use Friendica\Util\Strings;
27
28 /**
29  *  Manages and show Contacts and their content
30  *
31  *  @brief manages contacts
32  */
33 class Contact extends BaseModule
34 {
35         private static function batchActions(App $a)
36         {
37                 if (empty($_POST['contact_batch']) || !is_array($_POST['contact_batch'])) {
38                         return;
39                 }
40
41                 $contacts_id = $_POST['contact_batch'];
42
43                 $stmt = DBA::select('contact', ['id', 'archive'], ['id' => $contacts_id, 'uid' => local_user(), 'self' => false, 'deleted' => false]);
44                 $orig_records = DBA::toArray($stmt);
45
46                 $count_actions = 0;
47                 foreach ($orig_records as $orig_record) {
48                         $contact_id = $orig_record['id'];
49                         if (!empty($_POST['contacts_batch_update'])) {
50                                 self::updateContactFromPoll($contact_id);
51                                 $count_actions++;
52                         }
53                         if (!empty($_POST['contacts_batch_block'])) {
54                                 self::blockContact($contact_id);
55                                 $count_actions++;
56                         }
57                         if (!empty($_POST['contacts_batch_ignore'])) {
58                                 self::ignoreContact($contact_id);
59                                 $count_actions++;
60                         }
61                         if (!empty($_POST['contacts_batch_archive'])
62                                 && self::archiveContact($contact_id, $orig_record)
63                         ) {
64                                 $count_actions++;
65                         }
66                         if (!empty($_POST['contacts_batch_drop'])) {
67                                 self::dropContact($orig_record);
68                                 $count_actions++;
69                         }
70                 }
71                 if ($count_actions > 0) {
72                         info(L10n::tt('%d contact edited.', '%d contacts edited.', $count_actions));
73                 }
74
75                 $a->internalRedirect('contact');
76         }
77
78         public static function post()
79         {
80                 $a = self::getApp();
81
82                 if (!local_user()) {
83                         return;
84                 }
85
86                 // @TODO: Replace with parameter from router
87                 if ($a->argv[1] === 'batch') {
88                         self::batchActions($a);
89                         return;
90                 }
91
92                 // @TODO: Replace with parameter from router
93                 $contact_id = intval($a->argv[1]);
94                 if (!$contact_id) {
95                         return;
96                 }
97
98                 if (!DBA::exists('contact', ['id' => $contact_id, 'uid' => local_user(), 'deleted' => false])) {
99                         notice(L10n::t('Could not access contact record.') . EOL);
100                         $a->internalRedirect('contact');
101                         return; // NOTREACHED
102                 }
103
104                 Hook::callAll('contact_edit_post', $_POST);
105
106                 $profile_id = intval(defaults($_POST, 'profile-assign', 0));
107                 if ($profile_id) {
108                         if (!DBA::exists('profile', ['id' => $profile_id, 'uid' => local_user()])) {
109                                 notice(L10n::t('Could not locate selected profile.') . EOL);
110                                 return;
111                         }
112                 }
113
114                 $hidden = !empty($_POST['hidden']);
115
116                 $notify = !empty($_POST['notify']);
117
118                 $fetch_further_information = intval(defaults($_POST, 'fetch_further_information', 0));
119
120                 $ffi_keyword_blacklist = Strings::escapeHtml(trim(defaults($_POST, 'ffi_keyword_blacklist', '')));
121
122                 $priority = intval(defaults($_POST, 'poll', 0));
123                 if ($priority > 5 || $priority < 0) {
124                         $priority = 0;
125                 }
126
127                 $info = Strings::escapeHtml(trim(defaults($_POST, 'info', '')));
128
129                 $r = DBA::update('contact', [
130                         'profile-id' => $profile_id,
131                         'priority'   => $priority,
132                         'info'       => $info,
133                         'hidden'     => $hidden,
134                         'notify_new_posts' => $notify,
135                         'fetch_further_information' => $fetch_further_information,
136                         'ffi_keyword_blacklist'     => $ffi_keyword_blacklist],
137                         ['id' => $contact_id, 'uid' => local_user()]
138                 );
139
140                 if (DBA::isResult($r)) {
141                         info(L10n::t('Contact updated.') . EOL);
142                 } else {
143                         notice(L10n::t('Failed to update contact record.') . EOL);
144                 }
145
146                 $contact = DBA::selectFirst('contact', [], ['id' => $contact_id, 'uid' => local_user(), 'deleted' => false]);
147                 if (DBA::isResult($contact)) {
148                         $a->data['contact'] = $contact;
149                 }
150
151                 return;
152         }
153
154         /* contact actions */
155
156         private static function updateContactFromPoll($contact_id)
157         {
158                 $contact = DBA::selectFirst('contact', ['uid', 'url', 'network'], ['id' => $contact_id, 'uid' => local_user(), 'deleted' => false]);
159                 if (!DBA::isResult($contact)) {
160                         return;
161                 }
162
163                 $uid = $contact['uid'];
164
165                 if ($contact['network'] == Protocol::OSTATUS) {
166                         $result = Model\Contact::createFromProbe($uid, $contact['url'], false, $contact['network']);
167
168                         if ($result['success']) {
169                                 DBA::update('contact', ['subhub' => 1], ['id' => $contact_id]);
170                         }
171                 } else {
172                         // pull feed and consume it, which should subscribe to the hub.
173                         Worker::add(PRIORITY_HIGH, 'OnePoll', $contact_id, 'force');
174                 }
175         }
176
177         private static function updateContactFromProbe($contact_id)
178         {
179                 $contact = DBA::selectFirst('contact', ['uid', 'url', 'network'], ['id' => $contact_id, 'uid' => local_user(), 'deleted' => false]);
180                 if (!DBA::isResult($contact)) {
181                         return;
182                 }
183
184                 $uid = $contact['uid'];
185
186                 $data = Probe::uri($contact['url'], '', 0, false);
187
188                 // 'Feed' or 'Unknown' is mostly a sign of communication problems
189                 if ((in_array($data['network'], [Protocol::FEED, Protocol::PHANTOM])) && ($data['network'] != $contact['network'])) {
190                         return;
191                 }
192
193                 $updatefields = ['name', 'nick', 'url', 'addr', 'batch', 'notify', 'poll', 'request', 'confirm', 'poco', 'network', 'alias'];
194                 $fields = [];
195
196                 if ($data['network'] == Protocol::OSTATUS) {
197                         $result = Model\Contact::createFromProbe($uid, $data['url'], false);
198
199                         if ($result['success']) {
200                                 $fields['subhub'] = true;
201                         }
202                 }
203
204                 foreach ($updatefields AS $field) {
205                         if (!empty($data[$field])) {
206                                 $fields[$field] = $data[$field];
207                         }
208                 }
209
210                 $fields['nurl'] = Strings::normaliseLink($data['url']);
211
212                 if (!empty($data['priority'])) {
213                         $fields['priority'] = intval($data['priority']);
214                 }
215
216                 if (empty($fields)) {
217                         return;
218                 }
219
220                 DBA::update('contact', $fields, ['id' => $contact_id, 'uid' => local_user()]);
221
222                 // Update the entry in the contact table
223                 Model\Contact::updateAvatar($data['photo'], local_user(), $contact_id, true);
224
225                 // Update the entry in the gcontact table
226                 Model\GContact::updateFromProbe($data['url']);
227         }
228
229         private static function blockContact($contact_id)
230         {
231                 $blocked = !Model\Contact::isBlockedByUser($contact_id, local_user());
232                 Model\Contact::setBlockedForUser($contact_id, local_user(), $blocked);
233         }
234
235         private static function ignoreContact($contact_id)
236         {
237                 $ignored = !Model\Contact::isIgnoredByUser($contact_id, local_user());
238                 Model\Contact::setIgnoredForUser($contact_id, local_user(), $ignored);
239         }
240
241         private static function archiveContact($contact_id, $orig_record)
242         {
243                 $archived = (defaults($orig_record, 'archive', '') ? 0 : 1);
244                 $r = DBA::update('contact', ['archive' => $archived], ['id' => $contact_id, 'uid' => local_user()]);
245
246                 return DBA::isResult($r);
247         }
248
249         private static function dropContact($orig_record)
250         {
251                 $owner = Model\User::getOwnerDataById(local_user());
252                 if (!DBA::isResult($owner)) {
253                         return;
254                 }
255
256                 Model\Contact::terminateFriendship($owner, $orig_record, true);
257                 Model\Contact::remove($orig_record['id']);
258         }
259
260         public static function content($update = 0)
261         {
262                 if (!local_user()) {
263                         return Login::form($_SERVER['REQUEST_URI']);
264                 }
265
266                 $a = self::getApp();
267
268                 $nets = defaults($_GET, 'nets', '');
269                 $rel  = defaults($_GET, 'rel' , '');
270
271                 if (empty($a->page['aside'])) {
272                         $a->page['aside'] = '';
273                 }
274
275                 $contact_id = null;
276                 $contact = null;
277                 // @TODO: Replace with parameter from router
278                 if ($a->argc == 2 && intval($a->argv[1])
279                         || $a->argc == 3 && intval($a->argv[1]) && in_array($a->argv[2], ['posts', 'conversations'])
280                 ) {
281                         $contact_id = intval($a->argv[1]);
282                         $contact = DBA::selectFirst('contact', [], ['id' => $contact_id, 'uid' => local_user(), 'deleted' => false]);
283
284                         if (!DBA::isResult($contact)) {
285                                 $contact = DBA::selectFirst('contact', [], ['id' => $contact_id, 'uid' => 0, 'deleted' => false]);
286                         }
287
288                         // Don't display contacts that are about to be deleted
289                         if ($contact['network'] == Protocol::PHANTOM) {
290                                 $contact = false;
291                         }
292                 }
293
294                 if (DBA::isResult($contact)) {
295                         if ($contact['self']) {
296                                 // @TODO: Replace with parameter from router
297                                 if (($a->argc == 3) && intval($a->argv[1]) && in_array($a->argv[2], ['posts', 'conversations'])) {
298                                         $a->internalRedirect('profile/' . $contact['nick']);
299                                 } else {
300                                         $a->internalRedirect('profile/' . $contact['nick'] . '?tab=profile');
301                                 }
302                         }
303
304                         $a->data['contact'] = $contact;
305
306                         if (($contact['network'] != '') && ($contact['network'] != Protocol::DFRN)) {
307                                 $network_link = Strings::formatNetworkName($contact['network'], $contact['url']);
308                         } else {
309                                 $network_link = '';
310                         }
311
312                         $follow_link = '';
313                         $unfollow_link = '';
314                         if (in_array($contact['network'], Protocol::NATIVE_SUPPORT)) {
315                                 if ($contact['uid'] && in_array($contact['rel'], [Model\Contact::SHARING, Model\Contact::FRIEND])) {
316                                         $unfollow_link = 'unfollow?url=' . urlencode($contact['url']);
317                                 } elseif(!$contact['pending']) {
318                                         $follow_link = 'follow?url=' . urlencode($contact['url']);
319                                 }
320                         }
321
322                         $wallmessage_link = '';
323                         if ($contact['uid'] && Model\Contact::canReceivePrivateMessages($contact)) {
324                                 $wallmessage_link = 'message/new/' . $contact['id'];
325                         }
326
327                         $vcard_widget = Renderer::replaceMacros(Renderer::getMarkupTemplate('widget/vcard.tpl'), [
328                                 '$name'         => $contact['name'],
329                                 '$photo'        => $contact['photo'],
330                                 '$url'          => Model\Contact::magicLinkByContact($contact, $contact['url']),
331                                 '$addr'         => defaults($contact, 'addr', ''),
332                                 '$network_link' => $network_link,
333                                 '$network'      => L10n::t('Network:'),
334                                 '$account_type' => Model\Contact::getAccountType($contact),
335                                 '$follow'       => L10n::t('Follow'),
336                                 '$follow_link'   => $follow_link,
337                                 '$unfollow'     => L10n::t('Unfollow'),
338                                 '$unfollow_link' => $unfollow_link,
339                                 '$wallmessage'  => L10n::t('Message'),
340                                 '$wallmessage_link' => $wallmessage_link,
341                         ]);
342
343                         $findpeople_widget = '';
344                         $follow_widget = '';
345                         $networks_widget = '';
346                         $rel_widget = '';
347                 } else {
348                         $vcard_widget = '';
349                         $findpeople_widget = Widget::findPeople();
350                         if (isset($_GET['add'])) {
351                                 $follow_widget = Widget::follow($_GET['add']);
352                         } else {
353                                 $follow_widget = Widget::follow();
354                         }
355
356                         $networks_widget = Widget::networks($_SERVER['REQUEST_URI'], $nets);
357                         $rel_widget = Widget::contactRels($_SERVER['REQUEST_URI'], $rel);
358                 }
359
360                 if ($contact['uid'] != 0) {
361                         $groups_widget = Model\Group::sidebarWidget('contact', 'group', 'full', 'everyone', $contact_id);
362                 } else {
363                         $groups_widget = null;
364                 }
365
366                 $a->page['aside'] .= $vcard_widget . $findpeople_widget . $follow_widget . $groups_widget . $networks_widget . $rel_widget;
367
368                 $tpl = Renderer::getMarkupTemplate('contacts-head.tpl');
369                 $a->page['htmlhead'] .= Renderer::replaceMacros($tpl, [
370                         '$baseurl' => $a->getBaseURL(true),
371                 ]);
372
373                 $sort_type = 0;
374                 $o = '';
375                 Nav::setSelected('contact');
376
377                 if (!local_user()) {
378                         notice(L10n::t('Permission denied.') . EOL);
379                         return Login::form();
380                 }
381
382                 if ($a->argc == 3) {
383                         $contact_id = intval($a->argv[1]);
384                         if (!$contact_id) {
385                                 throw new BadRequestException();
386                         }
387
388                         // @TODO: Replace with parameter from router
389                         $cmd = $a->argv[2];
390
391                         $orig_record = DBA::selectFirst('contact', [], ['id' => $contact_id, 'uid' => [0, local_user()], 'self' => false, 'deleted' => false]);
392                         if (!DBA::isResult($orig_record)) {
393                                 throw new NotFoundException(L10n::t('Contact not found'));
394                         }
395
396                         if ($cmd === 'update' && ($orig_record['uid'] != 0)) {
397                                 self::updateContactFromPoll($contact_id);
398                                 $a->internalRedirect('contact/' . $contact_id);
399                                 // NOTREACHED
400                         }
401
402                         if ($cmd === 'updateprofile' && ($orig_record['uid'] != 0)) {
403                                 self::updateContactFromProbe($contact_id);
404                                 $a->internalRedirect('crepair/' . $contact_id);
405                                 // NOTREACHED
406                         }
407
408                         if ($cmd === 'block') {
409                                 self::blockContact($contact_id);
410
411                                 $blocked = Model\Contact::isBlockedByUser($contact_id, local_user());
412                                 info(($blocked ? L10n::t('Contact has been blocked') : L10n::t('Contact has been unblocked')) . EOL);
413
414                                 $a->internalRedirect('contact/' . $contact_id);
415                                 // NOTREACHED
416                         }
417
418                         if ($cmd === 'ignore') {
419                                 self::ignoreContact($contact_id);
420
421                                 $ignored = Model\Contact::isIgnoredByUser($contact_id, local_user());
422                                 info(($ignored ? L10n::t('Contact has been ignored') : L10n::t('Contact has been unignored')) . EOL);
423
424                                 $a->internalRedirect('contact/' . $contact_id);
425                                 // NOTREACHED
426                         }
427
428                         if ($cmd === 'archive' && ($orig_record['uid'] != 0)) {
429                                 $r = self::archiveContact($contact_id, $orig_record);
430                                 if ($r) {
431                                         $archived = (($orig_record['archive']) ? 0 : 1);
432                                         info((($archived) ? L10n::t('Contact has been archived') : L10n::t('Contact has been unarchived')) . EOL);
433                                 }
434
435                                 $a->internalRedirect('contact/' . $contact_id);
436                                 // NOTREACHED
437                         }
438
439                         if ($cmd === 'drop' && ($orig_record['uid'] != 0)) {
440                                 // Check if we should do HTML-based delete confirmation
441                                 if (!empty($_REQUEST['confirm'])) {
442                                         // <form> can't take arguments in its 'action' parameter
443                                         // so add any arguments as hidden inputs
444                                         $query = explode_querystring($a->query_string);
445                                         $inputs = [];
446                                         foreach ($query['args'] as $arg) {
447                                                 if (strpos($arg, 'confirm=') === false) {
448                                                         $arg_parts = explode('=', $arg);
449                                                         $inputs[] = ['name' => $arg_parts[0], 'value' => $arg_parts[1]];
450                                                 }
451                                         }
452
453                                         $a->page['aside'] = '';
454
455                                         return Renderer::replaceMacros(Renderer::getMarkupTemplate('contact_drop_confirm.tpl'), [
456                                                 '$header' => L10n::t('Drop contact'),
457                                                 '$contact' => self::getContactTemplateVars($orig_record),
458                                                 '$method' => 'get',
459                                                 '$message' => L10n::t('Do you really want to delete this contact?'),
460                                                 '$extra_inputs' => $inputs,
461                                                 '$confirm' => L10n::t('Yes'),
462                                                 '$confirm_url' => $query['base'],
463                                                 '$confirm_name' => 'confirmed',
464                                                 '$cancel' => L10n::t('Cancel'),
465                                         ]);
466                                 }
467                                 // Now check how the user responded to the confirmation query
468                                 if (!empty($_REQUEST['canceled'])) {
469                                         $a->internalRedirect('contact');
470                                 }
471
472                                 self::dropContact($orig_record);
473                                 info(L10n::t('Contact has been removed.') . EOL);
474
475                                 $a->internalRedirect('contact');
476                                 // NOTREACHED
477                         }
478                         if ($cmd === 'posts') {
479                                 return self::getPostsHTML($a, $contact_id);
480                         }
481                         if ($cmd === 'conversations') {
482                                 return self::getConversationsHMTL($a, $contact_id, $update);
483                         }
484                 }
485
486                 $_SESSION['return_path'] = $a->query_string;
487
488                 if (!empty($a->data['contact']) && is_array($a->data['contact'])) {
489                         $contact = $a->data['contact'];
490
491                         $a->page['htmlhead'] .= Renderer::replaceMacros(Renderer::getMarkupTemplate('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::FEDERATED)) {
525                                 $relation_text = '';
526                         }
527
528                         $relation_text = sprintf($relation_text, $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'] <= DBA::NULL_DATETIME) ? L10n::t('Never') : DateTimeFormat::local($contact['last-update'], 'D, j M Y, g:i A'));
540
541                         if ($contact['last-update'] > DBA::NULL_DATETIME) {
542                                 $last_update .= ' ' . (($contact['last-update'] <= $contact['success_update']) ? L10n::t('(Update was successful)') : L10n::t('(Update was not successful)'));
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'] > DBA::NULL_DATETIME && $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                                         [
563                                                 '0' => L10n::t('Disabled'),
564                                                 '1' => L10n::t('Fetch information'),
565                                                 '3' => L10n::t('Fetch keywords'),
566                                                 '2' => L10n::t('Fetch information and keywords')
567                                         ]
568                                 ];
569                         }
570
571                         $poll_interval = null;
572                         if (in_array($contact['network'], [Protocol::FEED, Protocol::MAIL])) {
573                                 $poll_interval = ContactSelector::pollInterval($contact['priority'], !$poll_enabled);
574                         }
575
576                         $profile_select = null;
577                         if ($contact['network'] == Protocol::DFRN) {
578                                 $profile_select = ContactSelector::profileAssign($contact['profile-id'], $contact['network'] !== Protocol::DFRN);
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 = Renderer::getMarkupTemplate('contact_edit.tpl');
595                         $o .= Renderer::replaceMacros($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(Strings::escapeTags($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                                 '$profile_select' => $profile_select,
622                                 '$contact_id'     => $contact['id'],
623                                 '$block_text'     => ($contact['blocked'] ? L10n::t('Unblock') : L10n::t('Block')),
624                                 '$ignore_text'    => ($contact['readonly'] ? L10n::t('Unignore') : L10n::t('Ignore')),
625                                 '$insecure'       => (in_array($contact['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::MAIL, Protocol::DIASPORA]) ? '' : $insecure),
626                                 '$info'           => $contact['info'],
627                                 '$cinfo'          => ['info', '', $contact['info'], ''],
628                                 '$blocked'        => ($contact['blocked'] ? L10n::t('Currently blocked') : ''),
629                                 '$ignored'        => ($contact['readonly'] ? L10n::t('Currently ignored') : ''),
630                                 '$archived'       => ($contact['archive'] ? L10n::t('Currently archived') : ''),
631                                 '$pending'        => ($contact['pending'] ? L10n::t('Awaiting connection acknowledge') : ''),
632                                 '$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')],
633                                 '$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')],
634                                 '$fetch_further_information' => $fetch_further_information,
635                                 '$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')],
636                                 '$photo'          => $contact['photo'],
637                                 '$name'           => $contact['name'],
638                                 '$dir_icon'       => $dir_icon,
639                                 '$sparkle'        => $sparkle,
640                                 '$url'            => $url,
641                                 '$profileurllabel'=> L10n::t('Profile URL'),
642                                 '$profileurl'     => $contact['url'],
643                                 '$account_type'   => Model\Contact::getAccountType($contact),
644                                 '$location'       => BBCode::convert($contact['location']),
645                                 '$location_label' => L10n::t('Location:'),
646                                 '$xmpp'           => BBCode::convert($contact['xmpp']),
647                                 '$xmpp_label'     => L10n::t('XMPP:'),
648                                 '$about'          => BBCode::convert($contact['about'], false),
649                                 '$about_label'    => L10n::t('About:'),
650                                 '$keywords'       => $contact['keywords'],
651                                 '$keywords_label' => L10n::t('Tags:'),
652                                 '$contact_action_button' => L10n::t('Actions'),
653                                 '$contact_actions'=> $contact_actions,
654                                 '$contact_status' => L10n::t('Status'),
655                                 '$contact_settings_label' => $contact_settings_label,
656                                 '$contact_profile_label' => L10n::t('Profile'),
657                         ]);
658
659                         $arr = ['contact' => $contact, 'output' => $o];
660
661                         Hook::callAll('contact_edit', $arr);
662
663                         return $arr['output'];
664                 }
665
666                 // @TODO: Replace with parameter from router
667                 $type = defaults($a->argv, 1, '');
668
669                 switch ($type) {
670                         case 'blocked':
671                                 $sql_extra = " AND `blocked`";
672                                 break;
673                         case 'hidden':
674                                 $sql_extra = " AND `hidden` AND NOT `blocked`";
675                                 break;
676                         case 'ignored':
677                                 $sql_extra = " AND `readonly` AND NOT `blocked`";
678                                 break;
679                         case 'archived':
680                                 $sql_extra = " AND `archive` AND NOT `blocked`";
681                                 break;
682                         default:
683                                 $sql_extra = " AND NOT `archive` AND NOT `blocked`";
684                 }
685
686                 $sql_extra .= sprintf(" AND `network` != '%s' ", Protocol::PHANTOM);
687
688                 $search = Strings::escapeTags(trim(defaults($_GET, 'search', '')));
689                 $nets   = Strings::escapeTags(trim(defaults($_GET, 'nets'  , '')));
690                 $rel    = Strings::escapeTags(trim(defaults($_GET, 'rel'   , '')));
691
692                 $tabs = [
693                         [
694                                 'label' => L10n::t('All Contacts'),
695                                 'url'   => 'contact',
696                                 'sel'   => !$type ? 'active' : '',
697                                 'title' => L10n::t('Show all contacts'),
698                                 'id'    => 'showall-tab',
699                                 'accesskey' => 'l',
700                         ],
701                         [
702                                 'label' => L10n::t('Blocked'),
703                                 'url'   => 'contact/blocked',
704                                 'sel'   => $type == 'blocked' ? 'active' : '',
705                                 'title' => L10n::t('Only show blocked contacts'),
706                                 'id'    => 'showblocked-tab',
707                                 'accesskey' => 'b',
708                         ],
709                         [
710                                 'label' => L10n::t('Ignored'),
711                                 'url'   => 'contact/ignored',
712                                 'sel'   => $type == 'ignored' ? 'active' : '',
713                                 'title' => L10n::t('Only show ignored contacts'),
714                                 'id'    => 'showignored-tab',
715                                 'accesskey' => 'i',
716                         ],
717                         [
718                                 'label' => L10n::t('Archived'),
719                                 'url'   => 'contact/archived',
720                                 'sel'   => $type == 'archived' ? 'active' : '',
721                                 'title' => L10n::t('Only show archived contacts'),
722                                 'id'    => 'showarchived-tab',
723                                 'accesskey' => 'y',
724                         ],
725                         [
726                                 'label' => L10n::t('Hidden'),
727                                 'url'   => 'contact/hidden',
728                                 'sel'   => $type == 'hidden' ? 'active' : '',
729                                 'title' => L10n::t('Only show hidden contacts'),
730                                 'id'    => 'showhidden-tab',
731                                 'accesskey' => 'h',
732                         ],
733                         [
734                                 'label' => L10n::t('Groups'),
735                                 'url'   => 'group',
736                                 'sel'   => '',
737                                 'title' => L10n::t('Organize your contact groups'),
738                                 'id'    => 'contactgroups-tab',
739                                 'accesskey' => 'e',
740                         ],
741                 ];
742
743                 $tab_tpl = Renderer::getMarkupTemplate('common_tabs.tpl');
744                 $t = Renderer::replaceMacros($tab_tpl, ['$tabs' => $tabs]);
745
746                 $total = 0;
747                 $searching = false;
748                 $search_hdr = null;
749                 if ($search) {
750                         $searching = true;
751                         $search_hdr = $search;
752                         $search_txt = DBA::escape(Strings::protectSprintf(preg_quote($search)));
753                         $sql_extra .= " AND (name REGEXP '$search_txt' OR url REGEXP '$search_txt'  OR nick REGEXP '$search_txt') ";
754                 }
755
756                 if ($nets) {
757                         $sql_extra .= sprintf(" AND network = '%s' ", DBA::escape($nets));
758                 }
759
760                 switch ($rel) {
761                         case 'followers': $sql_extra .= " AND `rel` IN (1, 3)"; break;
762                         case 'following': $sql_extra .= " AND `rel` IN (2, 3)"; break;
763                         case 'mutuals': $sql_extra .= " AND `rel` = 3"; break;
764                 }
765
766                 $sql_extra .=  " AND NOT `deleted` ";
767
768                 $sql_extra2 = ((($sort_type > 0) && ($sort_type <= Model\Contact::FRIEND)) ? sprintf(" AND `rel` = %d ", intval($sort_type)) : '');
769
770                 $r = q("SELECT COUNT(*) AS `total` FROM `contact`
771                         WHERE `uid` = %d AND `self` = 0 AND `pending` = 0 $sql_extra $sql_extra2 ",
772                         intval($_SESSION['uid'])
773                 );
774                 if (DBA::isResult($r)) {
775                         $total = $r[0]['total'];
776                 }
777                 $pager = new Pager($a->query_string);
778
779                 $sql_extra3 = Widget::unavailableNetworks();
780
781                 $contacts = [];
782
783                 $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 ",
784                         intval($_SESSION['uid']),
785                         $pager->getStart(),
786                         $pager->getItemsPerPage()
787                 );
788                 if (DBA::isResult($r)) {
789                         foreach ($r as $rr) {
790                                 $rr['blocked'] = Model\Contact::isBlockedByUser($rr['id'], local_user());
791                                 $rr['readonly'] = Model\Contact::isIgnoredByUser($rr['id'], local_user());
792                                 $contacts[] = self::getContactTemplateVars($rr);
793                         }
794                 }
795
796                 switch ($rel) {
797                         case 'followers': $header = L10n::t('Followers'); break;
798                         case 'following': $header = L10n::t('Following'); break;
799                         case 'mutuals':   $header = L10n::t('Mutual friends'); break;
800                         default:          $header = L10n::t('Contacts');
801                 }
802
803                 switch ($type) {
804                         case 'blocked':  $header .= ' - ' . L10n::t('Blocked'); break;
805                         case 'hidden':   $header .= ' - ' . L10n::t('Hidden'); break;
806                         case 'ignored':  $header .= ' - ' . L10n::t('Ignored'); break;
807                         case 'archived': $header .= ' - ' . L10n::t('Archived'); break;
808                 }
809
810                 $header .= $nets ? ' - ' . ContactSelector::networkToName($nets) : '';
811
812                 $tpl = Renderer::getMarkupTemplate('contacts-template.tpl');
813                 $o .= Renderer::replaceMacros($tpl, [
814                         '$header'     => $header,
815                         '$tabs'       => $t,
816                         '$total'      => $total,
817                         '$search'     => $search_hdr,
818                         '$desc'       => L10n::t('Search your contacts'),
819                         '$finding'    => $searching ? L10n::t('Results for: %s', $search) : '',
820                         '$submit'     => L10n::t('Find'),
821                         '$cmd'        => $a->cmd,
822                         '$contacts'   => $contacts,
823                         '$contact_drop_confirm' => L10n::t('Do you really want to delete this contact?'),
824                         'multiselect' => 1,
825                         '$batch_actions' => [
826                                 'contacts_batch_update'  => L10n::t('Update'),
827                                 'contacts_batch_block'   => L10n::t('Block') . '/' . L10n::t('Unblock'),
828                                 'contacts_batch_ignore'  => L10n::t('Ignore') . '/' . L10n::t('Unignore'),
829                                 'contacts_batch_archive' => L10n::t('Archive') . '/' . L10n::t('Unarchive'),
830                                 'contacts_batch_drop'    => L10n::t('Delete'),
831                         ],
832                         '$h_batch_actions' => L10n::t('Batch Actions'),
833                         '$paginate'   => $pager->renderFull($total),
834                 ]);
835
836                 return $o;
837         }
838
839         /**
840          * @brief List of pages for the Contact TabBar
841          *
842          * Available Pages are 'Status', 'Profile', 'Contacts' and 'Common Friends'
843          *
844          * @param App   $a
845          * @param array $contact    The contact array
846          * @param int   $active_tab 1 if tab should be marked as active
847          *
848          * @return string HTML string of the contact page tabs buttons.
849          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
850          */
851         public static function getTabsHTML($a, $contact, $active_tab)
852         {
853                 // tabs
854                 $tabs = [
855                         [
856                                 'label' => L10n::t('Status'),
857                                 'url'   => "contact/" . $contact['id'] . "/conversations",
858                                 'sel'   => (($active_tab == 1) ? 'active' : ''),
859                                 'title' => L10n::t('Conversations started by this contact'),
860                                 'id'    => 'status-tab',
861                                 'accesskey' => 'm',
862                         ],
863                         [
864                                 'label' => L10n::t('Posts and Comments'),
865                                 'url'   => "contact/" . $contact['id'] . "/posts",
866                                 'sel'   => (($active_tab == 2) ? 'active' : ''),
867                                 'title' => L10n::t('Status Messages and Posts'),
868                                 'id'    => 'posts-tab',
869                                 'accesskey' => 'p',
870                         ],
871                         [
872                                 'label' => L10n::t('Profile'),
873                                 'url'   => "contact/" . $contact['id'],
874                                 'sel'   => (($active_tab == 3) ? 'active' : ''),
875                                 'title' => L10n::t('Profile Details'),
876                                 'id'    => 'profile-tab',
877                                 'accesskey' => 'o',
878                         ]
879                 ];
880
881                 // Show this tab only if there is visible friend list
882                 $x = Model\GContact::countAllFriends(local_user(), $contact['id']);
883                 if ($x) {
884                         $tabs[] = ['label' => L10n::t('Contacts'),
885                                 'url'   => "allfriends/" . $contact['id'],
886                                 'sel'   => (($active_tab == 4) ? 'active' : ''),
887                                 'title' => L10n::t('View all contacts'),
888                                 'id'    => 'allfriends-tab',
889                                 'accesskey' => 't'];
890                 }
891
892                 // Show this tab only if there is visible common friend list
893                 $common = Model\GContact::countCommonFriends(local_user(), $contact['id']);
894                 if ($common) {
895                         $tabs[] = ['label' => L10n::t('Common Friends'),
896                                 'url'   => "common/loc/" . local_user() . "/" . $contact['id'],
897                                 'sel'   => (($active_tab == 5) ? 'active' : ''),
898                                 'title' => L10n::t('View all common friends'),
899                                 'id'    => 'common-loc-tab',
900                                 'accesskey' => 'd'
901                         ];
902                 }
903
904                 if (!empty($contact['uid'])) {
905                         $tabs[] = ['label' => L10n::t('Advanced'),
906                                 'url'   => 'crepair/' . $contact['id'],
907                                 'sel'   => (($active_tab == 6) ? 'active' : ''),
908                                 'title' => L10n::t('Advanced Contact Settings'),
909                                 'id'    => 'advanced-tab',
910                                 'accesskey' => 'r'
911                         ];
912                 }
913
914                 $tab_tpl = Renderer::getMarkupTemplate('common_tabs.tpl');
915                 $tab_str = Renderer::replaceMacros($tab_tpl, ['$tabs' => $tabs]);
916
917                 return $tab_str;
918         }
919
920         private static function getConversationsHMTL($a, $contact_id, $update)
921         {
922                 $o = '';
923
924                 if (!$update) {
925                         // We need the editor here to be able to reshare an item.
926                         if (local_user()) {
927                                 $x = [
928                                         'is_owner' => true,
929                                         'allow_location' => $a->user['allow_location'],
930                                         'default_location' => $a->user['default-location'],
931                                         'nickname' => $a->user['nickname'],
932                                         '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'),
933                                         'acl' => ACL::getFullSelectorHTML($a->user, true),
934                                         'bang' => '',
935                                         'visitor' => 'block',
936                                         'profile_uid' => local_user(),
937                                 ];
938                                 $o = status_editor($a, $x, 0, true);
939                         }
940                 }
941
942                 $contact = DBA::selectFirst('contact', ['uid', 'url', 'id'], ['id' => $contact_id, 'deleted' => false]);
943
944                 if (!$update) {
945                         $o .= self::getTabsHTML($a, $contact, 1);
946                 }
947
948                 if (DBA::isResult($contact)) {
949                         $a->page['aside'] = '';
950
951                         $profiledata = Model\Contact::getDetailsByURL($contact['url']);
952
953                         Model\Profile::load($a, '', 0, $profiledata, true);
954                         $o .= Model\Contact::getPostsFromUrl($contact['url'], true, $update);
955                 }
956
957                 return $o;
958         }
959
960         private static function getPostsHTML($a, $contact_id)
961         {
962                 $contact = DBA::selectFirst('contact', ['uid', 'url', 'id'], ['id' => $contact_id, 'deleted' => false]);
963
964                 $o = self::getTabsHTML($a, $contact, 2);
965
966                 if (DBA::isResult($contact)) {
967                         $a->page['aside'] = '';
968
969                         $profiledata = Model\Contact::getDetailsByURL($contact['url']);
970
971                         if (local_user() && in_array($profiledata['network'], Protocol::FEDERATED)) {
972                                 $profiledata['remoteconnect'] = System::baseUrl() . '/follow?url=' . urlencode($profiledata['url']);
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'      => $rr['name'],
1031                         'username'  => $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'      => $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 }