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