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