]> git.mxchange.org Git - friendica.git/blob - src/Module/Contact.php
Added post update to remove duplicated contacts
[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                         default:
645                                 $sql_extra = " AND NOT `archive` AND NOT `blocked`";
646                 }
647
648                 $sql_extra .= sprintf(" AND `network` != '%s' ", Protocol::PHANTOM);
649
650                 $search = Strings::escapeTags(trim(defaults($_GET, 'search', '')));
651                 $nets   = Strings::escapeTags(trim(defaults($_GET, 'nets'  , '')));
652                 $rel    = Strings::escapeTags(trim(defaults($_GET, 'rel'   , '')));
653
654                 $tabs = [
655                         [
656                                 'label' => L10n::t('All Contacts'),
657                                 'url'   => 'contact',
658                                 'sel'   => !$type ? 'active' : '',
659                                 'title' => L10n::t('Show all contacts'),
660                                 'id'    => 'showall-tab',
661                                 'accesskey' => 'l',
662                         ],
663                         [
664                                 'label' => L10n::t('Blocked'),
665                                 'url'   => 'contact/blocked',
666                                 'sel'   => $type == 'blocked' ? 'active' : '',
667                                 'title' => L10n::t('Only show blocked contacts'),
668                                 'id'    => 'showblocked-tab',
669                                 'accesskey' => 'b',
670                         ],
671                         [
672                                 'label' => L10n::t('Ignored'),
673                                 'url'   => 'contact/ignored',
674                                 'sel'   => $type == 'ignored' ? 'active' : '',
675                                 'title' => L10n::t('Only show ignored contacts'),
676                                 'id'    => 'showignored-tab',
677                                 'accesskey' => 'i',
678                         ],
679                         [
680                                 'label' => L10n::t('Archived'),
681                                 'url'   => 'contact/archived',
682                                 'sel'   => $type == 'archived' ? 'active' : '',
683                                 'title' => L10n::t('Only show archived contacts'),
684                                 'id'    => 'showarchived-tab',
685                                 'accesskey' => 'y',
686                         ],
687                         [
688                                 'label' => L10n::t('Hidden'),
689                                 'url'   => 'contact/hidden',
690                                 'sel'   => $type == 'hidden' ? 'active' : '',
691                                 'title' => L10n::t('Only show hidden contacts'),
692                                 'id'    => 'showhidden-tab',
693                                 'accesskey' => 'h',
694                         ],
695                         [
696                                 'label' => L10n::t('Groups'),
697                                 'url'   => 'group',
698                                 'sel'   => '',
699                                 'title' => L10n::t('Organize your contact groups'),
700                                 'id'    => 'contactgroups-tab',
701                                 'accesskey' => 'e',
702                         ],
703                 ];
704
705                 $tab_tpl = Renderer::getMarkupTemplate('common_tabs.tpl');
706                 $t = Renderer::replaceMacros($tab_tpl, ['$tabs' => $tabs]);
707
708                 $total = 0;
709                 $searching = false;
710                 $search_hdr = null;
711                 if ($search) {
712                         $searching = true;
713                         $search_hdr = $search;
714                         $search_txt = DBA::escape(Strings::protectSprintf(preg_quote($search)));
715                         $sql_extra .= " AND (name REGEXP '$search_txt' OR url REGEXP '$search_txt'  OR nick REGEXP '$search_txt') ";
716                 }
717
718                 if ($nets) {
719                         $sql_extra .= sprintf(" AND network = '%s' ", DBA::escape($nets));
720                 }
721
722                 switch ($rel) {
723                         case 'followers': $sql_extra .= " AND `rel` IN (1, 3)"; break;
724                         case 'following': $sql_extra .= " AND `rel` IN (2, 3)"; break;
725                         case 'mutuals': $sql_extra .= " AND `rel` = 3"; break;
726                 }
727
728                 $sql_extra .=  " AND NOT `deleted` ";
729
730                 $sql_extra2 = ((($sort_type > 0) && ($sort_type <= Model\Contact::FRIEND)) ? sprintf(" AND `rel` = %d ", intval($sort_type)) : '');
731
732                 $r = q("SELECT COUNT(*) AS `total` FROM `contact`
733                         WHERE `uid` = %d AND `self` = 0 AND `pending` = 0 $sql_extra $sql_extra2 ",
734                         intval($_SESSION['uid'])
735                 );
736                 if (DBA::isResult($r)) {
737                         $total = $r[0]['total'];
738                 }
739                 $pager = new Pager($a->query_string);
740
741                 $sql_extra3 = Widget::unavailableNetworks();
742
743                 $contacts = [];
744
745                 $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 ",
746                         intval($_SESSION['uid']),
747                         $pager->getStart(),
748                         $pager->getItemsPerPage()
749                 );
750                 if (DBA::isResult($r)) {
751                         foreach ($r as $rr) {
752                                 $rr['blocked'] = Model\Contact::isBlockedByUser($rr['id'], local_user());
753                                 $rr['readonly'] = Model\Contact::isIgnoredByUser($rr['id'], local_user());
754                                 $contacts[] = self::getContactTemplateVars($rr);
755                         }
756                 }
757
758                 switch ($rel) {
759                         case 'followers': $header = L10n::t('Followers'); break;
760                         case 'following': $header = L10n::t('Following'); break;
761                         case 'mutuals':   $header = L10n::t('Mutual friends'); break;
762                         default:          $header = L10n::t('Contacts');
763                 }
764
765                 switch ($type) {
766                         case 'blocked':  $header .= ' - ' . L10n::t('Blocked'); break;
767                         case 'hidden':   $header .= ' - ' . L10n::t('Hidden'); break;
768                         case 'ignored':  $header .= ' - ' . L10n::t('Ignored'); break;
769                         case 'archived': $header .= ' - ' . L10n::t('Archived'); break;
770                 }
771
772                 $header .= $nets ? ' - ' . ContactSelector::networkToName($nets) : '';
773
774                 $tpl = Renderer::getMarkupTemplate('contacts-template.tpl');
775                 $o .= Renderer::replaceMacros($tpl, [
776                         '$header'     => $header,
777                         '$tabs'       => $t,
778                         '$total'      => $total,
779                         '$search'     => $search_hdr,
780                         '$desc'       => L10n::t('Search your contacts'),
781                         '$finding'    => $searching ? L10n::t('Results for: %s', $search) : '',
782                         '$submit'     => L10n::t('Find'),
783                         '$cmd'        => $a->cmd,
784                         '$contacts'   => $contacts,
785                         '$contact_drop_confirm' => L10n::t('Do you really want to delete this contact?'),
786                         'multiselect' => 1,
787                         '$batch_actions' => [
788                                 'contacts_batch_update'  => L10n::t('Update'),
789                                 'contacts_batch_block'   => L10n::t('Block') . '/' . L10n::t('Unblock'),
790                                 'contacts_batch_ignore'  => L10n::t('Ignore') . '/' . L10n::t('Unignore'),
791                                 'contacts_batch_archive' => L10n::t('Archive') . '/' . L10n::t('Unarchive'),
792                                 'contacts_batch_drop'    => L10n::t('Delete'),
793                         ],
794                         '$h_batch_actions' => L10n::t('Batch Actions'),
795                         '$paginate'   => $pager->renderFull($total),
796                 ]);
797
798                 return $o;
799         }
800
801         /**
802          * @brief List of pages for the Contact TabBar
803          *
804          * Available Pages are 'Status', 'Profile', 'Contacts' and 'Common Friends'
805          *
806          * @param App   $a
807          * @param array $contact    The contact array
808          * @param int   $active_tab 1 if tab should be marked as active
809          *
810          * @return string HTML string of the contact page tabs buttons.
811          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
812          */
813         public static function getTabsHTML($a, $contact, $active_tab)
814         {
815                 // tabs
816                 $tabs = [
817                         [
818                                 'label' => L10n::t('Status'),
819                                 'url'   => "contact/" . $contact['id'] . "/conversations",
820                                 'sel'   => (($active_tab == 1) ? 'active' : ''),
821                                 'title' => L10n::t('Conversations started by this contact'),
822                                 'id'    => 'status-tab',
823                                 'accesskey' => 'm',
824                         ],
825                         [
826                                 'label' => L10n::t('Posts and Comments'),
827                                 'url'   => "contact/" . $contact['id'] . "/posts",
828                                 'sel'   => (($active_tab == 2) ? 'active' : ''),
829                                 'title' => L10n::t('Status Messages and Posts'),
830                                 'id'    => 'posts-tab',
831                                 'accesskey' => 'p',
832                         ],
833                         [
834                                 'label' => L10n::t('Profile'),
835                                 'url'   => "contact/" . $contact['id'],
836                                 'sel'   => (($active_tab == 3) ? 'active' : ''),
837                                 'title' => L10n::t('Profile Details'),
838                                 'id'    => 'profile-tab',
839                                 'accesskey' => 'o',
840                         ]
841                 ];
842
843                 // Show this tab only if there is visible friend list
844                 $x = Model\GContact::countAllFriends(local_user(), $contact['id']);
845                 if ($x) {
846                         $tabs[] = ['label' => L10n::t('Contacts'),
847                                 'url'   => "allfriends/" . $contact['id'],
848                                 'sel'   => (($active_tab == 4) ? 'active' : ''),
849                                 'title' => L10n::t('View all contacts'),
850                                 'id'    => 'allfriends-tab',
851                                 'accesskey' => 't'];
852                 }
853
854                 // Show this tab only if there is visible common friend list
855                 $common = Model\GContact::countCommonFriends(local_user(), $contact['id']);
856                 if ($common) {
857                         $tabs[] = ['label' => L10n::t('Common Friends'),
858                                 'url'   => "common/loc/" . local_user() . "/" . $contact['id'],
859                                 'sel'   => (($active_tab == 5) ? 'active' : ''),
860                                 'title' => L10n::t('View all common friends'),
861                                 'id'    => 'common-loc-tab',
862                                 'accesskey' => 'd'
863                         ];
864                 }
865
866                 if (!empty($contact['uid'])) {
867                         $tabs[] = ['label' => L10n::t('Advanced'),
868                                 'url'   => 'crepair/' . $contact['id'],
869                                 'sel'   => (($active_tab == 6) ? 'active' : ''),
870                                 'title' => L10n::t('Advanced Contact Settings'),
871                                 'id'    => 'advanced-tab',
872                                 'accesskey' => 'r'
873                         ];
874                 }
875
876                 $tab_tpl = Renderer::getMarkupTemplate('common_tabs.tpl');
877                 $tab_str = Renderer::replaceMacros($tab_tpl, ['$tabs' => $tabs]);
878
879                 return $tab_str;
880         }
881
882         private static function getConversationsHMTL($a, $contact_id, $update)
883         {
884                 $o = '';
885
886                 if (!$update) {
887                         // We need the editor here to be able to reshare an item.
888                         if (local_user()) {
889                                 $x = [
890                                         'is_owner' => true,
891                                         'allow_location' => $a->user['allow_location'],
892                                         'default_location' => $a->user['default-location'],
893                                         'nickname' => $a->user['nickname'],
894                                         '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'),
895                                         'acl' => ACL::getFullSelectorHTML($a->user, true),
896                                         'bang' => '',
897                                         'visitor' => 'block',
898                                         'profile_uid' => local_user(),
899                                 ];
900                                 $o = status_editor($a, $x, 0, true);
901                         }
902                 }
903
904                 $contact = DBA::selectFirst('contact', ['uid', 'url', 'id'], ['id' => $contact_id, 'deleted' => false]);
905
906                 if (!$update) {
907                         $o .= self::getTabsHTML($a, $contact, 1);
908                 }
909
910                 if (DBA::isResult($contact)) {
911                         $a->page['aside'] = '';
912
913                         $profiledata = Model\Contact::getDetailsByURL($contact['url']);
914
915                         Model\Profile::load($a, '', 0, $profiledata, true);
916                         $o .= Model\Contact::getPostsFromUrl($contact['url'], true, $update);
917                 }
918
919                 return $o;
920         }
921
922         private static function getPostsHTML($a, $contact_id)
923         {
924                 $contact = DBA::selectFirst('contact', ['uid', 'url', 'id'], ['id' => $contact_id, 'deleted' => false]);
925
926                 $o = self::getTabsHTML($a, $contact, 2);
927
928                 if (DBA::isResult($contact)) {
929                         $a->page['aside'] = '';
930
931                         $profiledata = Model\Contact::getDetailsByURL($contact['url']);
932
933                         if (local_user() && in_array($profiledata['network'], Protocol::FEDERATED)) {
934                                 $profiledata['remoteconnect'] = System::baseUrl() . '/follow?url=' . urlencode($profiledata['url']);
935                         }
936
937                         Model\Profile::load($a, '', 0, $profiledata, true);
938                         $o .= Model\Contact::getPostsFromUrl($contact['url']);
939                 }
940
941                 return $o;
942         }
943
944         public static function getContactTemplateVars(array $rr)
945         {
946                 $dir_icon = '';
947                 $alt_text = '';
948
949                 if (!empty($rr['uid']) && !empty($rr['rel'])) {
950                         switch ($rr['rel']) {
951                                 case Model\Contact::FRIEND:
952                                         $dir_icon = 'images/lrarrow.gif';
953                                         $alt_text = L10n::t('Mutual Friendship');
954                                         break;
955
956                                 case Model\Contact::FOLLOWER;
957                                         $dir_icon = 'images/larrow.gif';
958                                         $alt_text = L10n::t('is a fan of yours');
959                                         break;
960
961                                 case Model\Contact::SHARING;
962                                         $dir_icon = 'images/rarrow.gif';
963                                         $alt_text = L10n::t('you are a fan of');
964                                         break;
965
966                                 default:
967                                         break;
968                         }
969                 }
970
971                 $url = Model\Contact::magicLink($rr['url']);
972
973                 if (strpos($url, 'redir/') === 0) {
974                         $sparkle = ' class="sparkle" ';
975                 } else {
976                         $sparkle = '';
977                 }
978
979                 if ($rr['self']) {
980                         $dir_icon = 'images/larrow.gif';
981                         $alt_text = L10n::t('This is you');
982                         $url = $rr['url'];
983                         $sparkle = '';
984                 }
985
986                 return [
987                         'img_hover' => L10n::t('Visit %s\'s profile [%s]', $rr['name'], $rr['url']),
988                         'edit_hover'=> L10n::t('Edit contact'),
989                         'photo_menu'=> Model\Contact::photoMenu($rr),
990                         'id'        => $rr['id'],
991                         'alt_text'  => $alt_text,
992                         'dir_icon'  => $dir_icon,
993                         'thumb'     => ProxyUtils::proxifyUrl($rr['thumb'], false, ProxyUtils::SIZE_THUMB),
994                         'name'      => $rr['name'],
995                         'username'  => $rr['name'],
996                         'account_type' => Model\Contact::getAccountType($rr),
997                         'sparkle'   => $sparkle,
998                         'itemurl'   => defaults($rr, 'addr', $rr['url']),
999                         'url'       => $url,
1000                         'network'   => ContactSelector::networkToName($rr['network'], $rr['url']),
1001                         'nick'      => $rr['nick'],
1002                 ];
1003         }
1004
1005         /**
1006          * @brief Gives a array with actions which can performed to a given contact
1007          *
1008          * This includes actions like e.g. 'block', 'hide', 'archive', 'delete' and others
1009          *
1010          * @param array $contact Data about the Contact
1011          * @return array with contact related actions
1012          */
1013         private static function getContactActions($contact)
1014         {
1015                 $poll_enabled = in_array($contact['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::OSTATUS, Protocol::FEED, Protocol::MAIL]);
1016                 $contact_actions = [];
1017
1018                 // Provide friend suggestion only for Friendica contacts
1019                 if ($contact['network'] === Protocol::DFRN) {
1020                         $contact_actions['suggest'] = [
1021                                 'label' => L10n::t('Suggest friends'),
1022                                 'url'   => 'fsuggest/' . $contact['id'],
1023                                 'title' => '',
1024                                 'sel'   => '',
1025                                 'id'    => 'suggest',
1026                         ];
1027                 }
1028
1029                 if ($poll_enabled) {
1030                         $contact_actions['update'] = [
1031                                 'label' => L10n::t('Update now'),
1032                                 'url'   => 'contact/' . $contact['id'] . '/update',
1033                                 'title' => '',
1034                                 'sel'   => '',
1035                                 'id'    => 'update',
1036                         ];
1037                 }
1038
1039                 $contact_actions['block'] = [
1040                         'label' => (intval($contact['blocked']) ? L10n::t('Unblock') : L10n::t('Block')),
1041                         'url'   => 'contact/' . $contact['id'] . '/block',
1042                         'title' => L10n::t('Toggle Blocked status'),
1043                         'sel'   => (intval($contact['blocked']) ? 'active' : ''),
1044                         'id'    => 'toggle-block',
1045                 ];
1046
1047                 $contact_actions['ignore'] = [
1048                         'label' => (intval($contact['readonly']) ? L10n::t('Unignore') : L10n::t('Ignore')),
1049                         'url'   => 'contact/' . $contact['id'] . '/ignore',
1050                         'title' => L10n::t('Toggle Ignored status'),
1051                         'sel'   => (intval($contact['readonly']) ? 'active' : ''),
1052                         'id'    => 'toggle-ignore',
1053                 ];
1054
1055                 if ($contact['uid'] != 0) {
1056                         $contact_actions['archive'] = [
1057                                 'label' => (intval($contact['archive']) ? L10n::t('Unarchive') : L10n::t('Archive')),
1058                                 'url'   => 'contact/' . $contact['id'] . '/archive',
1059                                 'title' => L10n::t('Toggle Archive status'),
1060                                 'sel'   => (intval($contact['archive']) ? 'active' : ''),
1061                                 'id'    => 'toggle-archive',
1062                         ];
1063
1064                         $contact_actions['delete'] = [
1065                                 'label' => L10n::t('Delete'),
1066                                 'url'   => 'contact/' . $contact['id'] . '/drop',
1067                                 'title' => L10n::t('Delete contact'),
1068                                 'sel'   => '',
1069                                 'id'    => 'delete',
1070                         ];
1071                 }
1072
1073                 return $contact_actions;
1074         }
1075 }