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