Files
integration_gotosocial/lib/Reference/GoToSocialReferenceProvider.php
Neon Overlord 874c25cfb7
build / build (push) Failing after 1m32s
fix: namespace the dashboard widget and reference provider IDs
Nextcloud keys dashboard widgets and reference providers globally, not per
app, so the rebrand had to cover them too and did not. This fork shipped
with upstream's IDs -- mastodon_notifications, mastodon_home_timeline and
mastodon-multi -- and enabling it alongside integration_mastodon clobbered
that app's registry entries. The dashboard then resolved those IDs to
classes in the wrong app and logged:

  Could not register lazy dashboard widget: OCA\Mastodon\Dashboard\MastodonWidget

Found on a live instance running both apps side by side. No data was
affected; disabling this app restored the other immediately.

The JS registrations in dashboard.js/dashboardHome.js are renamed to match,
since OCA.Dashboard.register() must agree with the widget's getId().

'connected-accounts' is deliberately left shared -- it is a core Nextcloud
settings section, not an app-owned identifier. 'mastodonToken' is likewise
left alone: it is a query parameter passed between this app's own controller
and its own settings page.

Assisted-by: Claude Code:claude-opus-5
2026-08-03 15:14:08 -07:00

214 lines
6.7 KiB
PHP

<?php
/**
* @copyright Copyright (c) 2023 Julien Veyssier <julien-nc@posteo.net>
*
* @author Julien Veyssier <julien-nc@posteo.net>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
namespace OCA\GoToSocial\Reference;
use OCA\GoToSocial\AppInfo\Application;
use OCP\AppFramework\Services\IAppConfig;
use OCP\Collaboration\Reference\ADiscoverableReferenceProvider;
use OCP\Collaboration\Reference\IReference;
use OCP\Collaboration\Reference\IReferenceManager;
use OCP\Collaboration\Reference\ISearchableReferenceProvider;
use OCP\Collaboration\Reference\LinkReferenceProvider;
use OCP\IConfig;
use OCP\IL10N;
use OCP\IURLGenerator;
class GoToSocialReferenceProvider extends ADiscoverableReferenceProvider implements ISearchableReferenceProvider {
public function __construct(
private IAppConfig $appConfig,
private IConfig $config,
private IL10N $l10n,
private IURLGenerator $urlGenerator,
private IReferenceManager $referenceManager,
private LinkReferenceProvider $linkReferenceProvider,
private ?string $userId,
) {
}
/**
* @inheritDoc
*/
public function getId(): string {
return 'gotosocial-multi';
}
/**
* @inheritDoc
*/
public function getTitle(): string {
if ($this->userId !== null) {
$searchStatusesEnabled = $this->config->getUserValue($this->userId, Application::APP_ID, 'search_statuses_enabled', '1') === '1';
$searchAccountsEnabled = $this->config->getUserValue($this->userId, Application::APP_ID, 'search_accounts_enabled', '1') === '1';
$searchTagsEnabled = $this->config->getUserValue($this->userId, Application::APP_ID, 'search_hashtags_enabled', '1') === '1';
if ($searchStatusesEnabled && $searchAccountsEnabled && $searchTagsEnabled) {
return $this->l10n->t('GoToSocial people, posts and hashtags');
} elseif ($searchStatusesEnabled && $searchAccountsEnabled) {
return $this->l10n->t('GoToSocial people and posts');
} elseif ($searchStatusesEnabled && $searchTagsEnabled) {
return $this->l10n->t('GoToSocial posts and hashtags');
} elseif ($searchTagsEnabled && $searchAccountsEnabled) {
return $this->l10n->t('GoToSocial people and hashtags');
} elseif ($searchTagsEnabled) {
return $this->l10n->t('GoToSocial hashtags');
} elseif ($searchAccountsEnabled) {
return $this->l10n->t('GoToSocial people');
} elseif ($searchStatusesEnabled) {
return $this->l10n->t('GoToSocial posts');
}
return $this->l10n->t('GoToSocial');
}
return $this->l10n->t('GoToSocial people, posts and hashtags');
}
/**
* @inheritDoc
*/
public function getOrder(): int {
return 10;
}
/**
* @inheritDoc
*/
public function getIconUrl(): string {
return $this->urlGenerator->getAbsoluteURL(
$this->urlGenerator->imagePath(Application::APP_ID, 'app-dark.svg')
);
}
/**
* @inheritDoc
*/
public function getSupportedSearchProviderIds(): array {
if ($this->userId !== null) {
$searchProviderIds = [];
$searchStatusesEnabled = $this->config->getUserValue($this->userId, Application::APP_ID, 'search_statuses_enabled', '1') === '1';
if ($searchStatusesEnabled) {
$searchProviderIds[] = 'gotosocial-search-statuses';
}
$searchAccountsEnabled = $this->config->getUserValue($this->userId, Application::APP_ID, 'search_accounts_enabled', '1') === '1';
if ($searchAccountsEnabled) {
$searchProviderIds[] = 'gotosocial-search-accounts';
}
$searchTagsEnabled = $this->config->getUserValue($this->userId, Application::APP_ID, 'search_hashtags_enabled', '1') === '1';
if ($searchTagsEnabled) {
$searchProviderIds[] = 'gotosocial-search-hashtags';
}
return $searchProviderIds;
}
return [
'gotosocial-search-statuses',
'gotosocial-search-accounts',
'gotosocial-search-hashtags',
];
}
/**
* @inheritDoc
*/
public function matchReference(string $referenceText): bool {
$adminLinkPreviewEnabled = $this->appConfig->getAppValueString('link_preview_enabled', '1', lazy: true) === '1';
$userLinkPreviewEnabled = $this->config->getUserValue($this->userId, Application::APP_ID, 'link_preview_enabled', '1') === '1';
if (!$adminLinkPreviewEnabled || !$userLinkPreviewEnabled) {
return false;
}
// never resolve mastodon
// leave it to the linkReferenceProvider which does it pretty well
return false;
// link examples:
// https://instance.org/@user/123456
// https://instance.org/@user@instance.net/987654
return preg_match('/^(?:https?:\/\/)?(?:www\.)?[^\/]+\/@[^\/@]+\/\d+/i', $referenceText) === 1
|| preg_match('/^(?:https?:\/\/)?(?:www\.)?[^\/]+\/@[^\/@]+@[^\/@]+\/\d+/i', $referenceText) === 1;
}
/**
* @inheritDoc
*/
public function resolveReference(string $referenceText): ?IReference {
if ($this->matchReference($referenceText)) {
$urlInfo = $this->getUrlInfo($referenceText);
if ($urlInfo !== null) {
}
// fallback to opengraph
return $this->linkReferenceProvider->resolveReference($referenceText);
}
return null;
}
/**
* @param string $url
* @return array|null
*/
private function getUrlInfo(string $url): ?array {
preg_match('/^(?:https?:\/\/)?(?:www\.)?([^\/]+)\/@([^\/@]+)@([^\/@]+)\/(\d+)/i', $url, $matches);
if (count($matches) > 4) {
return [
'instance' => $matches[1],
'username' => $matches[2],
'id' => (int)$matches[4],
];
}
preg_match('/^(?:https?:\/\/)?(?:www\.)?([^\/]+)\/@([^\/@]+)\/(\d+)/i', $url, $matches);
if (count($matches) > 3) {
return [
'instance' => $matches[1],
'username' => $matches[2],
'id' => (int)$matches[4],
];
}
return null;
}
/**
* We use the userId here because when connecting/disconnecting from the GitHub account,
* we want to invalidate all the user cache and this is only possible with the cache prefix
* @inheritDoc
*/
public function getCachePrefix(string $referenceId): string {
return $this->userId ?? '';
}
/**
* We don't use the userId here but rather a reference unique id
* @inheritDoc
*/
public function getCacheKey(string $referenceId): ?string {
return $referenceId;
}
/**
* @param string $userId
* @return void
*/
public function invalidateUserCache(string $userId): void {
$this->referenceManager->invalidateCache($userId);
}
}