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