From 07f7585e40fdce7274dc1d1ebb9941d9f7dbbc6e Mon Sep 17 00:00:00 2001 From: Hypolite Petovan Date: Sun, 18 Dec 2022 22:49:18 -0500 Subject: [PATCH 1/3] Remove DI dependency in Profile\Profile module --- src/Module/Profile/Profile.php | 177 +++++++++++++++++++-------------- 1 file changed, 103 insertions(+), 74 deletions(-) diff --git a/src/Module/Profile/Profile.php b/src/Module/Profile/Profile.php index 2c680d4467..bed5ffce7f 100644 --- a/src/Module/Profile/Profile.php +++ b/src/Module/Profile/Profile.php @@ -21,35 +21,68 @@ namespace Friendica\Module\Profile; +use Friendica\App; use Friendica\Content\Feature; use Friendica\Content\ForumManager; use Friendica\Content\Nav; use Friendica\Content\Text\BBCode; use Friendica\Content\Text\HTML; +use Friendica\Core\Config\Capability\IManageConfigValues; use Friendica\Core\Hook; +use Friendica\Core\L10n; use Friendica\Core\Protocol; use Friendica\Core\Renderer; +use Friendica\Core\Session\Capability\IHandleUserSessions; use Friendica\Core\System; +use Friendica\Database\Database; use Friendica\Database\DBA; -use Friendica\DI; use Friendica\Model\Contact; use Friendica\Model\Profile as ProfileModel; use Friendica\Model\Tag; use Friendica\Model\User; use Friendica\Module\BaseProfile; +use Friendica\Module\Response; use Friendica\Module\Security\Login; use Friendica\Network\HTTPException; +use Friendica\Profile\ProfileField\Repository\ProfileField; use Friendica\Protocol\ActivityPub; use Friendica\Util\DateTimeFormat; +use Friendica\Util\Profiler; use Friendica\Util\Temporal; +use Psr\Log\LoggerInterface; class Profile extends BaseProfile { + /** @var Database */ + private $database; + /** @var App */ + private $app; + /** @var IHandleUserSessions */ + private $session; + /** @var IManageConfigValues */ + private $config; + /** @var App\Page */ + private $page; + /** @var ProfileField */ + private $profileField; + + public function __construct(ProfileField $profileField, App\Page $page, IManageConfigValues $config, IHandleUserSessions $session, App $app, Database $database, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, array $server, array $parameters = []) + { + parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters); + + $this->database = $database; + $this->app = $app; + $this->session = $session; + $this->config = $config; + $this->page = $page; + $this->profileField = $profileField; + } + protected function rawContent(array $request = []) { if (ActivityPub::isRequest()) { - $user = DBA::selectFirst('user', ['uid'], ['nickname' => $this->parameters['nickname'], 'account_removed' => false]); - if (DBA::isResult($user)) { + $user = $this->database->selectFirst('user', ['uid'], ['nickname' => $this->parameters['nickname'] ?? '', 'account_removed' => false]); + if ($user) { try { $data = ActivityPub\Transmitter::getProfile($user['uid']); header('Access-Control-Allow-Origin: *'); @@ -60,7 +93,7 @@ class Profile extends BaseProfile } } - if (DBA::exists('userd', ['username' => $this->parameters['nickname']])) { + if ($this->database->exists('userd', ['username' => $this->parameters['nickname']])) { // Known deleted user $data = ActivityPub\Transmitter::getDeletedUser($this->parameters['nickname']); @@ -74,43 +107,41 @@ class Profile extends BaseProfile protected function content(array $request = []): string { - $a = DI::app(); - - $profile = ProfileModel::load($a, $this->parameters['nickname'] ?? ''); + $profile = ProfileModel::load($this->app, $this->parameters['nickname'] ?? ''); if (!$profile) { - throw new HTTPException\NotFoundException(DI::l10n()->t('Profile not found.')); + throw new HTTPException\NotFoundException($this->t('Profile not found.')); } - $remote_contact_id = DI::userSession()->getRemoteContactID($profile['uid']); + $remote_contact_id = $this->session->getRemoteContactID($profile['uid']); - if (DI::config()->get('system', 'block_public') && !DI::userSession()->isAuthenticated()) { + if ($this->config->get('system', 'block_public') && !$this->session->isAuthenticated()) { return Login::form(); } - if (!empty($profile['hidewall']) && !DI::userSession()->isAuthenticated()) { + if (!empty($profile['hidewall']) && !$this->session->isAuthenticated()) { $this->baseUrl->redirect('profile/' . $profile['nickname'] . '/restricted'); } if (!empty($profile['page-flags']) && $profile['page-flags'] == User::PAGE_FLAGS_COMMUNITY) { - DI::page()['htmlhead'] .= '' . "\n"; + $this->page['htmlhead'] .= '' . "\n"; } - DI::page()['htmlhead'] .= self::buildHtmlHead($profile, $this->parameters['nickname'], $remote_contact_id); + $this->page['htmlhead'] .= $this->buildHtmlHead($profile, $this->parameters['nickname']); Nav::setSelected('home'); - $is_owner = DI::userSession()->getLocalUserId() == $profile['uid']; - $o = self::getTabsHTML('profile', $is_owner, $profile['nickname'], $profile['hide-friends']); + $is_owner = $this->session->getLocalUserId() == $profile['uid']; + $o = self::getTabsHTML('profile', $is_owner, $profile['nickname'], $profile['hide-friends']); - $view_as_contacts = []; - $view_as_contact_id = 0; + $view_as_contacts = []; + $view_as_contact_id = 0; $view_as_contact_alert = ''; if ($is_owner) { - $view_as_contact_id = intval($_GET['viewas'] ?? 0); + $view_as_contact_id = intval($request['viewas'] ?? 0); $view_as_contacts = Contact::selectToArray(['id', 'name'], [ - 'uid' => DI::userSession()->getLocalUserId(), - 'rel' => [Contact::FOLLOWER, Contact::SHARING, Contact::FRIEND], + 'uid' => $this->session->getLocalUserId(), + 'rel' => [Contact::FOLLOWER, Contact::SHARING, Contact::FRIEND], 'network' => Protocol::DFRN, 'blocked' => false, ]); @@ -123,7 +154,7 @@ class Profile extends BaseProfile } if (($key = array_search($view_as_contact_id, $view_as_contact_ids)) !== false) { - $view_as_contact_alert = DI::l10n()->t( + $view_as_contact_alert = $this->t( 'You\'re currently viewing your profile as %s Cancel', htmlentities($view_as_contacts[$key]['name'], ENT_COMPAT, 'UTF-8'), 'profile/' . $this->parameters['nickname'] . '/profile' @@ -133,47 +164,47 @@ class Profile extends BaseProfile $basic_fields = []; - $basic_fields += self::buildField('fullname', DI::l10n()->t('Full Name:'), $profile['name']); + $basic_fields += self::buildField('fullname', $this->t('Full Name:'), $profile['name']); if (Feature::isEnabled($profile['uid'], 'profile_membersince')) { $basic_fields += self::buildField( 'membersince', - DI::l10n()->t('Member since:'), + $this->t('Member since:'), DateTimeFormat::local($profile['register_date']) ); } if (!empty($profile['dob']) && $profile['dob'] > DBA::NULL_DATE) { - $year_bd_format = DI::l10n()->t('j F, Y'); - $short_bd_format = DI::l10n()->t('j F'); + $year_bd_format = $this->t('j F, Y'); + $short_bd_format = $this->t('j F'); - $dob = DI::l10n()->getDay( + $dob = $this->l10n->getDay( intval($profile['dob']) ? DateTimeFormat::utc($profile['dob'] . ' 00:00 +00:00', $year_bd_format) : DateTimeFormat::utc('2001-' . substr($profile['dob'], 5) . ' 00:00 +00:00', $short_bd_format) ); - $basic_fields += self::buildField('dob', DI::l10n()->t('Birthday:'), $dob); + $basic_fields += self::buildField('dob', $this->t('Birthday:'), $dob); if ($age = Temporal::getAgeByTimezone($profile['dob'], $profile['timezone'])) { - $basic_fields += self::buildField('age', DI::l10n()->t('Age: '), DI::l10n()->tt('%d year old', '%d years old', $age)); + $basic_fields += self::buildField('age', $this->t('Age: '), $this->tt('%d year old', '%d years old', $age)); } } if ($profile['about']) { - $basic_fields += self::buildField('about', DI::l10n()->t('Description:'), BBCode::convertForUriId($profile['uri-id'], $profile['about'])); + $basic_fields += self::buildField('about', $this->t('Description:'), BBCode::convertForUriId($profile['uri-id'], $profile['about'])); } if ($profile['xmpp']) { - $basic_fields += self::buildField('xmpp', DI::l10n()->t('XMPP:'), $profile['xmpp']); + $basic_fields += self::buildField('xmpp', $this->t('XMPP:'), $profile['xmpp']); } if ($profile['matrix']) { - $basic_fields += self::buildField('matrix', DI::l10n()->t('Matrix:'), $profile['matrix']); + $basic_fields += self::buildField('matrix', $this->t('Matrix:'), $profile['matrix']); } if ($profile['homepage']) { - $basic_fields += self::buildField('homepage', DI::l10n()->t('Homepage:'), HTML::toLink($profile['homepage'])); + $basic_fields += self::buildField('homepage', $this->t('Homepage:'), HTML::toLink($profile['homepage'])); } if ( @@ -183,7 +214,7 @@ class Profile extends BaseProfile || $profile['region'] || $profile['country-name'] ) { - $basic_fields += self::buildField('location', DI::l10n()->t('Location:'), ProfileModel::formatLocation($profile)); + $basic_fields += self::buildField('location', $this->t('Location:'), ProfileModel::formatLocation($profile)); } if ($profile['pub_keywords']) { @@ -191,12 +222,12 @@ class Profile extends BaseProfile // Separator is defined in Module\Settings\Profile\Index::cleanKeywords foreach (explode(', ', $profile['pub_keywords']) as $tag_label) { $tags[] = [ - 'url' => '/search?tag=' . $tag_label, + 'url' => '/search?tag=' . $tag_label, 'label' => Tag::TAG_CHARACTER[Tag::HASHTAG] . $tag_label, ]; } - $basic_fields += self::buildField('pub_keywords', DI::l10n()->t('Tags:'), $tags); + $basic_fields += self::buildField('pub_keywords', $this->t('Tags:'), $tags); } $custom_fields = []; @@ -205,9 +236,9 @@ class Profile extends BaseProfile $contact_id = $view_as_contact_id ?: $remote_contact_id ?: 0; if ($is_owner && $contact_id === 0) { - $profile_fields = DI::profileField()->selectByUserId($profile['uid']); + $profile_fields = $this->profileField->selectByUserId($profile['uid']); } else { - $profile_fields = DI::profileField()->selectByContactId($contact_id, $profile['uid']); + $profile_fields = $this->profileField->selectByContactId($contact_id, $profile['uid']); } foreach ($profile_fields as $profile_field) { @@ -217,42 +248,42 @@ class Profile extends BaseProfile BBCode::convertForUriId($profile['uri-id'], $profile_field->value), 'aprofile custom' ); - }; + } //show subcribed forum if it is enabled in the usersettings if (Feature::isEnabled($profile['uid'], 'forumlist_profile')) { $custom_fields += self::buildField( 'forumlist', - DI::l10n()->t('Forums:'), + $this->t('Forums:'), ForumManager::profileAdvanced($profile['uid']) ); } $tpl = Renderer::getMarkupTemplate('profile/profile.tpl'); - $o .= Renderer::replaceMacros($tpl, [ - '$title' => DI::l10n()->t('Profile'), - '$yourself' => DI::l10n()->t('Yourself'), - '$view_as_contacts' => $view_as_contacts, - '$view_as_contact_id' => $view_as_contact_id, + $o .= Renderer::replaceMacros($tpl, [ + '$title' => $this->t('Profile'), + '$yourself' => $this->t('Yourself'), + '$view_as_contacts' => $view_as_contacts, + '$view_as_contact_id' => $view_as_contact_id, '$view_as_contact_alert' => $view_as_contact_alert, - '$view_as' => DI::l10n()->t('View profile as:'), - '$submit' => DI::l10n()->t('Submit'), - '$basic' => DI::l10n()->t('Basic'), - '$advanced' => DI::l10n()->t('Advanced'), - '$is_owner' => $profile['uid'] == DI::userSession()->getLocalUserId(), - '$query_string' => DI::args()->getQueryString(), - '$basic_fields' => $basic_fields, - '$custom_fields' => $custom_fields, - '$profile' => $profile, - '$edit_link' => [ - 'url' => DI::baseUrl() . '/settings/profile', DI::l10n()->t('Edit profile'), + '$view_as' => $this->t('View profile as:'), + '$submit' => $this->t('Submit'), + '$basic' => $this->t('Basic'), + '$advanced' => $this->t('Advanced'), + '$is_owner' => $profile['uid'] == $this->session->getLocalUserId(), + '$query_string' => $this->args->getQueryString(), + '$basic_fields' => $basic_fields, + '$custom_fields' => $custom_fields, + '$profile' => $profile, + '$edit_link' => [ + 'url' => 'settings/profile', $this->t('Edit profile'), 'title' => '', - 'label' => DI::l10n()->t('Edit profile') + 'label' => $this->t('Edit profile') ], - '$viewas_link' => [ - 'url' => DI::args()->getQueryString() . '#viewas', + '$viewas_link' => [ + 'url' => $this->args->getQueryString() . '#viewas', 'title' => '', - 'label' => DI::l10n()->t('View as') + 'label' => $this->t('View as') ], ]); @@ -270,20 +301,18 @@ class Profile extends BaseProfile * @param string $class Optional CSS class to apply to the field * @return array */ - private static function buildField(string $name, string $label, $value, string $class = 'aprofile') + private static function buildField(string $name, string $label, $value, string $class = 'aprofile'): array { return [$name => [ - 'id' => 'aprofile-' . $name, + 'id' => 'aprofile-' . $name, 'class' => $class, 'label' => $label, 'value' => $value, ]]; } - private static function buildHtmlHead(array $profile, string $nickname, int $remote_contact_id) + private function buildHtmlHead(array $profile, string $nickname): string { - $baseUrl = DI::baseUrl(); - $htmlhead = "\n"; if (!empty($profile['page-flags']) && $profile['page-flags'] == User::PAGE_FLAGS_COMMUNITY) { @@ -300,8 +329,8 @@ class Profile extends BaseProfile } // site block - $blocked = !DI::userSession()->isAuthenticated() && DI::config()->get('system', 'block_public'); - $userblock = !DI::userSession()->isAuthenticated() && $profile['hidewall']; + $blocked = !$this->session->isAuthenticated() && $this->config->get('system', 'block_public'); + $userblock = !$this->session->isAuthenticated() && $profile['hidewall']; if (!$blocked && !$userblock) { $keywords = str_replace(['#', ',', ' ', ',,'], ['', ' ', ',', ','], $profile['pub_keywords'] ?? ''); if (strlen($keywords)) { @@ -315,17 +344,17 @@ class Profile extends BaseProfile $htmlhead .= '' . "\n"; } - $htmlhead .= '' . "\n"; - $htmlhead .= '' . "\n"; - $htmlhead .= '' . "\n"; - $htmlhead .= '' . "\n"; - $uri = urlencode('acct:' . $profile['nickname'] . '@' . $baseUrl->getHostname() . ($baseUrl->getUrlPath() ? '/' . $baseUrl->getUrlPath() : '')); - $htmlhead .= '' . "\n"; - header('Link: <' . $baseUrl . '/xrd/?uri=' . $uri . '>; rel="lrdd"; type="application/xrd+xml"', false); + $htmlhead .= '' . "\n"; + $htmlhead .= '' . "\n"; + $htmlhead .= '' . "\n"; + $htmlhead .= '' . "\n"; + $uri = urlencode('acct:' . $profile['nickname'] . '@' . $this->baseUrl->getHostname() . ($this->baseUrl->getUrlPath() ? '/' . $this->baseUrl->getUrlPath() : '')); + $htmlhead .= '' . "\n"; + header('Link: <' . $this->baseUrl . '/xrd/?uri=' . $uri . '>; rel="lrdd"; type="application/xrd+xml"', false); $dfrn_pages = ['request', 'confirm', 'notify', 'poll']; foreach ($dfrn_pages as $dfrn) { - $htmlhead .= '' . "\n"; + $htmlhead .= '' . "\n"; } return $htmlhead; From 76e9c4daa279188bb0cd72c3854f7041ea13729b Mon Sep 17 00:00:00 2001 From: Hypolite Petovan Date: Sun, 18 Dec 2022 22:49:49 -0500 Subject: [PATCH 2/3] Fix link to profile page in custom profile fields settings - Previous link was leading to conversations --- src/Module/Settings/Profile/Index.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Module/Settings/Profile/Index.php b/src/Module/Settings/Profile/Index.php index 76930b7263..488e82b48e 100644 --- a/src/Module/Settings/Profile/Index.php +++ b/src/Module/Settings/Profile/Index.php @@ -262,7 +262,7 @@ class Index extends BaseSettings

Reorder by dragging the field title.

Empty the label field to remove a custom field.

Non-public fields can only be seen by the selected Friendica contacts or the Friendica contacts in the selected groups.

", - 'profile/' . $profile['nickname'] + 'profile/' . $profile['nickname'] . '/profile' ), '$custom_fields' => $custom_fields, ]); From 39607b20e2973fb271544b69b3594d5a56b547c8 Mon Sep 17 00:00:00 2001 From: Hypolite Petovan Date: Sun, 18 Dec 2022 23:24:59 -0500 Subject: [PATCH 3/3] Convert custom profile field URL values to rel="me" links --- src/Content/Text/HTML.php | 37 +++++++++------------------ src/Module/Profile/Profile.php | 25 +++++++++++++++--- src/Util/Strings.php | 46 +++++++++++++++++++++++----------- 3 files changed, 66 insertions(+), 42 deletions(-) diff --git a/src/Content/Text/HTML.php b/src/Content/Text/HTML.php index 2e55c71d20..fa5a0a5905 100644 --- a/src/Content/Text/HTML.php +++ b/src/Content/Text/HTML.php @@ -736,22 +736,22 @@ class HTML '[youtube]$2[/youtube]', $s ); - + $s = preg_replace( '#](.*?)https?://www.youtube.com/embed/([A-Za-z0-9\-_=]+)(.*?)#ism', '[youtube]$2[/youtube]', $s ); - + $s = preg_replace( '#](.*?)https?://player.vimeo.com/video/([0-9]+)(.*?)#ism', '[vimeo]$2[/vimeo]', $s ); - + return $s; } - + /** * transform link href and img src from relative to absolute * @@ -764,30 +764,30 @@ class HTML if (empty($base)) { return $text; } - + $base = rtrim($base, '/'); - + $base2 = $base . "/"; - + // Replace links $pattern = "/]*) href=\"(?!http|https|\/)([^\"]*)\"/"; $replace = "$1', $s); - $s = preg_replace("/\<(.*?)(src|href)=(.*?)\&\;(.*?)\>/ism", '<$1$2=$3&$4>', $s); - return $s; - } - /** * Given a HTML text and a set of filtering reasons, adds a content hiding header with the provided reasons * diff --git a/src/Module/Profile/Profile.php b/src/Module/Profile/Profile.php index bed5ffce7f..864b8a76e8 100644 --- a/src/Module/Profile/Profile.php +++ b/src/Module/Profile/Profile.php @@ -26,7 +26,6 @@ use Friendica\Content\Feature; use Friendica\Content\ForumManager; use Friendica\Content\Nav; use Friendica\Content\Text\BBCode; -use Friendica\Content\Text\HTML; use Friendica\Core\Config\Capability\IManageConfigValues; use Friendica\Core\Hook; use Friendica\Core\L10n; @@ -48,6 +47,7 @@ use Friendica\Profile\ProfileField\Repository\ProfileField; use Friendica\Protocol\ActivityPub; use Friendica\Util\DateTimeFormat; use Friendica\Util\Profiler; +use Friendica\Util\Strings; use Friendica\Util\Temporal; use Psr\Log\LoggerInterface; @@ -204,7 +204,11 @@ class Profile extends BaseProfile } if ($profile['homepage']) { - $basic_fields += self::buildField('homepage', $this->t('Homepage:'), HTML::toLink($profile['homepage'])); + $basic_fields += self::buildField( + 'homepage', + $this->t('Homepage:'), + $this->tryRelMe($profile['homepage']) ?: $profile['homepage'] + ); } if ( @@ -245,7 +249,7 @@ class Profile extends BaseProfile $custom_fields += self::buildField( 'custom_' . $profile_field->order, $profile_field->label, - BBCode::convertForUriId($profile['uri-id'], $profile_field->value), + $this->tryRelMe($profile_field->value) ?: BBCode::convertForUriId($profile['uri-id'], $profile_field->value), 'aprofile custom' ); } @@ -359,4 +363,19 @@ class Profile extends BaseProfile return $htmlhead; } + + /** + * Check if the input is an HTTP(S) link and returns a rel="me" link if yes, empty string if not + * + * @param string $input + * @return string + */ + private function tryRelMe(string $input): string + { + if (preg_match(Strings::onlyLinkRegEx(), trim($input))) { + return '' . trim($input) . ''; + } + + return ''; + } } diff --git a/src/Util/Strings.php b/src/Util/Strings.php index 379f2a2521..c5d5c760a3 100644 --- a/src/Util/Strings.php +++ b/src/Util/Strings.php @@ -380,29 +380,47 @@ class Strings * Returns the regular expression string to match URLs in a given text * * @return string - * @see https://daringfireball.net/2010/07/improved_regex_for_matching_urls */ public static function autoLinkRegEx(): string { return '@ -(??«»“”‘’.] # Domain can\'t start with a . - [^/\s\xA0`!()\[\]{};:\'",<>?«»“”‘’]+ # Domain can\'t end with a . + [^/\s\xA0`!()\[\]{};:\'",<>?«»“”‘’.] # Domain can\'t start with a . + [^/\s\xA0`!()\[\]{};:\'",<>?«»“”‘’]+ # Domain can\'t end with a . \. [^/\s\xA0`!()\[\]{};:\'".,<>?«»“”‘’]+/? # Followed by a slash ) - (?: # One or more: - [^\s\xA0()<>]+ # Run of non-space, non-()<> - | # or - \(([^\s\xA0()<>]+|(\([^\s()<>]+\)))*\) # balanced parens, up to 2 levels - | # or - [^\s\xA0`!()\[\]{};:\'".,<>?«»“”‘’] # not a space or one of these punct chars - )* -)@xiu'; + (?: # One or more: + [^\s\xA0()<>]+ # Run of non-space, non-()<> + | # or + \(([^\s\xA0()<>]+|(\([^\s()<>]+\)))*\) # balanced parens, up to 2 levels + | # or + [^\s\xA0`!()\[\]{};:\'".,<>?«»“”‘’] # not a space or one of these punct chars + )*'; } /**