. * */ namespace Friendica\Module; use Friendica\App; use Friendica\BaseModule; use Friendica\Content\ContactSelector; use Friendica\Content\Nav; use Friendica\Content\Pager; use Friendica\Content\Text\BBCode; use Friendica\Content\Widget; use Friendica\Core\ACL; use Friendica\Core\Hook; use Friendica\Core\Protocol; use Friendica\Core\Renderer; use Friendica\Core\Theme; use Friendica\Core\Worker; use Friendica\Database\DBA; use Friendica\DI; use Friendica\Model; use Friendica\Module\Security\Login; use Friendica\Network\HTTPException\BadRequestException; use Friendica\Network\HTTPException\NotFoundException; use Friendica\Util\DateTimeFormat; use Friendica\Util\Strings; /** * Manages and show Contacts and their content */ class Contact extends BaseModule { const TAB_CONVERSATIONS = 1; const TAB_POSTS = 2; const TAB_PROFILE = 3; const TAB_CONTACTS = 4; const TAB_ADVANCED = 5; private static function batchActions() { if (empty($_POST['contact_batch']) || !is_array($_POST['contact_batch'])) { return; } $contacts_id = $_POST['contact_batch']; $stmt = DBA::select('contact', ['id', 'archive'], ['id' => $contacts_id, 'uid' => local_user(), 'self' => false, 'deleted' => false]); $orig_records = DBA::toArray($stmt); $count_actions = 0; foreach ($orig_records as $orig_record) { $contact_id = $orig_record['id']; if (!empty($_POST['contacts_batch_update'])) { self::updateContactFromPoll($contact_id); $count_actions++; } if (!empty($_POST['contacts_batch_block'])) { self::blockContact($contact_id); $count_actions++; } if (!empty($_POST['contacts_batch_ignore'])) { self::ignoreContact($contact_id); $count_actions++; } if (!empty($_POST['contacts_batch_archive']) && self::archiveContact($contact_id, $orig_record) ) { $count_actions++; } if (!empty($_POST['contacts_batch_drop'])) { self::dropContact($orig_record); $count_actions++; } } if ($count_actions > 0) { info(DI::l10n()->tt('%d contact edited.', '%d contacts edited.', $count_actions)); } DI::baseUrl()->redirect('contact'); } public static function post(array $parameters = []) { $a = DI::app(); if (!local_user()) { return; } // @TODO: Replace with parameter from router if ($a->argv[1] === 'batch') { self::batchActions(); return; } // @TODO: Replace with parameter from router $contact_id = intval($a->argv[1]); if (!$contact_id) { return; } if (!DBA::exists('contact', ['id' => $contact_id, 'uid' => local_user(), 'deleted' => false])) { notice(DI::l10n()->t('Could not access contact record.')); DI::baseUrl()->redirect('contact'); return; // NOTREACHED } Hook::callAll('contact_edit_post', $_POST); $hidden = !empty($_POST['hidden']); $notify = !empty($_POST['notify']); $fetch_further_information = intval($_POST['fetch_further_information'] ?? 0); $ffi_keyword_denylist = Strings::escapeHtml(trim($_POST['ffi_keyword_denylist'] ?? '')); $priority = intval($_POST['poll'] ?? 0); if ($priority > 5 || $priority < 0) { $priority = 0; } $info = Strings::escapeHtml(trim($_POST['info'] ?? '')); $r = DBA::update('contact', [ 'priority' => $priority, 'info' => $info, 'hidden' => $hidden, 'notify_new_posts' => $notify, 'fetch_further_information' => $fetch_further_information, 'ffi_keyword_denylist' => $ffi_keyword_denylist], ['id' => $contact_id, 'uid' => local_user()] ); if (!DBA::isResult($r)) { notice(DI::l10n()->t('Failed to update contact record.')); } $contact = DBA::selectFirst('contact', [], ['id' => $contact_id, 'uid' => local_user(), 'deleted' => false]); if (DBA::isResult($contact)) { $a->data['contact'] = $contact; } return; } /* contact actions */ private static function updateContactFromPoll($contact_id) { $contact = DBA::selectFirst('contact', ['uid', 'url', 'network'], ['id' => $contact_id, 'uid' => local_user(), 'deleted' => false]); if (!DBA::isResult($contact)) { return; } if ($contact['network'] == Protocol::OSTATUS) { $user = Model\User::getById($contact['uid']); $result = Model\Contact::createFromProbe($user, $contact['url'], false, $contact['network']); if ($result['success']) { DBA::update('contact', ['subhub' => 1], ['id' => $contact_id]); } } else { // pull feed and consume it, which should subscribe to the hub. Worker::add(PRIORITY_HIGH, 'OnePoll', $contact_id, 'force'); } } private static function updateContactFromProbe($contact_id) { $contact = DBA::selectFirst('contact', ['url'], ['id' => $contact_id, 'uid' => [0, local_user()], 'deleted' => false]); if (!DBA::isResult($contact)) { return; } // Update the entry in the contact table Model\Contact::updateFromProbe($contact_id); } /** * Toggles the blocked status of a contact identified by id. * * @param $contact_id * @throws \Exception */ private static function blockContact($contact_id) { $blocked = !Model\Contact\User::isBlocked($contact_id, local_user()); Model\Contact\User::setBlocked($contact_id, local_user(), $blocked); } /** * Toggles the ignored status of a contact identified by id. * * @param $contact_id * @throws \Exception */ private static function ignoreContact($contact_id) { $ignored = !Model\Contact\User::isIgnored($contact_id, local_user()); Model\Contact\User::setIgnored($contact_id, local_user(), $ignored); } /** * Toggles the archived status of a contact identified by id. * If the current status isn't provided, this will always archive the contact. * * @param $contact_id * @param $orig_record * @return bool * @throws \Exception */ private static function archiveContact($contact_id, $orig_record) { $archived = empty($orig_record['archive']); $r = DBA::update('contact', ['archive' => $archived], ['id' => $contact_id, 'uid' => local_user()]); return DBA::isResult($r); } private static function dropContact($orig_record) { $owner = Model\User::getOwnerDataById(local_user()); if (!DBA::isResult($owner)) { return; } Model\Contact::terminateFriendship($owner, $orig_record, true); Model\Contact::remove($orig_record['id']); } public static function content(array $parameters = [], $update = 0) { if (!local_user()) { return Login::form($_SERVER['REQUEST_URI']); } $a = DI::app(); $search = Strings::escapeTags(trim($_GET['search'] ?? '')); $nets = Strings::escapeTags(trim($_GET['nets'] ?? '')); $rel = Strings::escapeTags(trim($_GET['rel'] ?? '')); $group = Strings::escapeTags(trim($_GET['group'] ?? '')); $page = DI::page(); $page->registerFooterScript(Theme::getPathForFile('asset/typeahead.js/dist/typeahead.bundle.js')); $page->registerFooterScript(Theme::getPathForFile('js/friendica-tagsinput/friendica-tagsinput.js')); $page->registerStylesheet(Theme::getPathForFile('js/friendica-tagsinput/friendica-tagsinput.css')); $page->registerStylesheet(Theme::getPathForFile('js/friendica-tagsinput/friendica-tagsinput-typeahead.css')); $contact = null; // @TODO: Replace with parameter from router if ($a->argc == 2 && intval($a->argv[1]) || $a->argc == 3 && intval($a->argv[1]) && in_array($a->argv[2], ['posts', 'conversations']) ) { $contact_id = intval($a->argv[1]); // Ensure to use the user contact when the public contact was provided $data = Model\Contact::getPublicAndUserContacID($contact_id, local_user()); if (!empty($data['user']) && ($contact_id == $data['public'])) { $contact_id = $data['user']; } $contact = DBA::selectFirst('contact', [], ['id' => $contact_id, 'uid' => [0, local_user()], 'deleted' => false]); // Don't display contacts that are about to be deleted if ($contact['network'] == Protocol::PHANTOM) { $contact = false; } } if (DBA::isResult($contact)) { if ($contact['self']) { // @TODO: Replace with parameter from router if (($a->argc == 3) && intval($a->argv[1]) && in_array($a->argv[2], ['posts', 'conversations'])) { DI::baseUrl()->redirect('profile/' . $contact['nick']); } else { DI::baseUrl()->redirect('profile/' . $contact['nick'] . '/profile'); } } $a->data['contact'] = $contact; if (($contact['network'] != '') && ($contact['network'] != Protocol::DFRN)) { $network_link = Strings::formatNetworkName($contact['network'], $contact['url']); } else { $network_link = ''; } $follow_link = ''; $unfollow_link = ''; if (in_array($contact['network'], Protocol::NATIVE_SUPPORT)) { if ($contact['uid'] && in_array($contact['rel'], [Model\Contact::SHARING, Model\Contact::FRIEND])) { $unfollow_link = 'unfollow?url=' . urlencode($contact['url']); } elseif(!$contact['pending']) { $follow_link = 'follow?url=' . urlencode($contact['url']); } } $wallmessage_link = ''; if ($contact['uid'] && Model\Contact::canReceivePrivateMessages($contact)) { $wallmessage_link = 'message/new/' . $contact['id']; } $vcard_widget = Renderer::replaceMacros(Renderer::getMarkupTemplate('widget/vcard.tpl'), [ '$name' => $contact['name'], '$photo' => Model\Contact::getPhoto($contact), '$url' => Model\Contact::magicLinkByContact($contact, $contact['url']), '$addr' => $contact['addr'] ?? '', '$network_link' => $network_link, '$network' => DI::l10n()->t('Network:'), '$account_type' => Model\Contact::getAccountType($contact), '$follow' => DI::l10n()->t('Follow'), '$follow_link' => $follow_link, '$unfollow' => DI::l10n()->t('Unfollow'), '$unfollow_link' => $unfollow_link, '$wallmessage' => DI::l10n()->t('Message'), '$wallmessage_link' => $wallmessage_link, ]); $findpeople_widget = ''; $follow_widget = ''; $networks_widget = ''; $rel_widget = ''; if ($contact['uid'] != 0) { $groups_widget = Model\Group::sidebarWidget('contact', 'group', 'full', 'everyone', $contact_id); } else { $groups_widget = ''; } } else { $vcard_widget = ''; $findpeople_widget = Widget::findPeople(); if (isset($_GET['add'])) { $follow_widget = Widget::follow($_GET['add']); } else { $follow_widget = Widget::follow(); } $networks_widget = Widget::networks($_SERVER['REQUEST_URI'], $nets); $rel_widget = Widget::contactRels($_SERVER['REQUEST_URI'], $rel); $groups_widget = Widget::groups($_SERVER['REQUEST_URI'], $group); } DI::page()['aside'] .= $vcard_widget . $findpeople_widget . $follow_widget . $groups_widget . $networks_widget . $rel_widget; $tpl = Renderer::getMarkupTemplate('contacts-head.tpl'); DI::page()['htmlhead'] .= Renderer::replaceMacros($tpl, [ '$baseurl' => DI::baseUrl()->get(true), ]); $o = ''; Nav::setSelected('contact'); if (!local_user()) { notice(DI::l10n()->t('Permission denied.')); return Login::form(); } if ($a->argc == 3) { $contact_id = intval($a->argv[1]); if (!$contact_id) { throw new BadRequestException(); } // @TODO: Replace with parameter from router $cmd = $a->argv[2]; $orig_record = DBA::selectFirst('contact', [], ['id' => $contact_id, 'uid' => [0, local_user()], 'self' => false, 'deleted' => false]); if (!DBA::isResult($orig_record)) { throw new NotFoundException(DI::l10n()->t('Contact not found')); } if ($cmd === 'update' && ($orig_record['uid'] != 0)) { self::updateContactFromPoll($contact_id); DI::baseUrl()->redirect('contact/' . $contact_id); // NOTREACHED } if ($cmd === 'updateprofile') { self::updateContactFromProbe($contact_id); DI::baseUrl()->redirect('contact/' . $contact_id); // NOTREACHED } if ($cmd === 'block') { self::blockContact($contact_id); $blocked = Model\Contact\User::isBlocked($contact_id, local_user()); info(($blocked ? DI::l10n()->t('Contact has been blocked') : DI::l10n()->t('Contact has been unblocked'))); DI::baseUrl()->redirect('contact/' . $contact_id); // NOTREACHED } if ($cmd === 'ignore') { self::ignoreContact($contact_id); $ignored = Model\Contact\User::isIgnored($contact_id, local_user()); info(($ignored ? DI::l10n()->t('Contact has been ignored') : DI::l10n()->t('Contact has been unignored'))); DI::baseUrl()->redirect('contact/' . $contact_id); // NOTREACHED } if ($cmd === 'archive' && ($orig_record['uid'] != 0)) { $r = self::archiveContact($contact_id, $orig_record); if ($r) { $archived = (($orig_record['archive']) ? 0 : 1); info((($archived) ? DI::l10n()->t('Contact has been archived') : DI::l10n()->t('Contact has been unarchived'))); } DI::baseUrl()->redirect('contact/' . $contact_id); // NOTREACHED } if ($cmd === 'drop' && ($orig_record['uid'] != 0)) { // Check if we should do HTML-based delete confirmation if (!empty($_REQUEST['confirm'])) { //