]> git.mxchange.org Git - friendica.git/blob - src/Module/Contact.php
a01a2c78568c1ef5643e28897b4cf4587242cd5d
[friendica.git] / src / Module / Contact.php
1 <?php
2 /**
3  * @copyright Copyright (C) 2010-2021, the Friendica project
4  *
5  * @license GNU AGPL version 3 or any later version
6  *
7  * This program is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU Affero General Public License as
9  * published by the Free Software Foundation, either version 3 of the
10  * License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU Affero General Public License for more details.
16  *
17  * You should have received a copy of the GNU Affero General Public License
18  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
19  *
20  */
21
22 namespace Friendica\Module;
23
24 use Friendica\BaseModule;
25 use Friendica\Content\ContactSelector;
26 use Friendica\Content\Nav;
27 use Friendica\Content\Pager;
28 use Friendica\Content\Text\BBCode;
29 use Friendica\Content\Widget;
30 use Friendica\Core\Hook;
31 use Friendica\Core\Protocol;
32 use Friendica\Core\Renderer;
33 use Friendica\Core\Theme;
34 use Friendica\Core\Worker;
35 use Friendica\Database\DBA;
36 use Friendica\DI;
37 use Friendica\Model;
38 use Friendica\Model\User;
39 use Friendica\Module\Security\Login;
40 use Friendica\Network\HTTPException\BadRequestException;
41 use Friendica\Network\HTTPException\NotFoundException;
42 use Friendica\Util\DateTimeFormat;
43 use Friendica\Util\Strings;
44
45 /**
46  *  Manages and show Contacts and their content
47  */
48 class Contact extends BaseModule
49 {
50         const TAB_CONVERSATIONS = 1;
51         const TAB_POSTS = 2;
52         const TAB_PROFILE = 3;
53         const TAB_CONTACTS = 4;
54         const TAB_ADVANCED = 5;
55
56         private static function batchActions()
57         {
58                 if (empty($_POST['contact_batch']) || !is_array($_POST['contact_batch'])) {
59                         return;
60                 }
61
62                 $redirectUrl = $_POST['redirect_url'] ?? 'contact';
63
64                 self::checkFormSecurityTokenRedirectOnError($redirectUrl, 'contact_batch_actions');
65
66                 $orig_records = Model\Contact::selectToArray(['id', 'uid'], ['id' => $_POST['contact_batch'], 'uid' => [0, local_user()], 'self' => false, 'deleted' => false]);
67
68                 $count_actions = 0;
69                 foreach ($orig_records as $orig_record) {
70                         $cdata = Model\Contact::getPublicAndUserContactID($orig_record['id'], local_user());
71                         if (empty($cdata)) {
72                                 continue;
73                         }
74
75                         if (!empty($_POST['contacts_batch_update']) && $cdata['user']) {
76                                 self::updateContactFromPoll($cdata['user']);
77                                 $count_actions++;
78                         }
79
80                         if (!empty($_POST['contacts_batch_block'])) {
81                                 self::toggleBlockContact($cdata['public']);
82                                 $count_actions++;
83                         }
84
85                         if (!empty($_POST['contacts_batch_ignore'])) {
86                                 self::toggleIgnoreContact($cdata['public']);
87                                 $count_actions++;
88                         }
89
90                         if (!empty($_POST['contacts_batch_drop']) && $cdata['user']
91                                 && self::dropContact($cdata['user'], local_user())
92                         ) {
93                                 $count_actions++;
94                         }
95                 }
96                 if ($count_actions > 0) {
97                         info(DI::l10n()->tt('%d contact edited.', '%d contacts edited.', $count_actions));
98                 }
99
100                 DI::baseUrl()->redirect($redirectUrl);
101         }
102
103         public static function post(array $parameters = [])
104         {
105                 if (!local_user()) {
106                         return;
107                 }
108
109                 // @TODO: Replace with parameter from router
110                 if (DI::args()->getArgv()[1] === 'batch') {
111                         self::batchActions();
112                         return;
113                 }
114
115                 // @TODO: Replace with parameter from router
116                 $contact_id = intval(DI::args()->getArgv()[1]);
117                 if (!$contact_id) {
118                         return;
119                 }
120
121                 if (!DBA::exists('contact', ['id' => $contact_id, 'uid' => local_user(), 'deleted' => false])) {
122                         notice(DI::l10n()->t('Could not access contact record.'));
123                         DI::baseUrl()->redirect('contact');
124                         return; // NOTREACHED
125                 }
126
127                 Hook::callAll('contact_edit_post', $_POST);
128
129                 $hidden = !empty($_POST['hidden']);
130
131                 $notify = !empty($_POST['notify']);
132
133                 $fetch_further_information = intval($_POST['fetch_further_information'] ?? 0);
134
135                 $remote_self = $_POST['remote_self'] ?? false;
136
137                 $ffi_keyword_denylist = Strings::escapeHtml(trim($_POST['ffi_keyword_denylist'] ?? ''));
138
139                 $priority = intval($_POST['poll'] ?? 0);
140                 if ($priority > 5 || $priority < 0) {
141                         $priority = 0;
142                 }
143
144                 $info = Strings::escapeHtml(trim($_POST['info'] ?? ''));
145
146                 $r = Model\Contact::update([
147                         'priority'   => $priority,
148                         'info'       => $info,
149                         'hidden'     => $hidden,
150                         'notify_new_posts' => $notify,
151                         'fetch_further_information' => $fetch_further_information,
152                         'remote_self' => $remote_self,
153                         'ffi_keyword_denylist'     => $ffi_keyword_denylist],
154                         ['id' => $contact_id, 'uid' => local_user()]
155                 );
156
157                 if (!DBA::isResult($r)) {
158                         notice(DI::l10n()->t('Failed to update contact record.'));
159                 }
160                 return;
161         }
162
163         /* contact actions */
164
165         /**
166          * @param int $contact_id Id of contact with uid != 0
167          * @throws NotFoundException
168          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
169          * @throws \ImagickException
170          */
171         private static function updateContactFromPoll(int $contact_id)
172         {
173                 $contact = DBA::selectFirst('contact', ['uid', 'url', 'network'], ['id' => $contact_id, 'uid' => local_user(), 'deleted' => false]);
174                 if (!DBA::isResult($contact)) {
175                         return;
176                 }
177
178                 if ($contact['network'] == Protocol::OSTATUS) {
179                         $result = Model\Contact::createFromProbeForUser($contact['uid'], $contact['url'], $contact['network']);
180
181                         if ($result['success']) {
182                                 Model\Contact::update(['subhub' => 1], ['id' => $contact_id]);
183                         }
184
185                         // pull feed and consume it, which should subscribe to the hub.
186                         Worker::add(PRIORITY_HIGH, 'OnePoll', $contact_id, 'force');
187                 } else {
188                         Worker::add(PRIORITY_HIGH, 'UpdateContact', $contact_id);
189                 }
190         }
191
192         /**
193          * @param int $contact_id Id of the contact with uid != 0
194          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
195          * @throws \ImagickException
196          */
197         private static function updateContactFromProbe(int $contact_id)
198         {
199                 $contact = DBA::selectFirst('contact', ['url'], ['id' => $contact_id, 'uid' => local_user(), 'deleted' => false]);
200                 if (!DBA::isResult($contact)) {
201                         return;
202                 }
203
204                 // Update the entry in the contact table
205                 Model\Contact::updateFromProbe($contact_id);
206         }
207
208         /**
209          * Toggles the blocked status of a contact identified by id.
210          *
211          * @param int $contact_id Id of the contact with uid = 0
212          * @throws \Exception
213          */
214         private static function toggleBlockContact(int $contact_id)
215         {
216                 $blocked = !Model\Contact\User::isBlocked($contact_id, local_user());
217                 Model\Contact\User::setBlocked($contact_id, local_user(), $blocked);
218         }
219
220         /**
221          * Toggles the ignored status of a contact identified by id.
222          *
223          * @param int $contact_id Id of the contact with uid = 0
224          * @throws \Exception
225          */
226         private static function toggleIgnoreContact(int $contact_id)
227         {
228                 $ignored = !Model\Contact\User::isIgnored($contact_id, local_user());
229                 Model\Contact\User::setIgnored($contact_id, local_user(), $ignored);
230         }
231
232         /**
233          * @param int $contact_id Id for contact with uid != 0
234          * @param int $uid        Id for user we want to drop the contact for
235          * @return bool
236          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
237          * @throws \ImagickException
238          */
239         private static function dropContact(int $contact_id, int $uid): bool
240         {
241                 $contact = Model\Contact::getContactForUser($contact_id, $uid);
242                 if (!DBA::isResult($contact)) {
243                         return false;
244                 }
245
246                 $owner = Model\User::getOwnerDataById($uid);
247                 if (!DBA::isResult($owner)) {
248                         return false;
249                 }
250
251                 Model\Contact::terminateFriendship($owner, $contact, true);
252                 Model\Contact::remove($contact['id']);
253
254                 return true;
255         }
256
257         public static function content(array $parameters = [], $update = 0)
258         {
259                 if (!local_user()) {
260                         return Login::form($_SERVER['REQUEST_URI']);
261                 }
262
263                 $a = DI::app();
264
265                 $search = Strings::escapeTags(trim($_GET['search'] ?? ''));
266                 $nets   = Strings::escapeTags(trim($_GET['nets']   ?? ''));
267                 $rel    = Strings::escapeTags(trim($_GET['rel']    ?? ''));
268                 $group  = Strings::escapeTags(trim($_GET['group']  ?? ''));
269
270                 $accounttype = $_GET['accounttype'] ?? '';
271                 $accounttypeid = User::getAccountTypeByString($accounttype);
272
273                 $page = DI::page();
274
275                 $page->registerFooterScript(Theme::getPathForFile('asset/typeahead.js/dist/typeahead.bundle.js'));
276                 $page->registerFooterScript(Theme::getPathForFile('js/friendica-tagsinput/friendica-tagsinput.js'));
277                 $page->registerStylesheet(Theme::getPathForFile('js/friendica-tagsinput/friendica-tagsinput.css'));
278                 $page->registerStylesheet(Theme::getPathForFile('js/friendica-tagsinput/friendica-tagsinput-typeahead.css'));
279
280                 $contact = null;
281                 // @TODO: Replace with parameter from router
282                 if (DI::args()->getArgc() == 2 && intval(DI::args()->getArgv()[1])
283                         || DI::args()->getArgc() == 3 && intval(DI::args()->getArgv()[1]) && in_array(DI::args()->getArgv()[2], ['posts', 'conversations'])
284                 ) {
285                         $contact_id = intval(DI::args()->getArgv()[1]);
286
287                         // Ensure to use the user contact when the public contact was provided
288                         $data = Model\Contact::getPublicAndUserContactID($contact_id, local_user());
289                         if (!empty($data['user']) && ($contact_id == $data['public'])) {
290                                 $contact_id = $data['user'];
291                         }
292
293                         if (!empty($data)) {
294                                 $contact = DBA::selectFirst('contact', [], [
295                                         'id'      => $contact_id,
296                                         'uid'     => [0, local_user()],
297                                         'deleted' => false
298                                 ]);
299
300                                 // Don't display contacts that are about to be deleted
301                                 if (DBA::isResult($contact) && !empty($contact['network']) && $contact['network'] == Protocol::PHANTOM) {
302                                         $contact = false;
303                                 }
304                         }
305                 }
306
307                 if (DBA::isResult($contact)) {
308                         if ($contact['self']) {
309                                 // @TODO: Replace with parameter from router
310                                 if ((DI::args()->getArgc() == 3) && intval(DI::args()->getArgv()[1]) && in_array(DI::args()->getArgv()[2], ['posts', 'conversations'])) {
311                                         DI::baseUrl()->redirect('profile/' . $contact['nick']);
312                                 } else {
313                                         DI::baseUrl()->redirect('profile/' . $contact['nick'] . '/profile');
314                                 }
315                         }
316
317                         $vcard_widget = Widget\VCard::getHTML($contact);
318
319                         $findpeople_widget = '';
320                         $follow_widget = '';
321                         $account_widget = '';
322                         $networks_widget = '';
323                         $rel_widget = '';
324
325                         if ($contact['uid'] != 0) {
326                                 $groups_widget = Model\Group::sidebarWidget('contact', 'group', 'full', 'everyone', $contact_id);
327                         } else {
328                                 $groups_widget = '';
329                         }
330                 } else {
331                         $vcard_widget = '';
332                         $findpeople_widget = Widget::findPeople();
333                         if (isset($_GET['add'])) {
334                                 $follow_widget = Widget::follow($_GET['add']);
335                         } else {
336                                 $follow_widget = Widget::follow();
337                         }
338
339                         $account_widget = Widget::accounttypes($_SERVER['REQUEST_URI'], $accounttype);
340                         $networks_widget = Widget::networks($_SERVER['REQUEST_URI'], $nets);
341                         $rel_widget = Widget::contactRels($_SERVER['REQUEST_URI'], $rel);
342                         $groups_widget = Widget::groups($_SERVER['REQUEST_URI'], $group);
343                 }
344
345                 DI::page()['aside'] .= $vcard_widget . $findpeople_widget . $follow_widget . $account_widget . $groups_widget . $networks_widget . $rel_widget;
346
347                 $tpl = Renderer::getMarkupTemplate('contacts-head.tpl');
348                 DI::page()['htmlhead'] .= Renderer::replaceMacros($tpl, [
349                         '$baseurl' => DI::baseUrl()->get(true),
350                 ]);
351
352                 $o = '';
353                 Nav::setSelected('contact');
354
355                 if (!local_user()) {
356                         notice(DI::l10n()->t('Permission denied.'));
357                         return Login::form();
358                 }
359
360                 if (DI::args()->getArgc() == 3) {
361                         $contact_id = intval(DI::args()->getArgv()[1]);
362                         if (!$contact_id) {
363                                 throw new BadRequestException();
364                         }
365
366                         // @TODO: Replace with parameter from router
367                         $cmd = DI::args()->getArgv()[2];
368
369                         $orig_record = DBA::selectFirst('contact', [], ['id' => $contact_id, 'uid' => [0, local_user()], 'self' => false, 'deleted' => false]);
370                         if (!DBA::isResult($orig_record)) {
371                                 throw new NotFoundException(DI::l10n()->t('Contact not found'));
372                         }
373
374                         if ($cmd === 'posts') {
375                                 return self::getPostsHTML($a, $contact_id);
376                         }
377
378                         if ($cmd === 'conversations') {
379                                 return self::getConversationsHMTL($a, $contact_id, $update);
380                         }
381
382                         self::checkFormSecurityTokenRedirectOnError('contact/' . $contact_id, 'contact_action', 't');
383
384                         $cdata = Model\Contact::getPublicAndUserContactID($orig_record['id'], local_user());
385                         if (empty($cdata)) {
386                                 throw new NotFoundException(DI::l10n()->t('Contact not found'));
387                         }
388
389                         if ($cmd === 'update' && $cdata['user']) {
390                                 self::updateContactFromPoll($cdata['user']);
391                                 DI::baseUrl()->redirect('contact/' . $cdata['public']);
392                                 // NOTREACHED
393                         }
394
395                         if ($cmd === 'updateprofile' && $cdata['user']) {
396                                 self::updateContactFromProbe($cdata['user']);
397                                 DI::baseUrl()->redirect('contact/' . $cdata['public']);
398                                 // NOTREACHED
399                         }
400
401                         if ($cmd === 'block') {
402                                 if (public_contact() === $cdata['public']) {
403                                         throw new BadRequestException(DI::l10n()->t('You can\'t block yourself'));
404                                 }
405
406                                 self::toggleBlockContact($cdata['public']);
407
408                                 $blocked = Model\Contact\User::isBlocked($contact_id, local_user());
409                                 info(($blocked ? DI::l10n()->t('Contact has been blocked') : DI::l10n()->t('Contact has been unblocked')));
410
411                                 DI::baseUrl()->redirect('contact/' . $cdata['public']);
412                                 // NOTREACHED
413                         }
414
415                         if ($cmd === 'ignore') {
416                                 if (public_contact() === $cdata['public']) {
417                                         throw new BadRequestException(DI::l10n()->t('You can\'t ignore yourself'));
418                                 }
419
420                                 self::toggleIgnoreContact($cdata['public']);
421
422                                 $ignored = Model\Contact\User::isIgnored($cdata['public'], local_user());
423                                 info(($ignored ? DI::l10n()->t('Contact has been ignored') : DI::l10n()->t('Contact has been unignored')));
424
425                                 DI::baseUrl()->redirect('contact/' . $cdata['public']);
426                                 // NOTREACHED
427                         }
428
429                         if ($cmd === 'drop' && $cdata['user']) {
430                                 // Check if we should do HTML-based delete confirmation
431                                 if (!empty($_REQUEST['confirm'])) {
432                                         DI::page()['aside'] = '';
433
434                                         return Renderer::replaceMacros(Renderer::getMarkupTemplate('contact_drop_confirm.tpl'), [
435                                                 '$l10n' => [
436                                                         'header' => DI::l10n()->t('Drop contact'),
437                                                         'message' => DI::l10n()->t('Do you really want to delete this contact?'),
438                                                         'confirm' => DI::l10n()->t('Yes'),
439                                                         'cancel' => DI::l10n()->t('Cancel'),
440                                                 ],
441                                                 '$contact' => self::getContactTemplateVars($orig_record),
442                                                 '$method' => 'get',
443                                                 '$confirm_url' => DI::args()->getCommand(),
444                                                 '$confirm_name' => 't',
445                                                 '$confirm_value' => BaseModule::getFormSecurityToken('contact_action'),
446                                         ]);
447                                 }
448                                 // Now check how the user responded to the confirmation query
449                                 if (!empty($_REQUEST['canceled'])) {
450                                         DI::baseUrl()->redirect('contact');
451                                 }
452
453                                 if (self::dropContact($cdata['user'], local_user())) {
454                                         info(DI::l10n()->t('Contact has been removed.'));
455                                 }
456
457                                 DI::baseUrl()->redirect('contact');
458                                 // NOTREACHED
459                         }
460                 }
461
462                 $_SESSION['return_path'] = DI::args()->getQueryString();
463
464                 if (!empty($contact)) {
465                         DI::page()['htmlhead'] .= Renderer::replaceMacros(Renderer::getMarkupTemplate('contact_head.tpl'), [
466                                 '$baseurl' => DI::baseUrl()->get(true),
467                         ]);
468
469                         $contact['blocked']  = Model\Contact\User::isBlocked($contact['id'], local_user());
470                         $contact['readonly'] = Model\Contact\User::isIgnored($contact['id'], local_user());
471
472                         $relation_text = '';
473                         switch ($contact['rel']) {
474                                 case Model\Contact::FRIEND:
475                                         $relation_text = DI::l10n()->t('You are mutual friends with %s');
476                                         break;
477
478                                 case Model\Contact::FOLLOWER;
479                                         $relation_text = DI::l10n()->t('You are sharing with %s');
480                                         break;
481
482                                 case Model\Contact::SHARING;
483                                         $relation_text = DI::l10n()->t('%s is sharing with you');
484                                         break;
485
486                                 default:
487                                         break;
488                         }
489
490                         if ($contact['uid'] == 0) {
491                                 $relation_text = '';
492                         }
493
494                         if (!in_array($contact['network'], array_merge(Protocol::FEDERATED, [Protocol::TWITTER]))) {
495                                 $relation_text = '';
496                         }
497
498                         $relation_text = sprintf($relation_text, $contact['name']);
499
500                         $url = Model\Contact::magicLinkByContact($contact);
501                         if (strpos($url, 'redir/') === 0) {
502                                 $sparkle = ' class="sparkle" ';
503                         } else {
504                                 $sparkle = '';
505                         }
506
507                         $insecure = DI::l10n()->t('Private communications are not available for this contact.');
508
509                         $last_update = (($contact['last-update'] <= DBA::NULL_DATETIME) ? DI::l10n()->t('Never') : DateTimeFormat::local($contact['last-update'], 'D, j M Y, g:i A'));
510
511                         if ($contact['last-update'] > DBA::NULL_DATETIME) {
512                                 $last_update .= ' ' . ($contact['failed'] ? DI::l10n()->t('(Update was not successful)') : DI::l10n()->t('(Update was successful)'));
513                         }
514                         $lblsuggest = (($contact['network'] === Protocol::DFRN) ? DI::l10n()->t('Suggest friends') : '');
515
516                         $poll_enabled = in_array($contact['network'], [Protocol::DFRN, Protocol::OSTATUS, Protocol::FEED, Protocol::MAIL]);
517
518                         $nettype = DI::l10n()->t('Network type: %s', ContactSelector::networkToName($contact['network'], $contact['url'], $contact['protocol'], $contact['gsid']));
519
520                         // tabs
521                         $tab_str = self::getTabsHTML($contact, self::TAB_PROFILE);
522
523                         $lost_contact = (($contact['archive'] && $contact['term-date'] > DBA::NULL_DATETIME && $contact['term-date'] < DateTimeFormat::utcNow()) ? DI::l10n()->t('Communications lost with this contact!') : '');
524
525                         $fetch_further_information = null;
526                         if ($contact['network'] == Protocol::FEED) {
527                                 $fetch_further_information = [
528                                         'fetch_further_information',
529                                         DI::l10n()->t('Fetch further information for feeds'),
530                                         $contact['fetch_further_information'],
531                                         DI::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.'),
532                                         [
533                                                 '0' => DI::l10n()->t('Disabled'),
534                                                 '1' => DI::l10n()->t('Fetch information'),
535                                                 '3' => DI::l10n()->t('Fetch keywords'),
536                                                 '2' => DI::l10n()->t('Fetch information and keywords')
537                                         ]
538                                 ];
539                         }
540
541                         // Disable remote self for everything except feeds.
542                         // There is an issue when you repeat an item from maybe twitter and you got comments from friendica and twitter
543                         // Problem is, you couldn't reply to both networks.
544                         $allow_remote_self = in_array($contact['network'], [Protocol::ACTIVITYPUB, Protocol::FEED, Protocol::DFRN, Protocol::DIASPORA, Protocol::TWITTER])
545                                 && DI::config()->get('system', 'allow_users_remote_self');
546
547                         if ($contact['network'] == Protocol::FEED) {
548                                 $remote_self_options = [Model\Contact::MIRROR_DEACTIVATED => DI::l10n()->t('No mirroring'),
549                                         Model\Contact::MIRROR_FORWARDED => DI::l10n()->t('Mirror as forwarded posting'),
550                                         Model\Contact::MIRROR_OWN_POST => DI::l10n()->t('Mirror as my own posting')];
551                         } elseif (in_array($contact['network'], [Protocol::ACTIVITYPUB])) {
552                                 $remote_self_options = [Model\Contact::MIRROR_DEACTIVATED => DI::l10n()->t('No mirroring'),
553                                 Model\Contact::MIRROR_NATIVE_RESHARE => DI::l10n()->t('Native reshare')];
554                         } elseif (in_array($contact['network'], [Protocol::DFRN])) {
555                                 $remote_self_options = [Model\Contact::MIRROR_DEACTIVATED => DI::l10n()->t('No mirroring'),
556                                 Model\Contact::MIRROR_OWN_POST => DI::l10n()->t('Mirror as my own posting'),
557                                 Model\Contact::MIRROR_NATIVE_RESHARE => DI::l10n()->t('Native reshare')];
558                         } else {
559                                 $remote_self_options = [Model\Contact::MIRROR_DEACTIVATED => DI::l10n()->t('No mirroring'),
560                                         Model\Contact::MIRROR_OWN_POST => DI::l10n()->t('Mirror as my own posting')];
561                         }
562
563                         $poll_interval = null;
564                         if ((($contact['network'] == Protocol::FEED) && !DI::config()->get('system', 'adjust_poll_frequency')) || ($contact['network'] == Protocol::MAIL)) {
565                                 $poll_interval = ContactSelector::pollInterval($contact['priority'], !$poll_enabled);
566                         }
567
568                         // Load contactact related actions like hide, suggest, delete and others
569                         $contact_actions = self::getContactActions($contact);
570
571                         if ($contact['uid'] != 0) {
572                                 $lbl_info1 = DI::l10n()->t('Contact Information / Notes');
573                                 $contact_settings_label = DI::l10n()->t('Contact Settings');
574                         } else {
575                                 $lbl_info1 = null;
576                                 $contact_settings_label = null;
577                         }
578
579                         $tpl = Renderer::getMarkupTemplate('contact_edit.tpl');
580                         $o .= Renderer::replaceMacros($tpl, [
581                                 '$header'         => DI::l10n()->t('Contact'),
582                                 '$tab_str'        => $tab_str,
583                                 '$submit'         => DI::l10n()->t('Submit'),
584                                 '$lbl_info1'      => $lbl_info1,
585                                 '$lbl_info2'      => DI::l10n()->t('Their personal note'),
586                                 '$reason'         => trim(Strings::escapeTags($contact['reason'])),
587                                 '$infedit'        => DI::l10n()->t('Edit contact notes'),
588                                 '$common_link'    => 'contact/' . $contact['id'] . '/contacts/common',
589                                 '$relation_text'  => $relation_text,
590                                 '$visit'          => DI::l10n()->t('Visit %s\'s profile [%s]', $contact['name'], $contact['url']),
591                                 '$blockunblock'   => DI::l10n()->t('Block/Unblock contact'),
592                                 '$ignorecont'     => DI::l10n()->t('Ignore contact'),
593                                 '$lblrecent'      => DI::l10n()->t('View conversations'),
594                                 '$lblsuggest'     => $lblsuggest,
595                                 '$nettype'        => $nettype,
596                                 '$poll_interval'  => $poll_interval,
597                                 '$poll_enabled'   => $poll_enabled,
598                                 '$lastupdtext'    => DI::l10n()->t('Last update:'),
599                                 '$lost_contact'   => $lost_contact,
600                                 '$updpub'         => DI::l10n()->t('Update public posts'),
601                                 '$last_update'    => $last_update,
602                                 '$udnow'          => DI::l10n()->t('Update now'),
603                                 '$contact_id'     => $contact['id'],
604                                 '$block_text'     => ($contact['blocked'] ? DI::l10n()->t('Unblock') : DI::l10n()->t('Block')),
605                                 '$ignore_text'    => ($contact['readonly'] ? DI::l10n()->t('Unignore') : DI::l10n()->t('Ignore')),
606                                 '$insecure'       => (in_array($contact['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::MAIL, Protocol::DIASPORA]) ? '' : $insecure),
607                                 '$info'           => $contact['info'],
608                                 '$cinfo'          => ['info', '', $contact['info'], ''],
609                                 '$blocked'        => ($contact['blocked'] ? DI::l10n()->t('Currently blocked') : ''),
610                                 '$ignored'        => ($contact['readonly'] ? DI::l10n()->t('Currently ignored') : ''),
611                                 '$archived'       => ($contact['archive'] ? DI::l10n()->t('Currently archived') : ''),
612                                 '$pending'        => ($contact['pending'] ? DI::l10n()->t('Awaiting connection acknowledge') : ''),
613                                 '$hidden'         => ['hidden', DI::l10n()->t('Hide this contact from others'), ($contact['hidden'] == 1), DI::l10n()->t('Replies/likes to your public posts <strong>may</strong> still be visible')],
614                                 '$notify'         => ['notify', DI::l10n()->t('Notification for new posts'), ($contact['notify_new_posts'] == 1), DI::l10n()->t('Send a notification of every new post of this contact')],
615                                 '$fetch_further_information' => $fetch_further_information,
616                                 '$ffi_keyword_denylist' => ['ffi_keyword_denylist', DI::l10n()->t('Keyword Deny List'), $contact['ffi_keyword_denylist'], DI::l10n()->t('Comma separated list of keywords that should not be converted to hashtags, when "Fetch information and keywords" is selected')],
617                                 '$photo'          => Model\Contact::getPhoto($contact),
618                                 '$name'           => $contact['name'],
619                                 '$sparkle'        => $sparkle,
620                                 '$url'            => $url,
621                                 '$profileurllabel'=> DI::l10n()->t('Profile URL'),
622                                 '$profileurl'     => $contact['url'],
623                                 '$account_type'   => Model\Contact::getAccountType($contact),
624                                 '$location'       => BBCode::convertForUriId($contact['uri-id'] ?? 0, $contact['location']),
625                                 '$location_label' => DI::l10n()->t('Location:'),
626                                 '$xmpp'           => BBCode::convertForUriId($contact['uri-id'] ?? 0, $contact['xmpp']),
627                                 '$xmpp_label'     => DI::l10n()->t('XMPP:'),
628                                 '$matrix'         => BBCode::convertForUriId($contact['uri-id'] ?? 0, $contact['matrix']),
629                                 '$matrix_label'   => DI::l10n()->t('Matrix:'),
630                                 '$about'          => BBCode::convertForUriId($contact['uri-id'] ?? 0, $contact['about'], BBCode::EXTERNAL),
631                                 '$about_label'    => DI::l10n()->t('About:'),
632                                 '$keywords'       => $contact['keywords'],
633                                 '$keywords_label' => DI::l10n()->t('Tags:'),
634                                 '$contact_action_button' => DI::l10n()->t('Actions'),
635                                 '$contact_actions'=> $contact_actions,
636                                 '$contact_status' => DI::l10n()->t('Status'),
637                                 '$contact_settings_label' => $contact_settings_label,
638                                 '$contact_profile_label' => DI::l10n()->t('Profile'),
639                                 '$allow_remote_self' => $allow_remote_self,
640                                 '$remote_self'       => ['remote_self',
641                                         DI::l10n()->t('Mirror postings from this contact'),
642                                         $contact['remote_self'],
643                                         DI::l10n()->t('Mark this contact as remote_self, this will cause friendica to repost new entries from this contact.'),
644                                         $remote_self_options
645                                 ],
646                         ]);
647
648                         $arr = ['contact' => $contact, 'output' => $o];
649
650                         Hook::callAll('contact_edit', $arr);
651
652                         return $arr['output'];
653                 }
654
655                 $sql_values = [local_user()];
656
657                 // @TODO: Replace with parameter from router
658                 $type = DI::args()->getArgv()[1] ?? '';
659
660                 switch ($type) {
661                         case 'blocked':
662                                 $sql_extra = " AND EXISTS(SELECT `id` from `user-contact` WHERE `contact`.`id` = `user-contact`.`cid` and `user-contact`.`uid` = ? and `user-contact`.`blocked`)";
663                                 // This makes the query look for contact.uid = 0
664                                 array_unshift($sql_values, 0);
665                                 break;
666                         case 'hidden':
667                                 $sql_extra = " AND `hidden` AND NOT `blocked` AND NOT `pending`";
668                                 break;
669                         case 'ignored':
670                                 $sql_extra = " AND EXISTS(SELECT `id` from `user-contact` WHERE `contact`.`id` = `user-contact`.`cid` and `user-contact`.`uid` = ? and `user-contact`.`ignored`)";
671                                 // This makes the query look for contact.uid = 0
672                                 array_unshift($sql_values, 0);
673                                 break;
674                         case 'archived':
675                                 $sql_extra = " AND `archive` AND NOT `blocked` AND NOT `pending`";
676                                 break;
677                         case 'pending':
678                                 $sql_extra = " AND `pending` AND NOT `archive` AND NOT `failed` AND ((`rel` = ?)
679                                         OR EXISTS (SELECT `id` FROM `intro` WHERE `contact-id` = `contact`.`id` AND NOT `ignore`))";
680                                 $sql_values[] = Model\Contact::SHARING;
681                                 break;
682                         default:
683                                 $sql_extra = " AND NOT `archive` AND NOT `blocked` AND NOT `pending`";
684                                 break;
685                 }
686
687                 if (isset($accounttypeid)) {
688                         $sql_extra .= " AND `contact-type` = ?";
689                         $sql_values[] = $accounttypeid;
690                 }
691
692                 $searching = false;
693                 $search_hdr = null;
694                 if ($search) {
695                         $searching = true;
696                         $search_hdr = $search;
697                         $search_txt = preg_quote($search);
698                         $sql_extra .= " AND (name REGEXP ? OR url REGEXP ? OR nick REGEXP ?)";
699                         $sql_values[] = $search_txt;
700                         $sql_values[] = $search_txt;
701                         $sql_values[] = $search_txt;
702                 }
703
704                 if ($nets) {
705                         $sql_extra .= " AND network = ? ";
706                         $sql_values[] = $nets;
707                 }
708
709                 switch ($rel) {
710                         case 'followers':
711                                 $sql_extra .= " AND `rel` IN (?, ?)";
712                                 $sql_values[] = Model\Contact::FOLLOWER;
713                                 $sql_values[] = Model\Contact::FRIEND;
714                                 break;
715                         case 'following':
716                                 $sql_extra .= " AND `rel` IN (?, ?)";
717                                 $sql_values[] = Model\Contact::SHARING;
718                                 $sql_values[] = Model\Contact::FRIEND;
719                                 break;
720                         case 'mutuals':
721                                 $sql_extra .= " AND `rel` = ?";
722                                 $sql_values[] = Model\Contact::FRIEND;
723                                 break;
724                 }
725
726                 if ($group) {
727                         $sql_extra = " AND EXISTS(SELECT `id` FROM `group_member` WHERE `gid` = ? AND `contact`.`id` = `contact-id`)";
728                         $sql_values[] = $group;
729                 }
730
731                 $total = 0;
732                 $stmt = DBA::p("SELECT COUNT(*) AS `total`
733                         FROM `contact`
734                         WHERE `uid` = ?
735                         AND `self` = 0
736                         AND NOT `deleted`
737                         $sql_extra
738                         " . Widget::unavailableNetworks(),
739                         $sql_values
740                 );
741                 if (DBA::isResult($stmt)) {
742                         $total = DBA::fetch($stmt)['total'];
743                 }
744                 DBA::close($stmt);
745
746                 $pager = new Pager(DI::l10n(), DI::args()->getQueryString());
747
748                 $sql_values[] = $pager->getStart();
749                 $sql_values[] = $pager->getItemsPerPage();
750
751                 $contacts = [];
752
753                 $stmt = DBA::p("SELECT *
754                         FROM `contact`
755                         WHERE `uid` = ?
756                         AND `self` = 0
757                         AND NOT `deleted`
758                         $sql_extra
759                         ORDER BY `name` ASC
760                         LIMIT ?, ?",
761                         $sql_values
762                 );
763                 while ($contact = DBA::fetch($stmt)) {
764                         $contact['blocked'] = Model\Contact\User::isBlocked($contact['id'], local_user());
765                         $contact['readonly'] = Model\Contact\User::isIgnored($contact['id'], local_user());
766                         $contacts[] = self::getContactTemplateVars($contact);
767                 }
768                 DBA::close($stmt);
769
770                 $tabs = [
771                         [
772                                 'label' => DI::l10n()->t('All Contacts'),
773                                 'url'   => 'contact',
774                                 'sel'   => !$type ? 'active' : '',
775                                 'title' => DI::l10n()->t('Show all contacts'),
776                                 'id'    => 'showall-tab',
777                                 'accesskey' => 'l',
778                         ],
779                         [
780                                 'label' => DI::l10n()->t('Pending'),
781                                 'url'   => 'contact/pending',
782                                 'sel'   => $type == 'pending' ? 'active' : '',
783                                 'title' => DI::l10n()->t('Only show pending contacts'),
784                                 'id'    => 'showpending-tab',
785                                 'accesskey' => 'p',
786                         ],
787                         [
788                                 'label' => DI::l10n()->t('Blocked'),
789                                 'url'   => 'contact/blocked',
790                                 'sel'   => $type == 'blocked' ? 'active' : '',
791                                 'title' => DI::l10n()->t('Only show blocked contacts'),
792                                 'id'    => 'showblocked-tab',
793                                 'accesskey' => 'b',
794                         ],
795                         [
796                                 'label' => DI::l10n()->t('Ignored'),
797                                 'url'   => 'contact/ignored',
798                                 'sel'   => $type == 'ignored' ? 'active' : '',
799                                 'title' => DI::l10n()->t('Only show ignored contacts'),
800                                 'id'    => 'showignored-tab',
801                                 'accesskey' => 'i',
802                         ],
803                         [
804                                 'label' => DI::l10n()->t('Archived'),
805                                 'url'   => 'contact/archived',
806                                 'sel'   => $type == 'archived' ? 'active' : '',
807                                 'title' => DI::l10n()->t('Only show archived contacts'),
808                                 'id'    => 'showarchived-tab',
809                                 'accesskey' => 'y',
810                         ],
811                         [
812                                 'label' => DI::l10n()->t('Hidden'),
813                                 'url'   => 'contact/hidden',
814                                 'sel'   => $type == 'hidden' ? 'active' : '',
815                                 'title' => DI::l10n()->t('Only show hidden contacts'),
816                                 'id'    => 'showhidden-tab',
817                                 'accesskey' => 'h',
818                         ],
819                         [
820                                 'label' => DI::l10n()->t('Groups'),
821                                 'url'   => 'group',
822                                 'sel'   => '',
823                                 'title' => DI::l10n()->t('Organize your contact groups'),
824                                 'id'    => 'contactgroups-tab',
825                                 'accesskey' => 'e',
826                         ],
827                 ];
828
829                 $tabs_tpl = Renderer::getMarkupTemplate('common_tabs.tpl');
830                 $tabs_html = Renderer::replaceMacros($tabs_tpl, ['$tabs' => $tabs]);
831
832                 switch ($rel) {
833                         case 'followers': $header = DI::l10n()->t('Followers'); break;
834                         case 'following': $header = DI::l10n()->t('Following'); break;
835                         case 'mutuals':   $header = DI::l10n()->t('Mutual friends'); break;
836                         default:          $header = DI::l10n()->t('Contacts');
837                 }
838
839                 switch ($type) {
840                         case 'pending':  $header .= ' - ' . DI::l10n()->t('Pending'); break;
841                         case 'blocked':  $header .= ' - ' . DI::l10n()->t('Blocked'); break;
842                         case 'hidden':   $header .= ' - ' . DI::l10n()->t('Hidden'); break;
843                         case 'ignored':  $header .= ' - ' . DI::l10n()->t('Ignored'); break;
844                         case 'archived': $header .= ' - ' . DI::l10n()->t('Archived'); break;
845                 }
846
847                 $header .= $nets ? ' - ' . ContactSelector::networkToName($nets) : '';
848
849                 $tpl = Renderer::getMarkupTemplate('contacts-template.tpl');
850                 $o .= Renderer::replaceMacros($tpl, [
851                         '$header'     => $header,
852                         '$tabs'       => $tabs_html,
853                         '$total'      => $total,
854                         '$search'     => $search_hdr,
855                         '$desc'       => DI::l10n()->t('Search your contacts'),
856                         '$finding'    => $searching ? DI::l10n()->t('Results for: %s', $search) : '',
857                         '$submit'     => DI::l10n()->t('Find'),
858                         '$cmd'        => DI::args()->getCommand(),
859                         '$contacts'   => $contacts,
860                         '$form_security_token'  => BaseModule::getFormSecurityToken('contact_batch_actions'),
861                         '$contact_drop_confirm' => DI::l10n()->t('Do you really want to delete this contact?'),
862                         'multiselect' => 1,
863                         '$batch_actions' => [
864                                 'contacts_batch_update'  => DI::l10n()->t('Update'),
865                                 'contacts_batch_block'   => DI::l10n()->t('Block') . '/' . DI::l10n()->t('Unblock'),
866                                 'contacts_batch_ignore'  => DI::l10n()->t('Ignore') . '/' . DI::l10n()->t('Unignore'),
867                                 'contacts_batch_drop'    => DI::l10n()->t('Delete'),
868                         ],
869                         '$h_batch_actions' => DI::l10n()->t('Batch Actions'),
870                         '$paginate'   => $pager->renderFull($total),
871                 ]);
872
873                 return $o;
874         }
875
876         /**
877          * List of pages for the Contact TabBar
878          *
879          * Available Pages are 'Status', 'Profile', 'Contacts' and 'Common Friends'
880          *
881          * @param array $contact    The contact array
882          * @param int   $active_tab 1 if tab should be marked as active
883          *
884          * @return string HTML string of the contact page tabs buttons.
885          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
886          * @throws \ImagickException
887          */
888         public static function getTabsHTML(array $contact, int $active_tab)
889         {
890                 $cid = $pcid = $contact['id'];
891                 $data = Model\Contact::getPublicAndUserContactID($contact['id'], local_user());
892                 if (!empty($data['user']) && ($contact['id'] == $data['public'])) {
893                         $cid = $data['user'];
894                 } elseif (!empty($data['public'])) {
895                         $pcid = $data['public'];
896                 }
897
898                 // tabs
899                 $tabs = [
900                         [
901                                 'label' => DI::l10n()->t('Status'),
902                                 'url'   => 'contact/' . $pcid . '/conversations',
903                                 'sel'   => (($active_tab == self::TAB_CONVERSATIONS) ? 'active' : ''),
904                                 'title' => DI::l10n()->t('Conversations started by this contact'),
905                                 'id'    => 'status-tab',
906                                 'accesskey' => 'm',
907                         ],
908                         [
909                                 'label' => DI::l10n()->t('Posts and Comments'),
910                                 'url'   => 'contact/' . $pcid . '/posts',
911                                 'sel'   => (($active_tab == self::TAB_POSTS) ? 'active' : ''),
912                                 'title' => DI::l10n()->t('Status Messages and Posts'),
913                                 'id'    => 'posts-tab',
914                                 'accesskey' => 'p',
915                         ],
916                         [
917                                 'label' => DI::l10n()->t('Profile'),
918                                 'url'   => 'contact/' . $cid,
919                                 'sel'   => (($active_tab == self::TAB_PROFILE) ? 'active' : ''),
920                                 'title' => DI::l10n()->t('Profile Details'),
921                                 'id'    => 'profile-tab',
922                                 'accesskey' => 'o',
923                         ],
924                         ['label' => DI::l10n()->t('Contacts'),
925                                 'url'   => 'contact/' . $pcid . '/contacts',
926                                 'sel'   => (($active_tab == self::TAB_CONTACTS) ? 'active' : ''),
927                                 'title' => DI::l10n()->t('View all known contacts'),
928                                 'id'    => 'contacts-tab',
929                                 'accesskey' => 't'
930                         ],
931                 ];
932
933                 if (!empty($contact['network']) && in_array($contact['network'], [Protocol::FEED, Protocol::MAIL]) && ($cid != $pcid)) {
934                         $tabs[] = ['label' => DI::l10n()->t('Advanced'),
935                                 'url'   => 'contact/' . $cid . '/advanced/',
936                                 'sel'   => (($active_tab == self::TAB_ADVANCED) ? 'active' : ''),
937                                 'title' => DI::l10n()->t('Advanced Contact Settings'),
938                                 'id'    => 'advanced-tab',
939                                 'accesskey' => 'r'
940                         ];
941                 }
942
943                 $tab_tpl = Renderer::getMarkupTemplate('common_tabs.tpl');
944                 $tab_str = Renderer::replaceMacros($tab_tpl, ['$tabs' => $tabs]);
945
946                 return $tab_str;
947         }
948
949         public static function getConversationsHMTL($a, $contact_id, $update, $parent = 0)
950         {
951                 $o = '';
952
953                 if (!$update) {
954                         // We need the editor here to be able to reshare an item.
955                         if (local_user()) {
956                                 $o = DI::conversation()->statusEditor([], 0, true);
957                         }
958                 }
959
960                 $contact = DBA::selectFirst('contact', ['uid', 'url', 'id'], ['id' => $contact_id, 'deleted' => false]);
961
962                 if (!$update) {
963                         $o .= self::getTabsHTML($contact, self::TAB_CONVERSATIONS);
964                 }
965
966                 if (DBA::isResult($contact)) {
967                         if (!$update) {
968                                 $profiledata = Model\Contact::getByURLForUser($contact['url'], local_user());
969                                 DI::page()['aside'] = Widget\VCard::getHTML($profiledata);
970                         } else {
971                                 DI::page()['aside'] = '';
972                         }
973
974                         if ($contact['uid'] == 0) {
975                                 $o .= Model\Contact::getPostsFromId($contact['id'], true, $update, $parent);
976                         } else {
977                                 $o .= Model\Contact::getPostsFromUrl($contact['url'], true, $update, $parent);
978                         }
979                 }
980
981                 return $o;
982         }
983
984         private static function getPostsHTML($a, $contact_id)
985         {
986                 $contact = DBA::selectFirst('contact', ['uid', 'url', 'id'], ['id' => $contact_id, 'deleted' => false]);
987
988                 $o = self::getTabsHTML($contact, self::TAB_POSTS);
989
990                 if (DBA::isResult($contact)) {
991                         $profiledata = Model\Contact::getByURLForUser($contact['url'], local_user());
992
993                         if (local_user() && in_array($profiledata['network'], Protocol::FEDERATED)) {
994                                 $profiledata['remoteconnect'] = DI::baseUrl() . '/follow?url=' . urlencode($profiledata['url']);
995                         }
996
997                         DI::page()['aside'] = Widget\VCard::getHTML($profiledata);
998
999                         if ($contact['uid'] == 0) {
1000                                 $o .= Model\Contact::getPostsFromId($contact['id']);
1001                         } else {
1002                                 $o .= Model\Contact::getPostsFromUrl($contact['url']);
1003                         }
1004                 }
1005
1006                 return $o;
1007         }
1008
1009         /**
1010          * Return the fields for the contact template
1011          *
1012          * @param array $contact Contact array
1013          * @return array Template fields
1014          */
1015         public static function getContactTemplateVars(array $contact)
1016         {
1017                 $alt_text = '';
1018
1019                 if (!empty($contact['url']) && isset($contact['uid']) && ($contact['uid'] == 0) && local_user()) {
1020                         $personal = Model\Contact::getByURL($contact['url'], false, ['uid', 'rel', 'self'], local_user());
1021                         if (!empty($personal)) {
1022                                 $contact['uid'] = $personal['uid'];
1023                                 $contact['rel'] = $personal['rel'];
1024                                 $contact['self'] = $personal['self'];
1025                         }
1026                 }
1027
1028                 if (!empty($contact['uid']) && !empty($contact['rel']) && local_user() == $contact['uid']) {
1029                         switch ($contact['rel']) {
1030                                 case Model\Contact::FRIEND:
1031                                         $alt_text = DI::l10n()->t('Mutual Friendship');
1032                                         break;
1033
1034                                 case Model\Contact::FOLLOWER;
1035                                         $alt_text = DI::l10n()->t('is a fan of yours');
1036                                         break;
1037
1038                                 case Model\Contact::SHARING;
1039                                         $alt_text = DI::l10n()->t('you are a fan of');
1040                                         break;
1041
1042                                 default:
1043                                         break;
1044                         }
1045                 }
1046
1047                 $url = Model\Contact::magicLinkByContact($contact);
1048
1049                 if (strpos($url, 'redir/') === 0) {
1050                         $sparkle = ' class="sparkle" ';
1051                 } else {
1052                         $sparkle = '';
1053                 }
1054
1055                 if ($contact['pending']) {
1056                         if (in_array($contact['rel'], [Model\Contact::FRIEND, Model\Contact::SHARING])) {
1057                                 $alt_text = DI::l10n()->t('Pending outgoing contact request');
1058                         } else {
1059                                 $alt_text = DI::l10n()->t('Pending incoming contact request');
1060                         }
1061                 }
1062
1063                 if ($contact['self']) {
1064                         $alt_text = DI::l10n()->t('This is you');
1065                         $url = $contact['url'];
1066                         $sparkle = '';
1067                 }
1068
1069                 return [
1070                         'id'           => $contact['id'],
1071                         'url'          => $url,
1072                         'img_hover'    => DI::l10n()->t('Visit %s\'s profile [%s]', $contact['name'], $contact['url']),
1073                         'photo_menu'   => Model\Contact::photoMenu($contact),
1074                         'thumb'        => Model\Contact::getThumb($contact, true),
1075                         'alt_text'     => $alt_text,
1076                         'name'         => $contact['name'],
1077                         'nick'         => $contact['nick'],
1078                         'details'      => $contact['location'],
1079                         'tags'         => $contact['keywords'],
1080                         'about'        => $contact['about'],
1081                         'account_type' => Model\Contact::getAccountType($contact),
1082                         'sparkle'      => $sparkle,
1083                         'itemurl'      => ($contact['addr'] ?? '') ?: $contact['url'],
1084                         'network'      => ContactSelector::networkToName($contact['network'], $contact['url'], $contact['protocol'], $contact['gsid']),
1085                 ];
1086         }
1087
1088         /**
1089          * Gives a array with actions which can performed to a given contact
1090          *
1091          * This includes actions like e.g. 'block', 'hide', 'delete' and others
1092          *
1093          * @param array $contact Data about the Contact
1094          * @return array with contact related actions
1095          */
1096         private static function getContactActions($contact)
1097         {
1098                 $poll_enabled = in_array($contact['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::OSTATUS, Protocol::FEED, Protocol::MAIL]);
1099                 $contact_actions = [];
1100
1101                 $formSecurityToken = self::getFormSecurityToken('contact_action');
1102
1103                 // Provide friend suggestion only for Friendica contacts
1104                 if ($contact['network'] === Protocol::DFRN) {
1105                         $contact_actions['suggest'] = [
1106                                 'label' => DI::l10n()->t('Suggest friends'),
1107                                 'url'   => 'fsuggest/' . $contact['id'],
1108                                 'title' => '',
1109                                 'sel'   => '',
1110                                 'id'    => 'suggest',
1111                         ];
1112                 }
1113
1114                 if ($poll_enabled) {
1115                         $contact_actions['update'] = [
1116                                 'label' => DI::l10n()->t('Update now'),
1117                                 'url'   => 'contact/' . $contact['id'] . '/update?t=' . $formSecurityToken,
1118                                 'title' => '',
1119                                 'sel'   => '',
1120                                 'id'    => 'update',
1121                         ];
1122                 }
1123
1124                 if (in_array($contact['network'], Protocol::NATIVE_SUPPORT)) {
1125                         $contact_actions['updateprofile'] = [
1126                                 'label' => DI::l10n()->t('Refetch contact data'),
1127                                 'url'   => 'contact/' . $contact['id'] . '/updateprofile?t=' . $formSecurityToken,
1128                                 'title' => '',
1129                                 'sel'   => '',
1130                                 'id'    => 'updateprofile',
1131                         ];
1132                 }
1133
1134                 $contact_actions['block'] = [
1135                         'label' => (intval($contact['blocked']) ? DI::l10n()->t('Unblock') : DI::l10n()->t('Block')),
1136                         'url'   => 'contact/' . $contact['id'] . '/block?t=' . $formSecurityToken,
1137                         'title' => DI::l10n()->t('Toggle Blocked status'),
1138                         'sel'   => (intval($contact['blocked']) ? 'active' : ''),
1139                         'id'    => 'toggle-block',
1140                 ];
1141
1142                 $contact_actions['ignore'] = [
1143                         'label' => (intval($contact['readonly']) ? DI::l10n()->t('Unignore') : DI::l10n()->t('Ignore')),
1144                         'url'   => 'contact/' . $contact['id'] . '/ignore?t=' . $formSecurityToken,
1145                         'title' => DI::l10n()->t('Toggle Ignored status'),
1146                         'sel'   => (intval($contact['readonly']) ? 'active' : ''),
1147                         'id'    => 'toggle-ignore',
1148                 ];
1149
1150                 if ($contact['uid'] != 0) {
1151                         if (Protocol::supportsRevokeFollow($contact['network']) && in_array($contact['rel'], [Model\Contact::FOLLOWER, Model\Contact::FRIEND])) {
1152                                 $contact_actions['revoke_follow'] = [
1153                                         'label' => DI::l10n()->t('Revoke Follow'),
1154                                         'url'   => 'contact/' . $contact['id'] . '/revoke',
1155                                         'title' => DI::l10n()->t('Revoke the follow from this contact'),
1156                                         'sel'   => '',
1157                                         'id'    => 'revoke_follow',
1158                                 ];
1159                         }
1160
1161                         $contact_actions['delete'] = [
1162                                 'label' => DI::l10n()->t('Delete'),
1163                                 'url'   => 'contact/' . $contact['id'] . '/drop?t=' . $formSecurityToken,
1164                                 'title' => DI::l10n()->t('Delete contact'),
1165                                 'sel'   => '',
1166                                 'id'    => 'delete',
1167                         ];
1168                 }
1169
1170                 return $contact_actions;
1171         }
1172 }