From f7d5ee9226a44a3758e71e1bd77e22ce1571615c Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Thu, 1 Oct 2026 13:09:05 +0200 Subject: [PATCH 01/11] chore(psalm): Provide stubs instead of ignoring issues Signed-off-by: Carl Schwan --- lib/Command/SendEmails.php | 3 ++ lib/FilesHooks.php | 2 +- psalm.xml | 26 ++---------- tests/psalm-baseline.xml | 14 ------- tests/stubs/oc_core_command_base.php | 49 ++++++++++++++++++++++ tests/stubs/oc_files.php | 61 ++++++++++++++++++++++++++++ tests/stubs/oca_files_sharing.php | 7 ++++ tests/stubs/oca_groupfolders.php | 38 +++++++++++++++++ 8 files changed, 162 insertions(+), 38 deletions(-) create mode 100644 tests/stubs/oc_core_command_base.php create mode 100644 tests/stubs/oc_files.php create mode 100644 tests/stubs/oca_groupfolders.php diff --git a/lib/Command/SendEmails.php b/lib/Command/SendEmails.php index be15ea33a..107a5f5be 100644 --- a/lib/Command/SendEmails.php +++ b/lib/Command/SendEmails.php @@ -36,6 +36,7 @@ public function __construct( $this->logger = $logger; } + #[\Override] protected function configure() { $this ->setName('activity:send-mails') @@ -61,6 +62,7 @@ protected function configure() { * @param OutputInterface $output * @return int */ + #[\Override] protected function execute(InputInterface $input, OutputInterface $output): int { // We don't use time() but "time() - 1" here, so we don't run into // runtime issues later and delete emails, which were created in the @@ -98,6 +100,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int * @param CompletionContext $context * @return string[] */ + #[\Override] public function completeArgumentValues($argumentName, CompletionContext $context) { if ($argumentName === 'restrict-batching') { return ['asap', 'hourly', 'daily', 'weekly']; diff --git a/lib/FilesHooks.php b/lib/FilesHooks.php index 33593167d..952114aa1 100644 --- a/lib/FilesHooks.php +++ b/lib/FilesHooks.php @@ -652,7 +652,7 @@ protected function getSourcePathAndOwner($path) { $view = Filesystem::getView(); try { $owner = $view->getOwner($path); - $owner = !is_string($owner) || $owner === '' ? null : $owner; + $owner = $owner === '' ? null : $owner; } catch (NotFoundException $e) { $owner = null; } diff --git a/psalm.xml b/psalm.xml index d2f03dcef..07b18a1e5 100644 --- a/psalm.xml +++ b/psalm.xml @@ -23,34 +23,14 @@ - - - - - - - - - - - - - - - - - - - - - - - + + + diff --git a/tests/psalm-baseline.xml b/tests/psalm-baseline.xml index 25d3a3f89..5a8247d5a 100644 --- a/tests/psalm-baseline.xml +++ b/tests/psalm-baseline.xml @@ -5,12 +5,6 @@ - - - - - - filterFavorites($query);]]> @@ -27,15 +21,7 @@ teamManager]]> teamManager]]> - - - - - - - - diff --git a/tests/stubs/oc_core_command_base.php b/tests/stubs/oc_core_command_base.php new file mode 100644 index 000000000..c062e2fc7 --- /dev/null +++ b/tests/stubs/oc_core_command_base.php @@ -0,0 +1,49 @@ + + */ + public function getAllRulesForPaths(int $storageId, array $filePaths): array { + } + } +} + +namespace OCA\GroupFolders\Folder { + class FolderManager { + public function getFolderAclEnabled(int $id): bool { + } + } +} From 72504f69576d3aa86fdb5fece3b87ac13a55af9f Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Thu, 1 Oct 2026 13:18:59 +0200 Subject: [PATCH 02/11] chore: Setup and run rector Signed-off-by: Carl Schwan --- composer.json | 1 + lib/AppInfo/Application.php | 62 ++++---- lib/BackgroundJob/ExpireActivities.php | 13 +- lib/BackgroundJob/RemoteActivity.php | 7 +- lib/Capabilities.php | 2 +- lib/Command/SendEmails.php | 13 +- lib/Consumer.php | 6 - lib/Controller/APIv1Controller.php | 5 - lib/Controller/APIv2Controller.php | 23 ++- lib/Controller/ActivitiesController.php | 22 +-- lib/Controller/FeedController.php | 4 +- lib/Controller/RemoteActivityController.php | 22 +-- lib/CurrentUser.php | 2 +- lib/Dashboard/ActivityWidget.php | 44 +++--- lib/Data.php | 36 +---- lib/DigestSender.php | 32 ++-- lib/FilesHooks.php | 107 ++++--------- lib/FilesHooksStatic.php | 12 +- lib/Filter/AllFilter.php | 13 +- lib/Filter/ByFilter.php | 13 +- lib/Filter/SelfFilter.php | 13 +- lib/GroupHelper.php | 4 +- lib/Listener/SetUserDefaults.php | 4 +- lib/Listener/ShareEventListener.php | 2 +- lib/Listener/UserDeleted.php | 16 +- lib/MailQueueHandler.php | 20 ++- .../Version2006Date20170808154933.php | 2 - .../Version2006Date20170808155040.php | 2 - .../Version2006Date20170919095939.php | 2 - .../Version2007Date20181107114613.php | 2 - .../Version2008Date20181011095117.php | 4 +- .../Version2010Date20190416112817.php | 2 - .../Version2011Date20201006132544.php | 2 - .../Version2011Date20201006132545.php | 2 - .../Version2011Date20201006132546.php | 2 - .../Version2011Date20201006132547.php | 2 - .../Version2011Date20201207091915.php | 3 - .../Version8000Date20260603120000.php | 3 - lib/NotificationGenerator.php | 8 +- lib/SearchCriteria.php | 10 +- lib/Settings/Admin.php | 20 ++- lib/Settings/AdminSection.php | 8 +- lib/Settings/Personal.php | 20 ++- lib/Settings/PersonalSection.php | 8 +- lib/UserSettings.php | 49 ++---- rector.php | 27 ++++ tests/ConsumerTest.php | 12 +- tests/Controller/APIv1ControllerTest.php | 4 +- tests/Controller/APIv2ControllerTest.php | 18 +-- tests/DataTest.php | 6 +- tests/DigestSenderTest.php | 4 +- tests/FilesHooksTest.php | 10 +- tests/Listener/SetUserDefaultsTest.php | 1 - tests/Listener/UserDeletedTest.php | 2 - tests/MailQueueHandlerTest.php | 9 -- tests/Mock/Provider.php | 21 +-- tests/Template/RssTest.php | 2 +- vendor-bin/rector/composer.json | 5 + vendor-bin/rector/composer.lock | 143 ++++++++++++++++++ 59 files changed, 415 insertions(+), 498 deletions(-) create mode 100644 rector.php create mode 100644 vendor-bin/rector/composer.json create mode 100644 vendor-bin/rector/composer.lock diff --git a/composer.json b/composer.json index 06a69a45a..bd5607ae4 100644 --- a/composer.json +++ b/composer.json @@ -25,6 +25,7 @@ "psalm": "vendor-bin/psalm/vendor/vimeo/psalm/psalm --threads=1", "psalm:update-baseline": "vendor-bin/psalm/vendor/vimeo/psalm/psalm --threads=1 --update-baseline --set-baseline=tests/psalm-baseline.xml", "test:unit": "phpunit -c tests/phpunit.xml --color --fail-on-warning --fail-on-risky", + "rector": "rector && composer cs:fix", "post-install-cmd": [ "[ $COMPOSER_DEV_MODE -eq 0 ] || composer bin all install --ansi" ], diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 9e14c4cd8..38d907fc0 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -101,40 +101,34 @@ public function register(IRegistrationContext $context): void { ); }); - $context->registerService(Data::class, function (ContainerInterface $c) { - return new Data( - $c->get(IManager::class), - $c->get('ActivityConnectionAdapter'), - $c->get(LoggerInterface::class), - $c->get(IConfig::class), - $c->get(IRootFolder::class), - ); - }); - - $context->registerService(MailQueueHandler::class, function (ContainerInterface $c) { - return new MailQueueHandler( - $c->get(IDateTimeFormatter::class), - $c->get('ActivityConnectionAdapter'), - $c->get(IMailer::class), - $c->get(IURLGenerator::class), - $c->get(IUserManager::class), - $c->get(IFactory::class), - $c->get(IManager::class), - $c->get(IValidator::class), - $c->get(IAppConfig::class), - $c->get(IConfig::class), - $c->get(LoggerInterface::class), - $c->get(Data::class), - $c->get(GroupHelper::class), - $c->get(UserSettings::class), - $c->get(IEmailValidator::class), - ); - }); + $context->registerService(Data::class, fn (ContainerInterface $c): \OCA\Activity\Data => new Data( + $c->get(IManager::class), + $c->get('ActivityConnectionAdapter'), + $c->get(LoggerInterface::class), + $c->get(IConfig::class), + $c->get(IRootFolder::class), + )); + + $context->registerService(MailQueueHandler::class, fn (ContainerInterface $c): \OCA\Activity\MailQueueHandler => new MailQueueHandler( + $c->get(IDateTimeFormatter::class), + $c->get('ActivityConnectionAdapter'), + $c->get(IMailer::class), + $c->get(IURLGenerator::class), + $c->get(IUserManager::class), + $c->get(IFactory::class), + $c->get(IManager::class), + $c->get(IValidator::class), + $c->get(IAppConfig::class), + $c->get(IConfig::class), + $c->get(LoggerInterface::class), + $c->get(Data::class), + $c->get(GroupHelper::class), + $c->get(UserSettings::class), + $c->get(IEmailValidator::class), + )); // Allow automatic DI for the View, until we migrated to Nodes API - $context->registerService(View::class, function () { - return new View(''); - }, false); + $context->registerService(View::class, fn (): \OC\Files\View => new View(''), false); $context->registerCapability(Capabilities::class); $context->registerEventListener(AddMissingIndicesEvent::class, AddMissingIndicesListener::class); @@ -158,9 +152,7 @@ public function boot(IBootContext $context): void { private function registerActivityConsumer(): void { $c = $this->getContainer(); - $c->get(IManager::class)->registerConsumer(function () use ($c) { - return $c->get(Consumer::class); - }); + $c->get(IManager::class)->registerConsumer(fn () => $c->get(Consumer::class)); } public function registerNotifier(): void { diff --git a/lib/BackgroundJob/ExpireActivities.php b/lib/BackgroundJob/ExpireActivities.php index de84d9246..66d862730 100644 --- a/lib/BackgroundJob/ExpireActivities.php +++ b/lib/BackgroundJob/ExpireActivities.php @@ -9,27 +9,24 @@ namespace OCA\Activity\BackgroundJob; -use OCA\Activity\Data; use OCP\AppFramework\Utility\ITimeFactory; use OCP\BackgroundJob\TimedJob; use OCP\IConfig; class ExpireActivities extends TimedJob { - /** @var Data */ - protected $data; /** @var IConfig */ protected $config; - public function __construct(ITimeFactory $time, - Data $data, - IConfig $config) { + public function __construct( + ITimeFactory $time, + protected \OCA\Activity\Data $data, + IConfig $config, + ) { parent::__construct($time); // Run once per day $this->setInterval(60 * 60 * 24); $this->setTimeSensitivity(self::TIME_INSENSITIVE); - - $this->data = $data; $this->config = $config; } diff --git a/lib/BackgroundJob/RemoteActivity.php b/lib/BackgroundJob/RemoteActivity.php index 27e0130f1..6ab022b23 100644 --- a/lib/BackgroundJob/RemoteActivity.php +++ b/lib/BackgroundJob/RemoteActivity.php @@ -26,7 +26,7 @@ public function __construct( #[\Override] protected function run($argument) { - call_user_func_array([$this, 'sendActivity'], $argument); + call_user_func_array($this->sendActivity(...), $argument); } protected function sendActivity($target, $token, $path, $internalType, $time, $actor, $secondPath = '') { @@ -73,18 +73,17 @@ protected function sendActivity($target, $token, $path, $internalType, $time, $a 'connect_timeout' => 10, ] ); - } catch (ClientException $e) { + } catch (ClientException) { } } /** - * @param ICloudId $cloudId * @param string $token * @return string */ protected function getServerURL(ICloudId $cloudId, $token) { $remote = $cloudId->getRemote(); - if (strpos($remote, 'http') !== 0) { + if (!str_starts_with($remote, 'http')) { $remote = 'https://' . $remote; } diff --git a/lib/Capabilities.php b/lib/Capabilities.php index ce0760091..a215c0680 100644 --- a/lib/Capabilities.php +++ b/lib/Capabilities.php @@ -19,7 +19,7 @@ class Capabilities implements ICapability { * Return this classes capabilities */ #[\Override] - public function getCapabilities() { + public function getCapabilities(): array { return [ 'activity' => [ 'apiv2' => [ diff --git a/lib/Command/SendEmails.php b/lib/Command/SendEmails.php index 107a5f5be..aa859a154 100644 --- a/lib/Command/SendEmails.php +++ b/lib/Command/SendEmails.php @@ -19,11 +19,6 @@ use Symfony\Component\Console\Output\OutputInterface; class SendEmails extends Base { - /** - * @param MailQueueHandler $queueHandler - * @param IConfig $config - * @param LoggerInterface $logger - */ public function __construct( protected MailQueueHandler $queueHandler, protected IConfig $config, @@ -57,11 +52,6 @@ protected function configure() { ; } - /** - * @param InputInterface $input - * @param OutputInterface $output - * @return int - */ #[\Override] protected function execute(InputInterface $input, OutputInterface $output): int { // We don't use time() but "time() - 1" here, so we don't run into @@ -97,11 +87,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int /** * @param string $argumentName - * @param CompletionContext $context * @return string[] */ #[\Override] - public function completeArgumentValues($argumentName, CompletionContext $context) { + public function completeArgumentValues($argumentName, CompletionContext $context): array { if ($argumentName === 'restrict-batching') { return ['asap', 'hourly', 'daily', 'weekly']; } diff --git a/lib/Consumer.php b/lib/Consumer.php index cbbd6d763..9a5ada260 100644 --- a/lib/Consumer.php +++ b/lib/Consumer.php @@ -34,9 +34,7 @@ public function __construct( /** * Send an event to the notifications of a user * - * @param IEvent $event * - * @return void */ #[\Override] public function receive(IEvent $event): void { @@ -61,10 +59,6 @@ public function receive(IEvent $event): void { /** * Send an event to the notifications of a bulk of users * - * @param IEvent $event - * @param array $affectedUserIds - * @param ISetting $setting - * @return void * @throws Exception */ #[\Override] diff --git a/lib/Controller/APIv1Controller.php b/lib/Controller/APIv1Controller.php index 81279154b..496129eef 100644 --- a/lib/Controller/APIv1Controller.php +++ b/lib/Controller/APIv1Controller.php @@ -19,11 +19,6 @@ class APIv1Controller extends OCSController { /** * @param string $appName - * @param IRequest $request - * @param Data $data - * @param GroupHelper $groupHelper - * @param UserSettings $userSettings - * @param CurrentUser $currentUser */ public function __construct( $appName, diff --git a/lib/Controller/APIv2Controller.php b/lib/Controller/APIv2Controller.php index dd2dbad5a..7f70b4744 100644 --- a/lib/Controller/APIv2Controller.php +++ b/lib/Controller/APIv2Controller.php @@ -134,7 +134,6 @@ public function getFilter(string $filter, int $since = 0, int $limit = 50, bool /** * @param string $object_type Object type to count downloads for (must be 'files') * @param int $object_id File ID - * @return DataResponse */ #[NoAdminRequired] public function getDownloadCount(string $object_type = 'files', int $object_id = 0): DataResponse { @@ -232,17 +231,15 @@ public function getHistogram( public function listFilters(): DataResponse { $filters = $this->activityManager->getFilters(); - $filters = array_map(function (IFilter $filter) { - return [ - 'id' => $filter->getIdentifier(), - 'name' => $filter->getName(), - 'icon' => $filter->getIcon(), - 'priority' => $filter->getPriority(), - ]; - }, $filters); + $filters = array_map(fn (IFilter $filter): array => [ + 'id' => $filter->getIdentifier(), + 'name' => $filter->getName(), + 'icon' => $filter->getIcon(), + 'priority' => $filter->getPriority(), + ], $filters); // php 5.6 has problems with usort and objects - usort($filters, static function (array $a, array $b) { + usort($filters, static function (array $a, array $b): int|float { if ($a['priority'] === $b['priority']) { return ($a['id'] > $b['id']) ? 1 : -1; } @@ -281,10 +278,10 @@ protected function get(string $filter, int $since, int $limit, bool $previews, s false, $this->searchCriteria ); - } catch (\OutOfBoundsException $e) { + } catch (\OutOfBoundsException) { // Invalid since argument return new DataResponse([], Http::STATUS_FORBIDDEN); - } catch (\BadMethodCallException $e) { + } catch (\BadMethodCallException) { // No activity settings enabled return new DataResponse([], Http::STATUS_NO_CONTENT); } @@ -442,7 +439,7 @@ protected function getPreviewFromPath(int $fileId, string $filePath, array $info protected function getPreviewPathFromMimeType(string $mimeType): string { $mimeTypeIcon = $this->mimeTypeDetector->mimeTypeIcon($mimeType); - if (substr($mimeTypeIcon, -4) === '.png') { + if (str_ends_with($mimeTypeIcon, '.png')) { $mimeTypeIcon = substr($mimeTypeIcon, 0, -4) . '.svg'; } diff --git a/lib/Controller/ActivitiesController.php b/lib/Controller/ActivitiesController.php index bdb270ddc..6eac42a96 100644 --- a/lib/Controller/ActivitiesController.php +++ b/lib/Controller/ActivitiesController.php @@ -18,7 +18,6 @@ use OCP\AppFramework\Services\IInitialState; use OCP\EventDispatcher\IEventDispatcher; use OCP\IConfig; -use OCP\IL10N; use OCP\IRequest; use OCP\IURLGenerator; @@ -27,14 +26,13 @@ class ActivitiesController extends Controller { public function __construct( string $appName, IRequest $request, - private ?string $userId, - private IConfig $config, - private Data $data, - private IL10N $l10n, - private IEventDispatcher $eventDispatcher, - private IInitialState $initialState, - private IURLGenerator $urlGenerator, - private IManager $activityManager, + private readonly ?string $userId, + private readonly IConfig $config, + private readonly Data $data, + private readonly IEventDispatcher $eventDispatcher, + private readonly IInitialState $initialState, + private readonly IURLGenerator $urlGenerator, + private readonly IManager $activityManager, ) { parent::__construct($appName, $request); } @@ -44,7 +42,6 @@ public function __construct( * @NoCSRFRequired * * @param string $filter - * @return TemplateResponse */ public function index(): TemplateResponse { return $this->showList('all'); @@ -53,9 +50,6 @@ public function index(): TemplateResponse { /** * @NoAdminRequired * @NoCSRFRequired - * - * @param string $filter - * @return TemplateResponse */ public function showList(string $filter = 'all'): TemplateResponse { $filter = $this->data->validateFilter($filter); @@ -109,7 +103,7 @@ protected function getRSSLink(): string { */ protected function getLinkList(): array { $filters = $this->activityManager->getFilters(); - usort($filters, static function (IFilter $a, IFilter $b) { + usort($filters, static function (IFilter $a, IFilter $b): int { if ($a->getPriority() === $b->getPriority()) { return (int)($a->getIdentifier() > $b->getIdentifier()); } diff --git a/lib/Controller/FeedController.php b/lib/Controller/FeedController.php index 982d7f878..1cca9783e 100644 --- a/lib/Controller/FeedController.php +++ b/lib/Controller/FeedController.php @@ -61,7 +61,7 @@ public function show(string $filter = 'all'): TemplateResponse { $description = $this->l->t('Personal activity feed for %s', $user); $data = $this->data->get($this->helper, $this->settings, $user, 0, self::DEFAULT_PAGE_SIZE, 'desc', $filter); $activities = $data['data']; - } catch (\UnexpectedValueException $e) { + } catch (\UnexpectedValueException) { $this->l = $this->l10nFactory->get('activity'); $description = $this->l->t('Your feed URL is invalid'); $activities = [ @@ -86,7 +86,7 @@ public function show(string $filter = 'all'): TemplateResponse { 'activities' => $activities, ]); - if (stripos($this->request->getHeader('accept'), 'application/rss+xml') !== false) { + if (stripos((string)$this->request->getHeader('accept'), 'application/rss+xml') !== false) { $response->addHeader('Content-Type', 'application/rss+xml'); } else { $response->addHeader('Content-Type', 'text/xml; charset=UTF-8'); diff --git a/lib/Controller/RemoteActivityController.php b/lib/Controller/RemoteActivityController.php index d1c0ac403..12e7cbef6 100644 --- a/lib/Controller/RemoteActivityController.php +++ b/lib/Controller/RemoteActivityController.php @@ -54,7 +54,6 @@ public function __construct( * @param string[] $object * @param string[] $target * @param string[] $origin - * @return DataResponse */ #[BruteForceProtection(action: 'receiveActivity')] public function receiveActivity($token, array $to, array $actor, $type, $updated, array $object = [], array $target = [], array $origin = []): DataResponse { @@ -120,7 +119,7 @@ public function receiveActivity($token, array $to, array $actor, $type, $updated return $response; } - $normalizedActorServer = rtrim(strtolower(preg_replace('/^https?:\/\//', '', $actorServer)), '/'); + $normalizedActorServer = rtrim(strtolower((string)preg_replace('/^https?:\/\//', '', (string)$actorServer)), '/'); $normalizedShareRemote = rtrim(strtolower(preg_replace('/^https?:\/\//', '', $share['remote'])), '/'); if ($normalizedActorServer !== $normalizedShareRemote) { return new DataResponse([], Http::STATUS_FORBIDDEN); @@ -208,9 +207,6 @@ public function receiveActivity($token, array $to, array $actor, $type, $updated return new DataResponse(); } - /** - * @param null|string $path2 - */ protected function getSubject(string $type, string $path, ?string $path2) { switch ($type) { case 'Create': @@ -236,15 +232,11 @@ protected function getSubject(string $type, string $path, ?string $path2) { * @return string */ protected function translateType($type) { - switch ($type) { - case 'Create': - return Files::TYPE_SHARE_CREATED; - case 'Move': - case 'Update': - return Files::TYPE_FILE_CHANGED; - case 'Delete': - return Files::TYPE_SHARE_DELETED; - } - return ''; + return match ($type) { + 'Create' => Files::TYPE_SHARE_CREATED, + 'Move', 'Update' => Files::TYPE_FILE_CHANGED, + 'Delete' => Files::TYPE_SHARE_DELETED, + default => '', + }; } } diff --git a/lib/CurrentUser.php b/lib/CurrentUser.php index 661413135..169225d09 100644 --- a/lib/CurrentUser.php +++ b/lib/CurrentUser.php @@ -118,7 +118,7 @@ protected function getPublicShare(): ?IShare { try { return $this->shareManager->getShareByToken($token); - } catch (ShareNotFound $e) { + } catch (ShareNotFound) { return null; } } diff --git a/lib/Dashboard/ActivityWidget.php b/lib/Dashboard/ActivityWidget.php index e3266419a..1fbcebbbb 100644 --- a/lib/Dashboard/ActivityWidget.php +++ b/lib/Dashboard/ActivityWidget.php @@ -27,23 +27,19 @@ use OCP\Util; class ActivityWidget implements IAPIWidget, IButtonWidget, IIconWidget, IReloadableWidget, IOptionWidget { - private Data $data; - private IL10N $l10n; - private GroupHelper $helper; - private UserSettings $settings; - private IDateTimeFormatter $dateTimeFormatter; - private IURLGenerator $urlGenerator; - - public function __construct(IL10N $l10n, - Data $data, - GroupHelper $helper, - UserSettings $settings, + private readonly IL10N $l10n; + private readonly IDateTimeFormatter $dateTimeFormatter; + private readonly IURLGenerator $urlGenerator; + + public function __construct( + IL10N $l10n, + private readonly Data $data, + private readonly GroupHelper $helper, + private readonly UserSettings $settings, IURLGenerator $urlGenerator, - IDateTimeFormatter $dateTimeFormatter) { - $this->data = $data; + IDateTimeFormatter $dateTimeFormatter, + ) { $this->l10n = $l10n; - $this->helper = $helper; - $this->settings = $settings; $this->dateTimeFormatter = $dateTimeFormatter; $this->urlGenerator = $urlGenerator; } @@ -126,15 +122,13 @@ public function getItems(string $userId, ?string $since = null, int $limit = 7): '', 0 ); - return array_map(function (array $activity) { - return new WidgetItem( - $activity['subject'], - $this->dateTimeFormatter->formatTimeSpan($activity['timestamp']), - $activity['link'], - $activity['icon'], - (string)$activity['activity_id'] - ); - }, array_slice($activities['data'], 0, $limit)); + return array_map(fn (array $activity): \OCP\Dashboard\Model\WidgetItem => new WidgetItem( + $activity['subject'], + $this->dateTimeFormatter->formatTimeSpan($activity['timestamp']), + $activity['link'], + $activity['icon'], + (string)$activity['activity_id'] + ), array_slice($activities['data'], 0, $limit)); } /** @@ -155,7 +149,7 @@ public function getItemsV2(string $userId, ?string $since = null, int $limit = 7 '', 0 ); - $items = array_map(function (array $activity) { + $items = array_map(function (array $activity): \OCP\Dashboard\Model\WidgetItem { $userAvatarUrl = ''; if ($activity['user'] !== '') { $userAvatarUrl = $this->urlGenerator->getAbsoluteURL( diff --git a/lib/Data.php b/lib/Data.php index b10278b82..96b9876f5 100644 --- a/lib/Data.php +++ b/lib/Data.php @@ -50,9 +50,6 @@ public function __construct( /** * Check if the event should be processed (not excluded and has valid target) - * - * @param IEvent $event - * @return bool */ private function shouldSend(IEvent $event): bool { return $event->getAffectedUser() !== '' && !$this->isExcludedAuthor($event); @@ -60,9 +57,6 @@ private function shouldSend(IEvent $event): bool { /** * Check if the event's author is excluded from activity logging - * - * @param IEvent $event - * @return bool */ private function isExcludedAuthor(IEvent $event): bool { $excludedUsers = $this->config->getSystemValue('activity_log_exclude_users', []); @@ -87,9 +81,6 @@ private function isExcludedAuthor(IEvent $event): bool { /** * Send an event into the activity stream - * - * @param IEvent $event - * @return int */ public function send(IEvent $event): int { if (!$this->shouldSend($event)) { @@ -144,8 +135,6 @@ public function send(IEvent $event): int { * for a batch of users that are affected by the same event * (ex. Call Started, Call ended) * - * @param IEvent $event - * @param array $affectedUsers * @return array * @throws Exception */ @@ -215,9 +204,7 @@ public function bulkSend(IEvent $event, array $affectedUsers): array { /** * Send an event as email * - * @param IEvent $event * @param int $latestSendTime Activity $timestamp + batch setting of $affectedUser - * @return bool */ public function storeMail(IEvent $event, int $latestSendTime): bool { if (!$this->shouldSend($event)) { @@ -274,7 +261,6 @@ public function storeMail(IEvent $event, int $latestSendTime): bool { * * @param bool $returnEvents return only the events * @param SearchCriteria|null $search Additional search term and date range restrictions - * @return array * */ public function get(GroupHelper $groupHelper, UserSettings $userSettings, string $user, int $since, int $limit, string $sort, string $filter, string $objectType = '', int $objectId = 0, bool $returnEvents = false, ?SearchCriteria $search = null): array { @@ -526,10 +512,6 @@ private function applySearchCriteria(IQueryBuilder $query, SearchCriteria $crite } /** - * @param IQueryBuilder $query - * @param string $user - * @param int $since - * @param string $sort * * @return array Headers that should be set on the response * @@ -590,7 +572,6 @@ private function getFirstKnownActivityHeader(string $user, string $sort): array * @param string $user The affected user (file owner) * @param int $objectId The file ID * @param int|null $since Optional Unix timestamp; only count activities at or after this time - * @return int */ public function countDownloads(string $user, int $objectId, ?int $since = null): int { $query = $this->connection->getQueryBuilder(); @@ -635,7 +616,7 @@ public function validateFilter(string $filterValue): string { * * @param int $expireDays Minimum 1 day */ - public function expire($expireDays = 365) { + public function expire($expireDays = 365): void { $ttl = (60 * 60 * 24 * max(1, $expireDays)); $timelimit = time() - $ttl; $conditions = [ @@ -683,7 +664,6 @@ public function deleteActivities(array $conditions): void { * Using andWhere() for every condition is required: where() would replace any * previously set restriction, silently dropping all but the last condition. * - * @param IQueryBuilder $query * @param array $conditions List of [column, value, operator] tuples; operator defaults to '=' * @psalm-param list $conditions */ @@ -723,10 +703,6 @@ public function getById(int $activityId): ?IEvent { /** * Get the id of the first activity in the stream since a specified time - * - * @param string $user - * @param int $timestamp - * @return int */ public function getFirstActivitySince(string $user, int $timestamp): int { $query = $this->connection->getQueryBuilder(); @@ -744,9 +720,6 @@ public function getFirstActivitySince(string $user, int $timestamp): int { /** * Get the number of activity items and the latest activity id since the specified activity * - * @param string $user - * @param int $since - * @param bool $byOthers * @return array */ public function getActivitySince(string $user, int $since, bool $byOthers) { @@ -768,9 +741,6 @@ public function getActivitySince(string $user, int $since, bool $byOthers) { /** * Add galera safe delete chunking if using mysql * Stops us hitting wsrep_max_ws_rows when large row counts are deleted - * - * @param array $conditions - * @return void */ private function deleteActivitiesForMySQL(array $conditions): void { $query = $this->connection->getQueryBuilder(); @@ -785,9 +755,7 @@ private function deleteActivitiesForMySQL(array $conditions): void { if ($count === 0) { return; } - $ids = array_map(static function (array $id) { - return (int)$id[0]; - }, $result->fetchAll(\PDO::FETCH_NUM)); + $ids = array_map(static fn (array $id): int => (int)$id[0], $result->fetchAll(\PDO::FETCH_NUM)); $result->closeCursor(); $queryResult = 0; diff --git a/lib/DigestSender.php b/lib/DigestSender.php index dc3b875e4..e8cac1f1d 100644 --- a/lib/DigestSender.php +++ b/lib/DigestSender.php @@ -25,18 +25,18 @@ class DigestSender { public const ACTIVITY_LIMIT = 20; public function __construct( - private IConfig $config, - private Data $data, - private UserSettings $userSettings, - private GroupHelper $groupHelper, - private IMailer $mailer, - private IManager $activityManager, - private IUserManager $userManager, - private IURLGenerator $urlGenerator, - private Defaults $defaults, - private IFactory $l10nFactory, - private IDateTimeFormatter $dateTimeFormatter, - private LoggerInterface $logger, + private readonly IConfig $config, + private readonly Data $data, + private readonly UserSettings $userSettings, + private readonly GroupHelper $groupHelper, + private readonly IMailer $mailer, + private readonly IManager $activityManager, + private readonly IUserManager $userManager, + private readonly IURLGenerator $urlGenerator, + private readonly Defaults $defaults, + private readonly IFactory $l10nFactory, + private readonly IDateTimeFormatter $dateTimeFormatter, + private readonly LoggerInterface $logger, ) { } @@ -138,7 +138,7 @@ private function updateLastSentForUser(IUser $user, int $now): void { $this->config->setUserValue($uid, 'activity', 'activity_digest_last_send', (string)$lastActivityId); } - public function sendDigestForUser(IUser $user, int $now, string $timezone, string $language) { + public function sendDigestForUser(IUser $user, int $now, string $timezone, string $language): void { $uid = $user->getUID(); $l10n = $this->l10nFactory->get('activity', $language); $this->groupHelper->setL10n($l10n); @@ -201,7 +201,7 @@ public function sendDigestForUser(IUser $user, int $now, string $timezone, strin $andMoreText = $l10n->n('and %n more…', 'and %n more…', $skippedCount); $url = $this->urlGenerator->linkToRouteAbsolute('activity.Activities.showList', [ 'filter' => 'all' ]); $template->addBodyListItem( - '' . htmlspecialchars($andMoreText) . '', + '' . htmlspecialchars((string)$andMoreText) . '', plainText: $andMoreText, ); } @@ -229,10 +229,6 @@ public function sendDigestForUser(IUser $user, int $now, string $timezone, strin } } - /** - * @param IEvent $event - * @return string - */ protected function getHTMLSubject(IEvent $event): string { if ($event->getRichSubject() === '') { return htmlspecialchars($event->getParsedSubject()); diff --git a/lib/FilesHooks.php b/lib/FilesHooks.php index 952114aa1..06d653eea 100644 --- a/lib/FilesHooks.php +++ b/lib/FilesHooks.php @@ -77,7 +77,7 @@ public function __construct( * * @param string $path Path of the file that has been created */ - public function fileCreate($path) { + public function fileCreate($path): void { if ($path === '/' || $path === '' || $path === null) { return; } @@ -94,7 +94,7 @@ public function fileCreate($path) { * * @param string $path Path of the file that has been modified */ - public function fileUpdate($path) { + public function fileUpdate($path): void { $this->addNotificationsForFileAction($path, Files::TYPE_FILE_CHANGED, 'changed_self', 'changed_by'); } @@ -103,7 +103,7 @@ public function fileUpdate($path) { * * @param string $path Path of the file that has been deleted */ - public function fileDelete($path) { + public function fileDelete($path): void { $this->addNotificationsForFileAction($path, Files::TYPE_SHARE_DELETED, 'deleted_self', 'deleted_by'); } @@ -112,11 +112,11 @@ public function fileDelete($path) { * * @param string $path Path of the file that has been restored */ - public function fileRestore($path) { + public function fileRestore($path): void { $this->addNotificationsForFileAction($path, Files::TYPE_SHARE_RESTORED, 'restored_self', 'restored_by'); } - private function getFileChangeActivitySettings(int $fileId, array $users, $type = Files::TYPE_FILE_CHANGED): array { + private function getFileChangeActivitySettings(int $fileId, array $users, string $type = Files::TYPE_FILE_CHANGED): array { $filteredEmailUsers = $this->userSettings->filterUsersBySetting($users, 'email', $type); $filteredNotificationUsers = $this->userSettings->filterUsersBySetting($users, 'notification', $type); @@ -143,7 +143,7 @@ private function getFileChangeActivitySettings(int $fileId, array $users, $type */ protected function addNotificationsForFileAction($filePath, $activityType, $subject, $subjectBy) { // Do not add activities for .part-files - if (substr($filePath, -5) === '.part') { + if (str_ends_with($filePath, '.part')) { return; } @@ -200,7 +200,7 @@ protected function generateRemoteActivity(array $remoteUsers, string $type, int $arguments = [ $remoteUser, $info['token'], - $ownerPath !== false ? substr($ownerPath, strlen($info['node_path'])) : $info['node_path'], + $ownerPath !== false ? substr((string)$ownerPath, strlen($info['node_path'])) : $info['node_path'], $type, $time, $actor, @@ -220,8 +220,8 @@ protected function generateRemoteActivity(array $remoteUsers, string $type, int * @param string $oldPath Path of the file that has been moved * @param string $newPath Path of the file that has been moved */ - public function fileMove($oldPath, $newPath) { - if (substr($oldPath, -5) === '.part' || substr($newPath, -5) === '.part') { + public function fileMove($oldPath, $newPath): void { + if (str_ends_with($oldPath, '.part') || str_ends_with($newPath, '.part')) { // Do not add activities for .part-files $this->moveCase = false; return; @@ -242,7 +242,7 @@ public function fileMove($oldPath, $newPath) { return; } - if (strpos($oldDir, $newDir) === 0) { + if (str_starts_with($oldDir, $newDir)) { /** * a/b/c moved to a/c * @@ -252,7 +252,7 @@ public function fileMove($oldPath, $newPath) { * - a/ shared: move/rename */ $this->moveCase = 'moveUp'; - } elseif (strpos($newDir, $oldDir) === 0) { + } elseif (str_starts_with($newDir, $oldDir)) { /** * a/b moved to a/c/b * @@ -307,7 +307,7 @@ public function fileMove($oldPath, $newPath) { * @param string $oldPath Path of the file that has been moved * @param string $newPath Path of the file that has been moved */ - public function fileMovePost($oldPath, $newPath) { + public function fileMovePost($oldPath, $newPath): void { // Do not add activities for .part-files if ($this->moveCase === false) { return; @@ -478,10 +478,8 @@ protected function fileMoving($oldPath, $newPath) { * @param string[] $pathMap * @param int $fileId * @param string $oldFileName - * @param array $filteredEmailUsers - * @param array $filteredNotificationUsers */ - protected function generateDeleteActivities($users, $pathMap, $fileId, $oldFileName, array $filteredEmailUsers, array $filteredNotificationUsers) { + protected function generateDeleteActivities($users, array $pathMap, $fileId, $oldFileName, array $filteredEmailUsers, array $filteredNotificationUsers) { if (empty($users)) { return; } @@ -519,10 +517,8 @@ protected function generateDeleteActivities($users, $pathMap, $fileId, $oldFileN * @param string[] $pathMap * @param int $fileId * @param string $fileName - * @param array $filteredEmailUsers - * @param array $filteredNotificationUsers */ - protected function generateAddActivities($users, $pathMap, $fileId, $fileName, array $filteredEmailUsers, array $filteredNotificationUsers) { + protected function generateAddActivities($users, array $pathMap, $fileId, $fileName, array $filteredEmailUsers, array $filteredNotificationUsers) { if (empty($users)) { return; } @@ -563,10 +559,8 @@ protected function generateAddActivities($users, $pathMap, $fileId, $fileName, a * @param string $oldFileName * @param int $newParentId * @param string $fileName - * @param array $filteredEmailUsers - * @param array $filteredNotificationUsers */ - protected function generateMoveActivities($users, $beforePathMap, $afterPathMap, $fileId, $oldFileName, $newParentId, $fileName, array $filteredEmailUsers, array $filteredNotificationUsers) { + protected function generateMoveActivities($users, array $beforePathMap, array $afterPathMap, $fileId, $oldFileName, $newParentId, $fileName, array $filteredEmailUsers, array $filteredNotificationUsers) { if (empty($users)) { return; } @@ -615,7 +609,7 @@ protected function getUserPathsFromPath($path, $uidOwner) { try { $node = $this->rootFolder->getUserFolder($uidOwner)->get($path); - } catch (NotFoundException $e) { + } catch (NotFoundException) { return $emptyResult; } @@ -646,14 +640,13 @@ protected function getVisiblePath(string $absolutePath): string { * Return the source * * @param string $path - * @return array */ - protected function getSourcePathAndOwner($path) { + protected function getSourcePathAndOwner($path): array { $view = Filesystem::getView(); try { $owner = $view->getOwner($path); $owner = $owner === '' ? null : $owner; - } catch (NotFoundException $e) { + } catch (NotFoundException) { $owner = null; } $fileId = 0; @@ -663,7 +656,7 @@ protected function getSourcePathAndOwner($path) { /** @var \OCP\Files\Storage\IStorage $storage */ [$storage,] = $view->resolvePath($path); - if ($owner !== null && !$storage->instanceOfStorage('OCA\Files_Sharing\External\Storage')) { + if ($owner !== null && !$storage->instanceOfStorage(\OCA\Files_Sharing\External\Storage::class)) { Filesystem::initMountPoints($owner); } else { // Probably a remote user, let's try to at least generate activities @@ -691,7 +684,7 @@ protected function getSourcePathAndOwner($path) { * * @param IShare $share the share from the event */ - public function share($share) { + public function share($share): void { switch ($share->getShareType()) { case IShare::TYPE_USER: $this->shareWithUser( @@ -732,7 +725,6 @@ public function share($share) { /** * Sharing a file or folder with a user * - * @param string $shareWith * @param Node $fileSource File that is being shared * @param string $fileTarget File path */ @@ -755,7 +747,6 @@ protected function shareWithUser(string $shareWith, Node $fileSource, string $fi /** * Sharing a file or folder with a group * - * @param string $shareWith * @param Node $fileSource File that is being shared * @param string $fileTarget File path * @param int $shareId The Share ID of this share @@ -787,7 +778,6 @@ protected function shareWithGroup(string $shareWith, Node $fileSource, string $f * Sharing a file or folder via link/public * * @param Node $fileSource File that is being shared - * @param string $sharedBy */ protected function shareByLink(Node $fileSource, string $sharedBy) { $relativePath = $this->getUserRelativePath($sharedBy, $fileSource->getPath()); @@ -804,7 +794,6 @@ protected function shareByLink(Node $fileSource, string $sharedBy) { /** * Sharing a file or folder with a team * - * @param string $shareWith * @param Node $fileSource File that is being shared * @param string $fileTarget File path * @param int $shareId The Share ID of this share @@ -818,7 +807,7 @@ protected function shareWithTeam(string $shareWith, Node $fileSource, string $fi try { $team = $this->teamManager->getCircle($shareWith); $members = $team->getInheritedMembers(); - $members = array_filter($members, fn ($member) => $member->getUserType() === Member::TYPE_USER); + $members = array_filter($members, fn ($member): bool => $member->getUserType() === Member::TYPE_USER); $userIds = array_map(fn ($member) => $member->getUserId(), $members); } catch (\Throwable $e) { $this->logger->debug('Fetching team members for share activity failed', ['exception' => $e]); @@ -841,10 +830,9 @@ protected function shareWithTeam(string $shareWith, Node $fileSource, string $fi /** * Manage unsharing events * - * @param IShare $share * @throws \OCP\Files\NotFoundException */ - public function unShare(IShare $share) { + public function unShare(IShare $share): void { if (!in_array($share->getNodeType(), ['file', 'folder'], true) || $this->isDeletedNode($share->getShareOwner(), $share->getNodeId())) { return; } @@ -860,10 +848,9 @@ public function unShare(IShare $share) { /** * Manage unsharing a share from self only events * - * @param IShare $share * @throws \OCP\Files\NotFoundException */ - public function unShareSelf(IShare $share) { + public function unShareSelf(IShare $share): void { if (!in_array($share->getNodeType(), ['file', 'folder'], true)) { return; } @@ -877,7 +864,6 @@ public function unShareSelf(IShare $share) { /** * Unharing a file or folder from a user * - * @param IShare $share * @throws \OCP\Files\NotFoundException */ protected function unshareFromUser(IShare $share) { @@ -916,7 +902,6 @@ protected function unshareFromUser(IShare $share) { /** * Unharing a file or folder from a user * - * @param IShare $share * @throws \OCP\Files\NotFoundException */ protected function selfUnshareFromUser(IShare $share) { @@ -932,7 +917,6 @@ protected function selfUnshareFromUser(IShare $share) { /** * Unsharing a file or folder from a group * - * @param IShare $share * @throws \OCP\Files\NotFoundException */ protected function unshareFromGroup(IShare $share) { @@ -978,7 +962,6 @@ protected function unshareFromGroup(IShare $share) { /** * Unsharing a file or folder from self from a group share * - * @param IShare $share * @throws \OCP\Files\NotFoundException */ protected function unshareFromSelfGroup(IShare $share) { @@ -994,7 +977,6 @@ protected function unshareFromSelfGroup(IShare $share) { /** * Sharing a file or folder via link/public * - * @param IShare $share * @throws \OCP\Files\NotFoundException */ protected function unshareLink(IShare $share) { @@ -1029,7 +1011,6 @@ protected function unshareLink(IShare $share) { /** * @param string[] $usersIds - * @param string $actionUser * @param Node $fileSource File that is being shared * @param string $fileTarget File path * @param int $shareId The Share ID of this share @@ -1082,11 +1063,9 @@ protected function addNotificationsForUsers(array $usersIds, string $actionUser, * Check when there was a naming conflict and the target is different * for some of the users * - * @param array $affectedUsers * @param int $shareId - * @return mixed */ - protected function fixPathsForShareExceptions(array $affectedUsers, $shareId) { + protected function fixPathsForShareExceptions(array $affectedUsers, $shareId): array { $queryBuilder = $this->connection->getQueryBuilder(); $queryBuilder->select(['share_with', 'file_target']) ->from('share') @@ -1103,10 +1082,6 @@ protected function fixPathsForShareExceptions(array $affectedUsers, $shareId) { /** * Add notifications for the user that shares a file/folder - * - * @param string $subject - * @param string $shareWith - * @param Node $fileSource */ protected function shareNotificationForSharer(string $subject, string $shareWith, Node $fileSource, ?string $path = null) { $sharer = $this->currentUser->getUID(); @@ -1139,11 +1114,6 @@ protected function reshareNotificationForSharer(string $owner, string $subject, /** * Add notifications for the owners whose files have been reshared - * - * @param string $sharedBy - * @param string $subject - * @param string $shareWith - * @param Node $fileSource */ protected function shareNotificationForOriginalOwners(string $sharedBy, string $subject, string $shareWith, Node $fileSource) { $mount = $fileSource->getMountPoint(); @@ -1280,11 +1250,6 @@ protected function rollbackActivityTransaction(bool $shouldFlush): void { } } - /** - * @param int $fileId - * - * @return array - */ private function getAffectedUsersFromCachedMounts(int $fileId): array { $affectedUsers = $cachedMounts = []; $mountsForFile = $this->userMountCache->getMountsForFileId($fileId); @@ -1301,28 +1266,24 @@ private function getAffectedUsersFromCachedMounts(int $fileId): array { ]; } - $unrelatedUsers = $this->getUnrelatedUsers($fileId, $cachedMounts); + $unrelatedUsers = $this->getUnrelatedUsers($cachedMounts); - return array_filter($affectedUsers, function (string $userId) use ($unrelatedUsers): bool { - return !in_array($userId, $unrelatedUsers, true); - }, ARRAY_FILTER_USE_KEY); + return array_filter($affectedUsers, fn (string $userId): bool => !in_array($userId, $unrelatedUsers, true), ARRAY_FILTER_USE_KEY); } /** * returns an array of users that have confirmed no access to fileId * - * @param int $fileId - * @param array $cachedMounts * * @return string[] list of unrelated userIds */ - private function getUnrelatedUsers(int $fileId, array $cachedMounts): array { + private function getUnrelatedUsers(array $cachedMounts): array { /** @var \OCA\GroupFolders\ACL\RuleManager $ruleManager */ /** @var \OCA\GroupFolders\Folder\FolderManager $folderManager */ try { $ruleManager = \OCP\Server::get(\OCA\GroupFolders\ACL\RuleManager::class); $folderManager = \OCP\Server::get(\OCA\GroupFolders\Folder\FolderManager::class); - } catch (\Throwable $e) { + } catch (\Throwable) { return []; // if we have no access to RuleManager, we cannot filter unrelated users } @@ -1363,7 +1324,7 @@ private function getUnrelatedUsers(int $fileId, array $cachedMounts): array { $folderPath = '/' . $cachedMount['rootInternalPath']; $path = $cachedMount['internalPath']; - } catch (\Exception $e) { + } catch (\Exception) { // in case of issue during the process, we can imagine the user have no access to the file $usersToCheck[] = $cachedMount['userId']; continue; // we'll catch rules on next user with access to the file @@ -1373,7 +1334,7 @@ private function getUnrelatedUsers(int $fileId, array $cachedMounts): array { $paths = [$folderPath]; while ($path !== '') { $paths[] = $folderPath . '/' . $path; - $path = dirname($path); + $path = dirname((string)$path); if ($path === '.' || $path === '/') { $path = ''; } @@ -1423,9 +1384,7 @@ private function getUnrelatedUsers(int $fileId, array $cachedMounts): array { if ($group === null) { continue; } - $userIds = array_map(function (IUser $user): string { - return $user->getUID(); - }, $group->getUsers()); + $userIds = array_map(fn (IUser $user): string => $user->getUID(), $group->getUsers()); // merge current user list with members of the group $usersToCheck = array_values(array_unique(array_merge($usersToCheck, $userIds))); @@ -1446,7 +1405,7 @@ private function getUnrelatedUsers(int $fileId, array $cachedMounts): array { if ($node->isReadable()) { continue; // overkill ? as rootFolder->get() would throw an exception if file is not available } - } catch (\Throwable $e) { + } catch (\Throwable) { } $filteredUsers[] = $userId; @@ -1460,7 +1419,7 @@ private function isDeletedNode(string $owner, int $nodeId): bool { $userFolder = $this->rootFolder->getUserFolder($owner); $node = $userFolder->getFirstNodeById($nodeId); return $node === null; - } catch (NotFoundException $e) { + } catch (NotFoundException) { return true; } } diff --git a/lib/FilesHooksStatic.php b/lib/FilesHooksStatic.php index 81d8d81e1..82171020a 100644 --- a/lib/FilesHooksStatic.php +++ b/lib/FilesHooksStatic.php @@ -20,7 +20,7 @@ protected static function getHooks(): FilesHooks { * Store the create hook events * @param array $params The hook params */ - public static function fileCreate($params) { + public static function fileCreate(array $params): void { self::getHooks()->fileCreate($params['path']); } @@ -28,7 +28,7 @@ public static function fileCreate($params) { * Store the update hook events * @param array $params The hook params */ - public static function fileUpdate($params) { + public static function fileUpdate(array $params): void { self::getHooks()->fileUpdate($params['path']); } @@ -36,7 +36,7 @@ public static function fileUpdate($params) { * Store the delete hook events * @param array $params The hook params */ - public static function fileDelete($params) { + public static function fileDelete(array $params): void { self::getHooks()->fileDelete($params['path']); } @@ -44,7 +44,7 @@ public static function fileDelete($params) { * Store the rename hook events * @param array $params The hook params */ - public static function fileMove($params) { + public static function fileMove(array $params): void { self::getHooks()->fileMove($params['oldpath'], $params['newpath']); } @@ -52,7 +52,7 @@ public static function fileMove($params) { * Store the rename hook events * @param array $params The hook params */ - public static function fileMovePost($params) { + public static function fileMovePost(array $params): void { self::getHooks()->fileMovePost($params['oldpath'], $params['newpath']); } @@ -60,7 +60,7 @@ public static function fileMovePost($params) { * Store the restore hook events * @param array $params The hook params */ - public static function fileRestore($params) { + public static function fileRestore(array $params): void { self::getHooks()->fileRestore($params['filePath']); } } diff --git a/lib/Filter/AllFilter.php b/lib/Filter/AllFilter.php index feaa5fdf5..75cb80517 100644 --- a/lib/Filter/AllFilter.php +++ b/lib/Filter/AllFilter.php @@ -18,10 +18,6 @@ class AllFilter implements IFilter { /** @var IURLGenerator */ protected $url; - /** - * @param IL10N $l - * @param IURLGenerator $url - */ public function __construct(IL10N $l, IURLGenerator $url) { $this->l = $l; $this->url = $url; @@ -32,7 +28,7 @@ public function __construct(IL10N $l, IURLGenerator $url) { * @since 9.2.0 */ #[\Override] - public function getIdentifier() { + public function getIdentifier(): string { return 'all'; } @@ -46,11 +42,10 @@ public function getName() { } /** - * @return int * @since 9.2.0 */ #[\Override] - public function getPriority() { + public function getPriority(): int { return 0; } @@ -69,7 +64,7 @@ public function getIcon() { * @since 9.2.0 */ #[\Override] - public function filterTypes(array $types) { + public function filterTypes(array $types): array { return $types; } @@ -78,7 +73,7 @@ public function filterTypes(array $types) { * @since 9.2.0 */ #[\Override] - public function allowedApps() { + public function allowedApps(): array { return []; } } diff --git a/lib/Filter/ByFilter.php b/lib/Filter/ByFilter.php index 7533ce02f..e48aa36b8 100644 --- a/lib/Filter/ByFilter.php +++ b/lib/Filter/ByFilter.php @@ -18,10 +18,6 @@ class ByFilter implements IFilter { /** @var IURLGenerator */ protected $url; - /** - * @param IL10N $l - * @param IURLGenerator $url - */ public function __construct(IL10N $l, IURLGenerator $url) { $this->l = $l; $this->url = $url; @@ -32,7 +28,7 @@ public function __construct(IL10N $l, IURLGenerator $url) { * @since 9.2.0 */ #[\Override] - public function getIdentifier() { + public function getIdentifier(): string { return 'by'; } @@ -46,11 +42,10 @@ public function getName() { } /** - * @return int * @since 9.2.0 */ #[\Override] - public function getPriority() { + public function getPriority(): int { return 2; } @@ -69,7 +64,7 @@ public function getIcon() { * @since 9.2.0 */ #[\Override] - public function filterTypes(array $types) { + public function filterTypes(array $types): array { return $types; } @@ -78,7 +73,7 @@ public function filterTypes(array $types) { * @since 9.2.0 */ #[\Override] - public function allowedApps() { + public function allowedApps(): array { return []; } } diff --git a/lib/Filter/SelfFilter.php b/lib/Filter/SelfFilter.php index c98e4a49c..30dfcbf70 100644 --- a/lib/Filter/SelfFilter.php +++ b/lib/Filter/SelfFilter.php @@ -18,10 +18,6 @@ class SelfFilter implements IFilter { /** @var IURLGenerator */ protected $url; - /** - * @param IL10N $l - * @param IURLGenerator $url - */ public function __construct(IL10N $l, IURLGenerator $url) { $this->l = $l; $this->url = $url; @@ -32,7 +28,7 @@ public function __construct(IL10N $l, IURLGenerator $url) { * @since 9.2.0 */ #[\Override] - public function getIdentifier() { + public function getIdentifier(): string { return 'self'; } @@ -46,11 +42,10 @@ public function getName() { } /** - * @return int * @since 9.2.0 */ #[\Override] - public function getPriority() { + public function getPriority(): int { return 1; } @@ -69,7 +64,7 @@ public function getIcon() { * @since 9.2.0 */ #[\Override] - public function filterTypes(array $types) { + public function filterTypes(array $types): array { return $types; } @@ -78,7 +73,7 @@ public function filterTypes(array $types) { * @since 9.2.0 */ #[\Override] - public function allowedApps() { + public function allowedApps(): array { return []; } } diff --git a/lib/GroupHelper.php b/lib/GroupHelper.php index fb5909ef7..d844f7142 100644 --- a/lib/GroupHelper.php +++ b/lib/GroupHelper.php @@ -64,12 +64,12 @@ public function addEvent(int $id, IEvent $event): void { } if (!$event->isValidParsed()) { - $this->logger->info('Activity event was claimed to be parsed, but was not fully parsed by ' . get_class($provider) . ' [app: ' . $event->getApp() . ', subject: ' . $event->getSubject() . ']', ['app' => $event->getApp()]); + $this->logger->info('Activity event was claimed to be parsed, but was not fully parsed by ' . $provider::class . ' [app: ' . $event->getApp() . ', subject: ' . $event->getSubject() . ']', ['app' => $event->getApp()]); } } catch (UnknownActivityException) { } catch (\InvalidArgumentException) { // todo 39.0.0 Log as error - $this->logger->warning(get_class($provider) . '::parse() threw \InvalidArgumentException which is deprecated. Throw \OCP\Activity\Exceptions\UnknownActivityException when the event is not known to your provider and otherwise handle all \InvalidArgumentException yourself.', ['app' => $event->getApp()]); + $this->logger->warning($provider::class . '::parse() threw \InvalidArgumentException which is deprecated. Throw \OCP\Activity\Exceptions\UnknownActivityException when the event is not known to your provider and otherwise handle all \InvalidArgumentException yourself.', ['app' => $event->getApp()]); } catch (\Throwable $e) { $this->logger->error('Error while parsing activity event', ['exception' => $e, 'app' => $event->getApp()]); } diff --git a/lib/Listener/SetUserDefaults.php b/lib/Listener/SetUserDefaults.php index e676cc6b8..80f7861fa 100644 --- a/lib/Listener/SetUserDefaults.php +++ b/lib/Listener/SetUserDefaults.php @@ -22,8 +22,8 @@ class SetUserDefaults implements IEventListener { public function __construct( - private IConfig $config, - private IAppConfig $appConfig, + private readonly IConfig $config, + private readonly IAppConfig $appConfig, ) { } diff --git a/lib/Listener/ShareEventListener.php b/lib/Listener/ShareEventListener.php index 1e1ed0ca3..02088ea80 100644 --- a/lib/Listener/ShareEventListener.php +++ b/lib/Listener/ShareEventListener.php @@ -21,7 +21,7 @@ class ShareEventListener implements IEventListener { public function __construct( - private FilesHooks $fileHooks, + private readonly FilesHooks $fileHooks, ) { } diff --git a/lib/Listener/UserDeleted.php b/lib/Listener/UserDeleted.php index 25c1bf3af..b37dd9ca1 100644 --- a/lib/Listener/UserDeleted.php +++ b/lib/Listener/UserDeleted.php @@ -8,8 +8,6 @@ namespace OCA\Activity\Listener; -use OCA\Activity\Data; -use OCA\Activity\MailQueueHandler; use OCP\EventDispatcher\Event; use OCP\EventDispatcher\IEventListener; use OCP\IUser; @@ -19,16 +17,10 @@ * @template-implements IEventListener */ class UserDeleted implements IEventListener { - /** @var Data */ - private $data; - /** - * @var MailQueueHandler - */ - private $mailQueueHandler; - - public function __construct(Data $data, MailQueueHandler $mailQueueHandler) { - $this->data = $data; - $this->mailQueueHandler = $mailQueueHandler; + public function __construct( + private readonly \OCA\Activity\Data $data, + private readonly \OCA\Activity\MailQueueHandler $mailQueueHandler, + ) { } #[\Override] diff --git a/lib/MailQueueHandler.php b/lib/MailQueueHandler.php index bfd436521..b0e9449de 100644 --- a/lib/MailQueueHandler.php +++ b/lib/MailQueueHandler.php @@ -302,7 +302,7 @@ protected function sendEmailToUser(string $userName, string $email, string $lang ->setTimestamp((int)$activity['amq_timestamp']) ->setSubject((string)$activity['amq_subject'], (array)json_decode($activity['amq_subjectparams'], true)) ->setObject((string)$activity['object_type'], (int)$activity['object_id']); - } catch (\InvalidArgumentException $e) { + } catch (\InvalidArgumentException) { continue; } @@ -310,16 +310,14 @@ protected function sendEmailToUser(string $userName, string $email, string $lang } $activityEvents = array_map( - function ($event) use ($timezone, $l) { - return [ - 'event' => $event, - 'dateTime' => $this->dateFormatter->formatDateTime( - $event->getTimestamp(), - 'long', 'short', - new \DateTimeZone($timezone), $l - ) - ]; - }, + fn (\OCP\Activity\IEvent $event): array => [ + 'event' => $event, + 'dateTime' => $this->dateFormatter->formatDateTime( + $event->getTimestamp(), + 'long', 'short', + new \DateTimeZone($timezone), $l + ) + ], $this->groupHelper->getEvents() ); diff --git a/lib/Migration/Version2006Date20170808154933.php b/lib/Migration/Version2006Date20170808154933.php index 565663220..082f6426c 100644 --- a/lib/Migration/Version2006Date20170808154933.php +++ b/lib/Migration/Version2006Date20170808154933.php @@ -14,9 +14,7 @@ class Version2006Date20170808154933 extends SimpleMigrationStep { /** - * @param IOutput $output * @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` - * @param array $options * @return null|ISchemaWrapper * @since 13.0.0 */ diff --git a/lib/Migration/Version2006Date20170808155040.php b/lib/Migration/Version2006Date20170808155040.php index 92aedd8b8..ef999fc41 100644 --- a/lib/Migration/Version2006Date20170808155040.php +++ b/lib/Migration/Version2006Date20170808155040.php @@ -13,9 +13,7 @@ class Version2006Date20170808155040 extends SimpleMigrationStep { /** - * @param IOutput $output * @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` - * @param array $options * @return null|ISchemaWrapper * @throws \OCP\DB\Schema\SchemaException * @since 13.0.0 diff --git a/lib/Migration/Version2006Date20170919095939.php b/lib/Migration/Version2006Date20170919095939.php index 94f683948..4373c99ba 100644 --- a/lib/Migration/Version2006Date20170919095939.php +++ b/lib/Migration/Version2006Date20170919095939.php @@ -26,9 +26,7 @@ protected function getColumnsByTable() { } /** - * @param IOutput $output * @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` - * @param array $options * @return null|ISchemaWrapper * @since 13.0.0 */ diff --git a/lib/Migration/Version2007Date20181107114613.php b/lib/Migration/Version2007Date20181107114613.php index 2b8f6db49..7d183130b 100644 --- a/lib/Migration/Version2007Date20181107114613.php +++ b/lib/Migration/Version2007Date20181107114613.php @@ -16,9 +16,7 @@ class Version2007Date20181107114613 extends SimpleMigrationStep { /** - * @param IOutput $output * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` - * @param array $options * @return null|ISchemaWrapper */ #[\Override] diff --git a/lib/Migration/Version2008Date20181011095117.php b/lib/Migration/Version2008Date20181011095117.php index ca9bcb77d..fc3ebaa07 100644 --- a/lib/Migration/Version2008Date20181011095117.php +++ b/lib/Migration/Version2008Date20181011095117.php @@ -17,9 +17,7 @@ class Version2008Date20181011095117 extends SimpleMigrationStep { /** - * @param IOutput $output * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` - * @param array $options * @return null|ISchemaWrapper */ #[\Override] @@ -29,7 +27,7 @@ public function changeSchema(IOutput $output, Closure $schemaClosure, array $opt try { $table = $schema->getTable('activity_mq'); - } catch (SchemaException $e) { + } catch (SchemaException) { return null; } diff --git a/lib/Migration/Version2010Date20190416112817.php b/lib/Migration/Version2010Date20190416112817.php index e039faa48..cdef34902 100644 --- a/lib/Migration/Version2010Date20190416112817.php +++ b/lib/Migration/Version2010Date20190416112817.php @@ -18,9 +18,7 @@ */ class Version2010Date20190416112817 extends SimpleMigrationStep { /** - * @param IOutput $output * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` - * @param array $options * @return null|ISchemaWrapper */ #[\Override] diff --git a/lib/Migration/Version2011Date20201006132544.php b/lib/Migration/Version2011Date20201006132544.php index 1bdbcf823..e022d9ef3 100644 --- a/lib/Migration/Version2011Date20201006132544.php +++ b/lib/Migration/Version2011Date20201006132544.php @@ -24,9 +24,7 @@ public function __construct(IDBConnection $connection) { } /** - * @param IOutput $output * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` - * @param array $options * @return null|ISchemaWrapper */ #[\Override] diff --git a/lib/Migration/Version2011Date20201006132545.php b/lib/Migration/Version2011Date20201006132545.php index 9fbcb53b5..f5a2c4353 100644 --- a/lib/Migration/Version2011Date20201006132545.php +++ b/lib/Migration/Version2011Date20201006132545.php @@ -16,9 +16,7 @@ class Version2011Date20201006132545 extends SimpleMigrationStep { /** - * @param IOutput $output * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` - * @param array $options * @return null|ISchemaWrapper */ #[\Override] diff --git a/lib/Migration/Version2011Date20201006132546.php b/lib/Migration/Version2011Date20201006132546.php index 30fc009c4..7be5f1b54 100644 --- a/lib/Migration/Version2011Date20201006132546.php +++ b/lib/Migration/Version2011Date20201006132546.php @@ -24,9 +24,7 @@ public function __construct(IDBConnection $connection) { } /** - * @param IOutput $output * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` - * @param array $options * @return null|ISchemaWrapper */ #[\Override] diff --git a/lib/Migration/Version2011Date20201006132547.php b/lib/Migration/Version2011Date20201006132547.php index 61a91ec98..fa4db3d23 100644 --- a/lib/Migration/Version2011Date20201006132547.php +++ b/lib/Migration/Version2011Date20201006132547.php @@ -15,9 +15,7 @@ class Version2011Date20201006132547 extends SimpleMigrationStep { /** - * @param IOutput $output * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` - * @param array $options * @return null|ISchemaWrapper */ #[\Override] diff --git a/lib/Migration/Version2011Date20201207091915.php b/lib/Migration/Version2011Date20201207091915.php index d25ad9de6..7bbbdfcac 100644 --- a/lib/Migration/Version2011Date20201207091915.php +++ b/lib/Migration/Version2011Date20201207091915.php @@ -15,10 +15,7 @@ class Version2011Date20201207091915 extends SimpleMigrationStep { /** - * @param IOutput $output * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` - * @param array $options - * @return null|ISchemaWrapper */ #[\Override] public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper { diff --git a/lib/Migration/Version8000Date20260603120000.php b/lib/Migration/Version8000Date20260603120000.php index b5fc71e65..b7a1d3054 100644 --- a/lib/Migration/Version8000Date20260603120000.php +++ b/lib/Migration/Version8000Date20260603120000.php @@ -32,10 +32,7 @@ */ class Version8000Date20260603120000 extends SimpleMigrationStep { /** - * @param IOutput $output * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` - * @param array $options - * @return null|ISchemaWrapper */ #[\Override] public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper { diff --git a/lib/NotificationGenerator.php b/lib/NotificationGenerator.php index 3e6b80fe5..86d8f1a33 100644 --- a/lib/NotificationGenerator.php +++ b/lib/NotificationGenerator.php @@ -46,11 +46,11 @@ public function deferNotifications(): bool { return $this->notificationManager->defer(); } - public function flushNotifications() { + public function flushNotifications(): void { $this->notificationManager->flush(); } - public function sendNotificationForEvent(IEvent $event, int $activityId, ?bool $notificationSetting = null) { + public function sendNotificationForEvent(IEvent $event, int $activityId, ?bool $notificationSetting = null): void { $selfAction = $event->getAffectedUser() === $event->getAuthor(); $notifySetting = $notificationSetting ?? $this->userSettings->getUserSetting($event->getAffectedUser(), 'notification', $event->getType()); @@ -93,9 +93,9 @@ private function populateEvent(IEvent $event, string $language) { try { $event = $provider->parse($language, $event); } catch (UnknownActivityException) { - } catch (\InvalidArgumentException $e) { + } catch (\InvalidArgumentException) { // todo 39.0.0 Log as error - $this->logger->warning(get_class($provider) . '::parse() threw \InvalidArgumentException which is deprecated. Throw \OCP\Activity\Exceptions\UnknownActivityException when the event is not known to your provider and otherwise handle all \InvalidArgumentException yourself.'); + $this->logger->warning($provider::class . '::parse() threw \InvalidArgumentException which is deprecated. Throw \OCP\Activity\Exceptions\UnknownActivityException when the event is not known to your provider and otherwise handle all \InvalidArgumentException yourself.'); } } $this->activityManager->setFormattingObject('', 0); diff --git a/lib/SearchCriteria.php b/lib/SearchCriteria.php index 6b339b028..eb5435049 100644 --- a/lib/SearchCriteria.php +++ b/lib/SearchCriteria.php @@ -19,7 +19,7 @@ * * @psalm-immutable */ -final class SearchCriteria { +final readonly class SearchCriteria { /** * Single characters match such a large share of a stream that the query * degenerates into a scan of the whole user partition while still returning @@ -48,10 +48,10 @@ final class SearchCriteria { public const MAX_ACTOR_LENGTH = 64; private function __construct( - public readonly ?string $term, - public readonly ?int $from, - public readonly ?int $to, - public readonly ?string $actor, + public ?string $term, + public ?int $from, + public ?int $to, + public ?string $actor, ) { } diff --git a/lib/Settings/Admin.php b/lib/Settings/Admin.php index c62c6bc64..373a239fa 100644 --- a/lib/Settings/Admin.php +++ b/lib/Settings/Admin.php @@ -18,17 +18,21 @@ use OCP\Settings\ISettings; class Admin implements ISettings { - private IConfig $config; - private IL10N $l10n; - private IManager $manager; - private UserSettings $userSettings; - private IInitialState $initialState; - - public function __construct(IConfig $config, IL10N $l10n, UserSettings $userSettings, IManager $manager, IInitialState $initialState) { + private readonly IConfig $config; + private readonly IL10N $l10n; + private readonly IManager $manager; + private readonly IInitialState $initialState; + + public function __construct( + IConfig $config, + IL10N $l10n, + private readonly UserSettings $userSettings, + IManager $manager, + IInitialState $initialState, + ) { $this->config = $config; $this->l10n = $l10n; $this->manager = $manager; - $this->userSettings = $userSettings; $this->initialState = $initialState; } diff --git a/lib/Settings/AdminSection.php b/lib/Settings/AdminSection.php index 34e600408..599d51816 100644 --- a/lib/Settings/AdminSection.php +++ b/lib/Settings/AdminSection.php @@ -18,10 +18,6 @@ class AdminSection implements IIconSection { /** @var IURLGenerator */ private $url; - /** - * @param IURLGenerator $url - * @param IL10N $l - */ public function __construct(IURLGenerator $url, IL10N $l) { $this->url = $url; $this->l = $l; @@ -47,7 +43,7 @@ public function getIcon() { * @since 9.1 */ #[\Override] - public function getID() { + public function getID(): string { return 'activity'; } @@ -72,7 +68,7 @@ public function getName() { * @since 9.1 */ #[\Override] - public function getPriority() { + public function getPriority(): int { return 55; } } diff --git a/lib/Settings/Personal.php b/lib/Settings/Personal.php index 2721b8560..924329484 100644 --- a/lib/Settings/Personal.php +++ b/lib/Settings/Personal.php @@ -20,25 +20,23 @@ use OCP\Settings\ISettings; class Personal implements ISettings { - private IConfig $config; - private IManager $manager; - private UserSettings $userSettings; - private IL10N $l10n; - private string $userId; - private IUser $user; - private IInitialState $initialState; + private readonly IConfig $config; + private readonly IManager $manager; + private readonly IL10N $l10n; + private readonly string $userId; + private readonly IUser $user; + private readonly IInitialState $initialState; public function __construct( IConfig $config, IManager $manager, - UserSettings $userSettings, + private readonly UserSettings $userSettings, IL10N $l10n, CurrentUser $currentUser, IInitialState $initialState, ) { $this->config = $config; $this->manager = $manager; - $this->userSettings = $userSettings; $this->l10n = $l10n; $this->userId = (string)$currentUser->getUID(); $this->user = $currentUser->getUser(); @@ -145,7 +143,7 @@ public function getForm(): TemplateResponse { * @return string the section ID, e.g. 'sharing' */ #[\Override] - public function getSection() { + public function getSection(): string { return 'notifications'; } @@ -157,7 +155,7 @@ public function getSection() { * E.g.: 70 */ #[\Override] - public function getPriority() { + public function getPriority(): int { return 55; } } diff --git a/lib/Settings/PersonalSection.php b/lib/Settings/PersonalSection.php index 64cf0cff0..be6c90207 100644 --- a/lib/Settings/PersonalSection.php +++ b/lib/Settings/PersonalSection.php @@ -20,10 +20,6 @@ class PersonalSection implements IIconSection { /** @var IURLGenerator */ private $url; - /** - * @param IURLGenerator $url - * @param IL10N $l - */ public function __construct(IURLGenerator $url, IL10N $l) { $this->url = $url; $this->l = $l; @@ -49,7 +45,7 @@ public function getIcon() { * @since 9.1 */ #[\Override] - public function getID() { + public function getID(): string { return 'notifications'; } @@ -74,7 +70,7 @@ public function getName() { * @since 9.1 */ #[\Override] - public function getPriority() { + public function getPriority(): int { return 10; } } diff --git a/lib/UserSettings.php b/lib/UserSettings.php index e90c0544c..5be4cd2e0 100644 --- a/lib/UserSettings.php +++ b/lib/UserSettings.php @@ -30,10 +30,6 @@ class UserSettings { public const BATCH_TIME_DAILY = 3600 * 24; public const BATCH_TIME_WEEKLY = 3600 * 24 * 7; - /** - * @param IManager $manager - * @param IConfig $config - */ public function __construct( protected IManager $manager, protected IConfig $config, @@ -46,10 +42,8 @@ public function __construct( * * Falls back to some good default values if the user does not have a preference * - * @param string $user * @param string $method Should be one of 'stream', 'email' or 'setting' * @param string $type One of the activity types, 'batchtime' or 'self' - * @return bool|int */ public function getUserSetting(string $user, string $method, string $type): bool|int { if ($method === 'email' && $this->config->getAppValue('activity', 'enable_email', 'yes') === 'no') { @@ -81,10 +75,6 @@ public function getUserSetting(string $user, string $method, string $type): bool /** * Get the admin configured default for the setting * Falling back to the implementation default if not set by the admin - * - * @param string $method - * @param string $type - * @return bool|int */ public function getAdminSetting(string $method, string $type): bool|int { $defaultSetting = $this->getDefaultSetting($method, $type); @@ -108,7 +98,6 @@ public function getAdminSetting(string $method, string $type): bool|int { * * @param string $method Should be one of 'stream', 'email' or 'setting' * @param string $type One of the activity types, 'batchtime', 'self' or 'selfemail' - * @return bool|int */ protected function getDefaultSetting(string $method, string $type): bool|int { if ($method === 'setting') { @@ -120,23 +109,16 @@ protected function getDefaultSetting(string $method, string $type): bool|int { return true; } - if ($type === 'selfemail') { - return false; - } - return false; } try { $setting = $this->manager->getSettingById($type); - switch ($method) { - case 'email': - return $setting->isDefaultEnabledMail(); - case 'notification': - return $setting->isDefaultEnabledNotification(); - default: - return false; - } + return match ($method) { + 'email' => $setting->isDefaultEnabledMail(), + 'notification' => $setting->isDefaultEnabledNotification(), + default => false, + }; } catch (SettingNotFoundException) { return false; } @@ -147,7 +129,6 @@ protected function getDefaultSetting(string $method, string $type): bool|int { * * @param string $method Should be one of 'stream', 'email' or 'setting' * @param string $type One of the activity types, 'batchtime', 'self' or 'selfemail' - * @return bool */ protected function canModifySetting(string $method, string $type): bool { if ($method === 'setting') { @@ -156,14 +137,11 @@ protected function canModifySetting(string $method, string $type): bool { try { $setting = $this->manager->getSettingById($type); - switch ($method) { - case 'email': - return $setting->canChangeMail(); - case 'notification': - return $setting->canChangeNotification(); - default: - return false; - } + return match ($method) { + 'email' => $setting->canChangeMail(), + 'notification' => $setting->canChangeNotification(), + default => false, + }; } catch (SettingNotFoundException) { return false; } @@ -175,9 +153,7 @@ protected function canModifySetting(string $method, string $type): bool { public function getNotificationTypes(): array { $settings = $this->manager->getSettings(); - $return = array_map(function (ActivitySettings $setting) { - return $setting->getIdentifier(); - }, $settings); + $return = array_map(fn (ActivitySettings $setting) => $setting->getIdentifier(), $settings); // TYPE_FILE_CHANGED is used to group all file changes together // But we still differentiate between file_created, file_deleted and file_restored @@ -192,9 +168,6 @@ public function getNotificationTypes(): array { /** * Filters the given user array by their notification setting * - * @param array $users - * @param string $method - * @param string $type * @return array Returns a "username => b:true" Map for method = notification * Returns a "username => i:batchtime" Map for method = email */ diff --git a/rector.php b/rector.php new file mode 100644 index 000000000..484e82f3b --- /dev/null +++ b/rector.php @@ -0,0 +1,27 @@ +withPaths([ + __DIR__ . '/lib', + __DIR__ . '/tests', + ]) + ->withSkip([ + __DIR__ . '/tests/stubs', + ]) + ->withPreparedSets( + deadCode: true, + typeDeclarations: true, + )->withPhpSets( + php82: true, + )->withConfiguredRule(ClassPropertyAssignToConstructorPromotionRector::class, [ + 'inline_public' => true, + 'rename_property' => true, + ]); diff --git a/tests/ConsumerTest.php b/tests/ConsumerTest.php index 4cea7a995..6b75b2372 100644 --- a/tests/ConsumerTest.php +++ b/tests/ConsumerTest.php @@ -265,9 +265,7 @@ public function testBulkReceiveNotification(string $type, string $author, string if ($expectNotification) { $this->userConfig->method('getValuesByUsers') - ->willReturnCallback(function (string $app, string $key, mixed $type, array $users): array { - return array_fill_keys($users, true); - }); + ->willReturnCallback(fn (string $app, string $key, mixed $type, array $users): array => array_fill_keys($users, true)); $this->notificationGenerator->expects($this->atLeastOnce()) ->method('sendNotificationForEvent'); } else { @@ -363,9 +361,7 @@ public function testBulkReceiveDeferAndFlushNotifications(): void { ->willReturn([1 => 'affectedUser']); $this->userConfig->method('getValuesByUsers') - ->willReturnCallback(function (string $app, string $key, mixed $type, array $users): array { - return array_fill_keys($users, true); - }); + ->willReturnCallback(fn (string $app, string $key, mixed $type, array $users): array => array_fill_keys($users, true)); $this->notificationGenerator->expects($this->once()) ->method('deferNotifications') @@ -398,9 +394,7 @@ public function testBulkReceiveNoFlushWhenDeferReturnsFalse(): void { ->willReturn([1 => 'affectedUser']); $this->userConfig->method('getValuesByUsers') - ->willReturnCallback(function (string $app, string $key, mixed $type, array $users): array { - return array_fill_keys($users, true); - }); + ->willReturnCallback(fn (string $app, string $key, mixed $type, array $users): array => array_fill_keys($users, true)); $this->notificationGenerator->expects($this->once()) ->method('deferNotifications') diff --git a/tests/Controller/APIv1ControllerTest.php b/tests/Controller/APIv1ControllerTest.php index 625f07a26..367ebb7dd 100644 --- a/tests/Controller/APIv1ControllerTest.php +++ b/tests/Controller/APIv1ControllerTest.php @@ -201,9 +201,7 @@ public function testGet(string $user, int $start, int $count, array $expected): $l = $this->createMock(IL10N::class); $l ->method('t') - ->willReturnCallback(function ($text, $parameters = []) { - return vsprintf($text, $parameters); - }); + ->willReturnCallback(fn ($text, $parameters = []): string => vsprintf($text, $parameters)); $activityManager = new Manager( $this->createMock(IRequest::class), diff --git a/tests/Controller/APIv2ControllerTest.php b/tests/Controller/APIv2ControllerTest.php index ef2123635..0f4e142fe 100644 --- a/tests/Controller/APIv2ControllerTest.php +++ b/tests/Controller/APIv2ControllerTest.php @@ -474,7 +474,7 @@ public function testGetHistogramWindowEndsTodayAndSpansTheRequestedDays(): void $captured = []; $this->data->expects($this->once()) ->method('getDailyCounts') - ->willReturnCallback(function ($settings, $user, $filter, $from, $to) use (&$captured) { + ->willReturnCallback(function ($settings, $user, $filter, $from, $to) use (&$captured): array { $captured = ['from' => $from, 'to' => $to]; return ['counts' => [], 'partialBefore' => null]; }); @@ -505,7 +505,7 @@ public function testGetHistogramClampsTheWindow(int $requested, int $expectedDay $span = 0; $this->data->method('getDailyCounts') - ->willReturnCallback(function ($settings, $user, $filter, $from, $to) use (&$span) { + ->willReturnCallback(function ($settings, $user, $filter, $from, $to) use (&$span): array { $span = $to - $from; return ['counts' => [], 'partialBefore' => null]; }); @@ -523,7 +523,7 @@ public function testGetHistogramPassesTheSearchAndActorButNotADateRange(): void $this->data->expects($this->once()) ->method('getDailyCounts') - ->willReturnCallback(function ($settings, $user, $filter, $from, $to, $tz, $objectType, $objectId, $criteria) { + ->willReturnCallback(function ($settings, $user, $filter, $from, $to, $tz, $objectType, $objectId, $criteria): array { // The histogram is the control a range is chosen with, so it must // not be narrowed by the range that is currently selected $this->assertSame('report', $criteria->term); @@ -1038,9 +1038,7 @@ public function testGetPreview(string $author, int $fileId, string $path, string $node = $this->createMock(\OCP\Files\File::class); $this->urlGenerator ->method('linkToRouteAbsolute') - ->willReturnCallback(function ($url, $params) { - return $url . '#' . ($params['fileid'] ?? $params['fileId']); - }); + ->willReturnCallback(fn ($url, array $params): string => $url . '#' . ($params['fileid'] ?? $params['fileId'])); if ($isDir) { $controller->expects($this->once()) @@ -1130,9 +1128,7 @@ public function testGetPreviewFromPath(int $fileId, string $filePath, string $mi ->willReturn('mime-type-icon'); $this->urlGenerator ->method('linkToRouteAbsolute') - ->willReturnCallback(function ($url, $params) { - return $url . '#' . ($params['fileid'] ?? $params['fileId']); - }); + ->willReturnCallback(fn ($url, array $params): string => $url . '#' . ($params['fileid'] ?? $params['fileId'])); $this->mimeTypeDetector->expects($isDir ? $this->never() : $this->once()) ->method('detectPath') ->willReturn($mimeType); @@ -1168,9 +1164,7 @@ public function testGetPreviewPathFromMimeType(string $mimeType, string $icon, s $this->urlGenerator->expects($this->once()) ->method('getAbsoluteURL') - ->willReturnCallback(function ($url) { - return 'absolute-' . $url; - }); + ->willReturnCallback(fn ($url): string => 'absolute-' . $url); $this->assertSame( $expected, diff --git a/tests/DataTest.php b/tests/DataTest.php index ff3e5be0a..275e4cc03 100644 --- a/tests/DataTest.php +++ b/tests/DataTest.php @@ -265,9 +265,7 @@ public function testSetOffsetFromSince(string $sort, ?string $timestampWhere, ?s ->willReturn($query->expr()); $mock ->method('createNamedParameter') - ->willReturnCallback(function ($arg) use ($query) { - return $query->expr()->literal($arg); - }); + ->willReturnCallback(fn ($arg) => $query->expr()->literal($arg)); if ($timestampWhere !== null && $idWhere !== null) { $mock->expects($this->exactly(2)) ->method('andWhere'); @@ -302,7 +300,7 @@ public function testCountDownloads(): void { $objectId = 12345; $now = time(); - $insertRow = function (string $affectedUser, int $objectId, int $timestamp) { + $insertRow = function (string $affectedUser, int $objectId, int $timestamp): void { $query = $this->dbConnection->getQueryBuilder(); $query->insert('activity') ->values([ diff --git a/tests/DigestSenderTest.php b/tests/DigestSenderTest.php index 670946b74..e278153f3 100644 --- a/tests/DigestSenderTest.php +++ b/tests/DigestSenderTest.php @@ -69,9 +69,7 @@ protected function expectDigestUsers(array $users, array $timezones): void { $this->config->method('getUsersForUserValue') ->willReturn($users); $this->config->method('getUserValueForUsers') - ->willReturnCallback(static function (string $app, string $key) use ($timezones) { - return ($app === 'core' && $key === 'timezone') ? $timezones : []; - }); + ->willReturnCallback(static fn (string $app, string $key): array => ($app === 'core' && $key === 'timezone') ? $timezones : []); } protected function createUser(string $uid, string $email): IUser&MockObject { diff --git a/tests/FilesHooksTest.php b/tests/FilesHooksTest.php index 87cf88629..0652c5572 100644 --- a/tests/FilesHooksTest.php +++ b/tests/FilesHooksTest.php @@ -77,7 +77,7 @@ class FilesHooksTest extends TestCase { protected NotificationGenerator&MockObject $notificationGenerator; protected TagManager&MockObject $tagManager; protected Tags&MockObject $tags; - protected (OCA\Circles\CirclesManager&MockObject)|null $teamManager; + protected (OCA\Circles\CirclesManager&MockObject)|null $teamManager = null; protected function setUp(): void { parent::setUp(); @@ -459,9 +459,7 @@ public function testAddNotificationsForFileAction(array $filterUsers, bool $moun $this->settings->expects($this->exactly(2)) ->method('filterUsersBySetting') - ->willReturnCallback(function ($users, $method) use ($filterUsers) { - return $filterUsers[$method]; - }); + ->willReturnCallback(fn ($users, $method) => $filterUsers[$method]); $addCalls = []; foreach ($addNotifications as $user => $arguments) { @@ -480,7 +478,7 @@ public function testAddNotificationsForFileAction(array $filterUsers, bool $moun $receivedActivities = []; $filesHooks ->method('addNotificationsForUser') - ->willReturnCallback(function (...$params) use (&$receivedActivities) { + ->willReturnCallback(function (...$params) use (&$receivedActivities): void { $receivedActivities[] = $params; }); @@ -820,7 +818,7 @@ public function testShareWithGroup(array $usersInGroup, int $settingCalls, int $ $receivedActivities = []; $filesHooks ->method('addNotificationsForUser') - ->willReturnCallback(function (...$params) use (&$receivedActivities) { + ->willReturnCallback(function (...$params) use (&$receivedActivities): void { $receivedActivities[] = $params; }); diff --git a/tests/Listener/SetUserDefaultsTest.php b/tests/Listener/SetUserDefaultsTest.php index 7614e0821..b7f0094b3 100644 --- a/tests/Listener/SetUserDefaultsTest.php +++ b/tests/Listener/SetUserDefaultsTest.php @@ -42,7 +42,6 @@ class SetUserDefaultsTest extends TestCase { public const UID = 'myuser'; public function setUp(): void { - parent::setUp(); $this->config = $this->createMock(IConfig::class); $this->appConfig = $this->createMock(IAppConfig::class); $user = $this->createMock(IUser::class); diff --git a/tests/Listener/UserDeletedTest.php b/tests/Listener/UserDeletedTest.php index eea832455..2971c1dfa 100644 --- a/tests/Listener/UserDeletedTest.php +++ b/tests/Listener/UserDeletedTest.php @@ -41,8 +41,6 @@ class UserDeletedTest extends TestCase { public const UID = 'myuser'; public function setUp(): void { - parent::setUp(); - $user = $this->createMock(IUser::class); $user->expects($this->exactly(2))->method('getUID')->with()->willReturn(self::UID); diff --git a/tests/MailQueueHandlerTest.php b/tests/MailQueueHandlerTest.php index 293efe71b..9cdfce3d1 100644 --- a/tests/MailQueueHandlerTest.php +++ b/tests/MailQueueHandlerTest.php @@ -173,10 +173,6 @@ public static function getAffectedUsersData(): array { /** * @dataProvider getAffectedUsersData - * - * @param int|null $limit - * @param array $affected - * @param array $untouched */ #[DataProvider('getAffectedUsersData')] public function testGetAffectedUsers(?int $limit, array $affected, array $untouched): void { @@ -445,11 +441,6 @@ public function testGetMailMaxItemsReturnsOneWhenValueIsBelowOne(): void { $this->assertSame(1, $result); } - /** - * @param array $users - * @param int $maxTime - * @param string $explain - */ protected function assertRemainingMailEntries(array $users, int $maxTime, string $explain): void { foreach ($users as $user) { [$data,] = self::invokePrivate($this->mailQueueHandler, 'getItemsForUser', [$user, $maxTime]); diff --git a/tests/Mock/Provider.php b/tests/Mock/Provider.php index b76d77a5c..da23172c9 100644 --- a/tests/Mock/Provider.php +++ b/tests/Mock/Provider.php @@ -33,7 +33,6 @@ class Provider implements IProvider { * @param IEvent|null $previousEvent A potential previous event which you can combine with the current one. * To do so, simply use setChildEvent($previousEvent) after setting the * combined subject on the current event. - * @return IEvent * @throws \InvalidArgumentException Should be thrown if your provider does not know this event * @since 11.0.0 */ @@ -42,20 +41,12 @@ public function parse($language, IEvent $event, ?IEvent $previousEvent = null): throw new \InvalidArgumentException(); } - switch ($event->getSubject()) { - case 'subject1': - $event->setParsedSubject(vsprintf('Subject1 #%1$s', $event->getSubjectParameters())); - break; - case 'subject2': - $event->setParsedSubject(vsprintf('Subject2 @%2$s #%1$s', $event->getSubjectParameters())); - break; - case 'subject3': - $event->setParsedSubject(vsprintf('Subject3 #%1$s @%2$s', $event->getSubjectParameters())); - break; - - default: - throw new \InvalidArgumentException(); - } + match ($event->getSubject()) { + 'subject1' => $event->setParsedSubject(vsprintf('Subject1 #%1$s', $event->getSubjectParameters())), + 'subject2' => $event->setParsedSubject(vsprintf('Subject2 @%2$s #%1$s', $event->getSubjectParameters())), + 'subject3' => $event->setParsedSubject(vsprintf('Subject3 #%1$s @%2$s', $event->getSubjectParameters())), + default => throw new \InvalidArgumentException(), + }; return $event; } diff --git a/tests/Template/RssTest.php b/tests/Template/RssTest.php index 78b91e80a..966005fa6 100644 --- a/tests/Template/RssTest.php +++ b/tests/Template/RssTest.php @@ -137,7 +137,7 @@ public function testContent(array $activities, string $expected): void { $rendered ); - $rendered = substr($rendered, strlen($prefixStub), 0 - strlen($suffixStub)); + $rendered = substr($rendered, strlen($prefixStub), -strlen($suffixStub)); $this->assertEquals($expected, $rendered); } } diff --git a/vendor-bin/rector/composer.json b/vendor-bin/rector/composer.json new file mode 100644 index 000000000..8592ef65e --- /dev/null +++ b/vendor-bin/rector/composer.json @@ -0,0 +1,5 @@ +{ + "require-dev": { + "rector/rector": "^2.0" + } +} diff --git a/vendor-bin/rector/composer.lock b/vendor-bin/rector/composer.lock new file mode 100644 index 000000000..a26fc1d9a --- /dev/null +++ b/vendor-bin/rector/composer.lock @@ -0,0 +1,143 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "57211dc5c619d183df6361f5d7d57fe5", + "packages": [], + "packages-dev": [ + { + "name": "phpstan/phpstan", + "version": "2.2.8", + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/e285254e60f33c21902efef4a926ca0987c06804", + "reference": "e285254e60f33c21902efef4a926ca0987c06804", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ondřej Mirtes" + }, + { + "name": "Markus Staab" + }, + { + "name": "Vincent Langlet" + } + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + } + ], + "time": "2026-08-04T22:21:45+00:00" + }, + { + "name": "rector/rector", + "version": "2.5.8", + "source": { + "type": "git", + "url": "https://github.com/rectorphp/rector.git", + "reference": "861c77fd2a6d45317a401f4e0b617f947e8b6f96" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/861c77fd2a6d45317a401f4e0b617f947e8b6f96", + "reference": "861c77fd2a6d45317a401f4e0b617f947e8b6f96", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0", + "phpstan/phpstan": "^2.2.6" + }, + "conflict": { + "rector/rector-doctrine": "*", + "rector/rector-downgrade-php": "*", + "rector/rector-phpunit": "*", + "rector/rector-symfony": "*" + }, + "suggest": { + "ext-dom": "To manipulate phpunit.xml via the custom-rule command" + }, + "bin": [ + "bin/rector" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Instant Upgrade and Automated Refactoring of any PHP code", + "homepage": "https://getrector.com/", + "keywords": [ + "automation", + "dev", + "migration", + "refactoring" + ], + "support": { + "issues": "https://github.com/rectorphp/rector/issues", + "source": "https://github.com/rectorphp/rector/tree/2.5.8" + }, + "funding": [ + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2026-07-27T06:19:16+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": false, + "prefer-lowest": false, + "platform": {}, + "platform-dev": {}, + "plugin-api-version": "2.9.0" +} From d1595993b229a1ebbf45ddef6498b857a22738e5 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Thu, 1 Oct 2026 13:32:10 +0200 Subject: [PATCH 03/11] chore: Port away simple usage of View Use newer node api instead Signed-off-by: Carl Schwan --- lib/AppInfo/Application.php | 4 --- lib/FilesHooks.php | 19 +++++++++--- tests/FilesHooksTest.php | 59 +++++++++++++++++++++++++++++++++---- 3 files changed, 68 insertions(+), 14 deletions(-) diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 38d907fc0..ddeb1c3cb 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -9,7 +9,6 @@ namespace OCA\Activity\AppInfo; use OC\DB\ConnectionAdapter; -use OC\Files\View; use OC\SystemConfig; use OCA\Activity\Capabilities; use OCA\Activity\Consumer; @@ -127,9 +126,6 @@ public function register(IRegistrationContext $context): void { $c->get(IEmailValidator::class), )); - // Allow automatic DI for the View, until we migrated to Nodes API - $context->registerService(View::class, fn (): \OC\Files\View => new View(''), false); - $context->registerCapability(Capabilities::class); $context->registerEventListener(AddMissingIndicesEvent::class, AddMissingIndicesListener::class); $context->registerEventListener(LoadSidebar::class, LoadSidebarScripts::class); diff --git a/lib/FilesHooks.php b/lib/FilesHooks.php index 06d653eea..00360619b 100644 --- a/lib/FilesHooks.php +++ b/lib/FilesHooks.php @@ -9,7 +9,6 @@ namespace OCA\Activity; use OC\Files\Filesystem; -use OC\Files\View; use OCA\Activity\BackgroundJob\RemoteActivity; use OCA\Activity\Extension\Files; use OCA\Activity\Extension\Files_Sharing; @@ -57,7 +56,6 @@ public function __construct( protected Data $activityData, protected UserSettings $userSettings, protected IGroupManager $groupManager, - protected View $view, protected IRootFolder $rootFolder, protected IShareHelper $shareHelper, protected IDBConnection $connection, @@ -671,14 +669,27 @@ protected function getSourcePathAndOwner($path): array { $info = Filesystem::getFileInfo($path); if ($info !== false) { - $ownerView = new View('/' . $owner . '/files'); $fileId = (int)$info['fileid']; - $path = $ownerView->getPath($fileId); + $path = $this->getOwnerPathById($owner, $fileId); } return [$path, $owner, $fileId]; } + /** + * @return string Path relative to the owner's files folder + * @throws NotFoundException + */ + protected function getOwnerPathById(string $owner, int $fileId): string { + $userFolder = $this->rootFolder->getUserFolder($owner); + $node = $userFolder->getFirstNodeById($fileId); + $path = $node === null ? null : $userFolder->getRelativePath($node->getPath()); + if ($path === null) { + throw new NotFoundException('File with id ' . $fileId . ' not found for ' . $owner); + } + return $path; + } + /** * Manage sharing events * diff --git a/tests/FilesHooksTest.php b/tests/FilesHooksTest.php index 0652c5572..d8db7f525 100644 --- a/tests/FilesHooksTest.php +++ b/tests/FilesHooksTest.php @@ -25,7 +25,6 @@ namespace OCA\Activity; use OC\Files\Config\CachedMountFileInfo; -use OC\Files\View; use OC\TagManager; use OC\Tags; use OCA\Activity\Extension\Files; @@ -68,7 +67,6 @@ class FilesHooksTest extends TestCase { protected Data&MockObject $data; protected UserSettings&MockObject $settings; protected IGroupManager&MockObject $groupManager; - protected View&MockObject $view; protected IRootFolder&MockObject $rootFolder; protected IShareHelper&MockObject $shareHelper; protected IURLGenerator&MockObject $urlGenerator; @@ -86,7 +84,6 @@ protected function setUp(): void { $this->data = $this->createMock(Data::class); $this->settings = $this->createMock(UserSettings::class); $this->groupManager = $this->createMock(IGroupManager::class); - $this->view = $this->createMock(View::class); $this->rootFolder = $this->createMock(IRootFolder::class); $this->shareHelper = $this->createMock(IShareHelper::class); $this->urlGenerator = $this->createMock(IURLGenerator::class); @@ -122,7 +119,6 @@ protected function getFilesHooks(array $mockedMethods = [], string $user = 'user $this->data, $this->settings, $this->groupManager, - $this->view, $this->rootFolder, $this->shareHelper, Server::get(IDBConnection::class), @@ -144,7 +140,6 @@ protected function getFilesHooks(array $mockedMethods = [], string $user = 'user $this->data, $this->settings, $this->groupManager, - $this->view, $this->rootFolder, $this->shareHelper, Server::get(IDBConnection::class), @@ -192,7 +187,6 @@ public function testFileCreate(string $currentUser, bool $isPublicShare, string $this->data, $this->settings, $this->groupManager, - $this->view, $this->rootFolder, $this->shareHelper, Server::get(IDBConnection::class), @@ -559,6 +553,59 @@ private function getNodeMock(int $fileId = 1337, string $path = 'path', bool $is return $node; } + public static function dataGetOwnerPathById(): array { + return [ + ['/owner/files/folder/file.txt', '/folder/file.txt'], + ['/owner/files', '/'], + ]; + } + + #[DataProvider('dataGetOwnerPathById')] + public function testGetOwnerPathById(string $absolutePath, string $expected): void { + $node = $this->getNodeMock(42, $absolutePath); + $userFolder = $this->createMock(IUserFolder::class); + $userFolder->method('getFirstNodeById') + ->with(42) + ->willReturn($node); + $userFolder->method('getRelativePath') + ->with($absolutePath) + ->willReturn($expected); + $this->rootFolder->method('getUserFolder') + ->with('owner') + ->willReturn($userFolder); + + $this->assertSame($expected, self::invokePrivate($this->filesHooks, 'getOwnerPathById', ['owner', 42])); + } + + public function testGetOwnerPathByIdNotFound(): void { + $userFolder = $this->createMock(IUserFolder::class); + $userFolder->method('getFirstNodeById') + ->with(42) + ->willReturn(null); + $this->rootFolder->method('getUserFolder') + ->with('owner') + ->willReturn($userFolder); + + $this->expectException(NotFoundException::class); + self::invokePrivate($this->filesHooks, 'getOwnerPathById', ['owner', 42]); + } + + public function testGetOwnerPathByIdOutsideUserFolder(): void { + $node = $this->getNodeMock(42, '/other/files/file.txt'); + $userFolder = $this->createMock(IUserFolder::class); + $userFolder->method('getFirstNodeById') + ->with(42) + ->willReturn($node); + $userFolder->method('getRelativePath') + ->willReturn(null); + $this->rootFolder->method('getUserFolder') + ->with('owner') + ->willReturn($userFolder); + + $this->expectException(NotFoundException::class); + self::invokePrivate($this->filesHooks, 'getOwnerPathById', ['owner', 42]); + } + public function testHookShareWithUser(): void { $filesHooks = $this->getFilesHooks([ 'shareWithUser', From 5c7e78a22bdbc6e1d5298e9206c99d5356237647 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Thu, 1 Oct 2026 13:35:57 +0200 Subject: [PATCH 04/11] fix(ActivitiesControllerTest): Adapt code to new implementation Signed-off-by: Carl Schwan --- tests/Controller/ActivitiesControllerTest.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/Controller/ActivitiesControllerTest.php b/tests/Controller/ActivitiesControllerTest.php index 6a6684540..3b79d2525 100644 --- a/tests/Controller/ActivitiesControllerTest.php +++ b/tests/Controller/ActivitiesControllerTest.php @@ -30,7 +30,6 @@ use OCP\AppFramework\Services\IInitialState; use OCP\EventDispatcher\IEventDispatcher; use OCP\IConfig; -use OCP\IL10N; use OCP\IRequest; use OCP\IURLGenerator; use PHPUnit\Framework\Attributes\Group; @@ -46,7 +45,6 @@ class ActivitiesControllerTest extends TestCase { protected IConfig&MockObject $config; protected Data&MockObject $data; protected IEventDispatcher&MockObject $eventDispatcher; - protected MockObject&IL10N $l10n; protected IInitialState&MockObject $initialState; protected MockObject&IURLGenerator $urlGenerator; protected IManager&MockObject $activityManager; @@ -59,7 +57,6 @@ protected function setUp(): void { $this->data = $this->createMock(Data::class); $this->eventDispatcher = $this->createMock(IEventDispatcher::class); $this->request = $this->createMock(IRequest::class); - $this->l10n = $this->createMock(IL10N::class); $this->initialState = $this->createMock(IInitialState::class); $this->urlGenerator = $this->createMock(IURLGenerator::class); $this->activityManager = $this->createMock(IManager::class); @@ -75,7 +72,6 @@ protected function getController(array $methods = []): ActivitiesController { 'some-user', $this->config, $this->data, - $this->l10n, $this->eventDispatcher, $this->initialState, $this->urlGenerator, @@ -90,7 +86,6 @@ protected function getController(array $methods = []): ActivitiesController { 'some-user', $this->config, $this->data, - $this->l10n, $this->eventDispatcher, $this->initialState, $this->urlGenerator, From 90d2c0a4c1a8be5968cec78c68e567b414fb53ce Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Thu, 1 Oct 2026 13:38:30 +0200 Subject: [PATCH 05/11] fix(psalm): Make rector and psalm happy Signed-off-by: Carl Schwan --- lib/Controller/APIv2Controller.php | 4 ++-- lib/Controller/FeedController.php | 2 +- lib/Controller/RemoteActivityController.php | 2 +- lib/DigestSender.php | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/Controller/APIv2Controller.php b/lib/Controller/APIv2Controller.php index 7f70b4744..caefc3790 100644 --- a/lib/Controller/APIv2Controller.php +++ b/lib/Controller/APIv2Controller.php @@ -239,12 +239,12 @@ public function listFilters(): DataResponse { ], $filters); // php 5.6 has problems with usort and objects - usort($filters, static function (array $a, array $b): int|float { + usort($filters, static function (array $a, array $b): int { if ($a['priority'] === $b['priority']) { return ($a['id'] > $b['id']) ? 1 : -1; } - return $a['priority'] - $b['priority']; + return $a['priority'] <=> $b['priority']; }); return new DataResponse($filters); diff --git a/lib/Controller/FeedController.php b/lib/Controller/FeedController.php index 1cca9783e..fb8cebc21 100644 --- a/lib/Controller/FeedController.php +++ b/lib/Controller/FeedController.php @@ -86,7 +86,7 @@ public function show(string $filter = 'all'): TemplateResponse { 'activities' => $activities, ]); - if (stripos((string)$this->request->getHeader('accept'), 'application/rss+xml') !== false) { + if (stripos($this->request->getHeader('accept'), 'application/rss+xml') !== false) { $response->addHeader('Content-Type', 'application/rss+xml'); } else { $response->addHeader('Content-Type', 'text/xml; charset=UTF-8'); diff --git a/lib/Controller/RemoteActivityController.php b/lib/Controller/RemoteActivityController.php index 12e7cbef6..4a88be00e 100644 --- a/lib/Controller/RemoteActivityController.php +++ b/lib/Controller/RemoteActivityController.php @@ -119,7 +119,7 @@ public function receiveActivity($token, array $to, array $actor, $type, $updated return $response; } - $normalizedActorServer = rtrim(strtolower((string)preg_replace('/^https?:\/\//', '', (string)$actorServer)), '/'); + $normalizedActorServer = rtrim(strtolower((string)preg_replace('/^https?:\/\//', '', $actorServer)), '/'); $normalizedShareRemote = rtrim(strtolower(preg_replace('/^https?:\/\//', '', $share['remote'])), '/'); if ($normalizedActorServer !== $normalizedShareRemote) { return new DataResponse([], Http::STATUS_FORBIDDEN); diff --git a/lib/DigestSender.php b/lib/DigestSender.php index e8cac1f1d..e497366d7 100644 --- a/lib/DigestSender.php +++ b/lib/DigestSender.php @@ -201,7 +201,7 @@ public function sendDigestForUser(IUser $user, int $now, string $timezone, strin $andMoreText = $l10n->n('and %n more…', 'and %n more…', $skippedCount); $url = $this->urlGenerator->linkToRouteAbsolute('activity.Activities.showList', [ 'filter' => 'all' ]); $template->addBodyListItem( - '' . htmlspecialchars((string)$andMoreText) . '', + '' . htmlspecialchars($andMoreText) . '', plainText: $andMoreText, ); } From 13e7c7959bbe11b8b4fc3201fa69588cfef97e2a Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Thu, 1 Oct 2026 14:16:14 +0200 Subject: [PATCH 06/11] fix(playwright): Fix routing with vue3 Signed-off-by: Carl Schwan --- playwright/e2e/admin-settings.spec.ts | 2 +- playwright/e2e/settings.spec.ts | 2 +- playwright/e2e/sidebar.spec.ts | 6 +++--- playwright/e2e/stream.spec.ts | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/playwright/e2e/admin-settings.spec.ts b/playwright/e2e/admin-settings.spec.ts index 8a305f52a..fbd8925c8 100644 --- a/playwright/e2e/admin-settings.spec.ts +++ b/playwright/e2e/admin-settings.spec.ts @@ -20,7 +20,7 @@ async function toggleCheckbox(page: Page, cb: Locator) { test.describe('Admin settings', () => { test.beforeEach(async ({ page }) => { await login(page.request, admin) - await page.goto('/settings/admin/activity') + await page.goto('settings/admin/activity') }) test('Email enable toggle is visible', async ({ page }) => { diff --git a/playwright/e2e/settings.spec.ts b/playwright/e2e/settings.spec.ts index bcf549cb5..e5d2ff91f 100644 --- a/playwright/e2e/settings.spec.ts +++ b/playwright/e2e/settings.spec.ts @@ -24,7 +24,7 @@ test.describe("User settings persist across reloads", () => { test.beforeEach(async ({ page }) => { await login(page.request, user) - await page.goto('/settings/user/notifications') + await page.goto('settings/user/notifications') }) test('Form survives a reload', async ({ page }) => { diff --git a/playwright/e2e/sidebar.spec.ts b/playwright/e2e/sidebar.spec.ts index 3abe11a74..860c5f04b 100644 --- a/playwright/e2e/sidebar.spec.ts +++ b/playwright/e2e/sidebar.spec.ts @@ -12,7 +12,7 @@ import { addTag, createPublicShare, randHash, showActivityTab, toggleFavorite } test.beforeEach(async ({ page }) => { const user = await createRandomUser() await login(page.request, user) - await page.goto('/apps/files') + await page.goto('apps/files') await expect(getFileListRow(page, 'welcome.txt')).toBeVisible() }) @@ -26,7 +26,7 @@ test('Has favorite activity', async ({ page }) => { await showActivityTab(page, 'welcome.txt') await expect(page.locator('.activity-entry').first()).toContainText('Added to favorites') - await page.goto('/apps/files') + await page.goto('apps/files') await expect(getFileListRow(page, 'welcome.txt')).toBeVisible() await toggleFavorite(page, 'welcome.txt') @@ -58,7 +58,7 @@ test('Has move activity', async ({ page }) => { test('Has tag activity', async ({ page }) => { await addTag(page, 'welcome.txt', `my_tag_${randHash()}`) - await page.goto('/apps/files') + await page.goto('apps/files') await showActivityTab(page, 'welcome.txt') await expect(page.locator('.activity-entry').first()).toContainText('Added system tag') diff --git a/playwright/e2e/stream.spec.ts b/playwright/e2e/stream.spec.ts index 7ccb5774f..d6aeab655 100644 --- a/playwright/e2e/stream.spec.ts +++ b/playwright/e2e/stream.spec.ts @@ -24,7 +24,7 @@ test.beforeEach(async ({ page }) => { const user = await createRandomUser() await login(page.request, user) // Seed activity: visiting the files app triggers file creation events - await page.goto('/apps/files') + await page.goto('apps/files') await expect(getFileListRow(page, 'welcome.txt')).toBeVisible() }) From 6b9cf2c4db42a61a195e4a88b5e4a4ba8ab18ea8 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Thu, 1 Oct 2026 16:18:34 +0200 Subject: [PATCH 07/11] refactor: Run rector with nextcloud rules Signed-off-by: Carl Schwan --- lib/AppInfo/Application.php | 2 + lib/Controller/APIv2Controller.php | 4 +- lib/Controller/ActivitiesController.php | 4 +- lib/Controller/FeedController.php | 6 +- lib/Controller/SettingsController.php | 32 +- lib/DigestSender.php | 22 +- lib/Listener/SetUserDefaults.php | 12 +- lib/MailQueueHandler.php | 12 +- lib/UserSettings.php | 26 +- rector.php | 7 +- tests/Controller/APIv1ControllerTest.php | 12 +- tests/Controller/ActivitiesControllerTest.php | 5 + tests/Controller/FeedControllerTest.php | 8 +- tests/DigestSenderTest.php | 16 +- tests/Listener/SetUserDefaultsTest.php | 57 +- tests/MailQueueHandlerTest.php | 8 +- tests/UserSettingsTest.php | 11 +- vendor-bin/rector/composer.json | 3 +- vendor-bin/rector/composer.lock | 809 +++++++++++++++++- 19 files changed, 958 insertions(+), 98 deletions(-) diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index ddeb1c3cb..177de631f 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -30,6 +30,7 @@ use OCP\AppFramework\Bootstrap\IBootContext; use OCP\AppFramework\Bootstrap\IBootstrap; use OCP\AppFramework\Bootstrap\IRegistrationContext; +use OCP\Config\IUserConfig; use OCP\DB\Events\AddMissingIndicesEvent; use OCP\Files\IRootFolder; use OCP\IAppConfig; @@ -124,6 +125,7 @@ public function register(IRegistrationContext $context): void { $c->get(GroupHelper::class), $c->get(UserSettings::class), $c->get(IEmailValidator::class), + $c->get(IUserConfig::class), )); $context->registerCapability(Capabilities::class); diff --git a/lib/Controller/APIv2Controller.php b/lib/Controller/APIv2Controller.php index caefc3790..a584ddba4 100644 --- a/lib/Controller/APIv2Controller.php +++ b/lib/Controller/APIv2Controller.php @@ -253,11 +253,11 @@ public function listFilters(): DataResponse { protected function get(string $filter, int $since, int $limit, bool $previews, string $filterObjectType, int $filterObjectId, string $sort, string $search = '', int $from = 0, int $to = 0, string $actor = ''): DataResponse { try { $this->validateParameters($filter, $since, $limit, $previews, $filterObjectType, $filterObjectId, $sort, $search, $from, $to, $actor); - } catch (InvalidFilterException $e) { + } catch (InvalidFilterException) { return new DataResponse([], Http::STATUS_NOT_FOUND); } catch (InvalidSearchCriteriaException $e) { return new DataResponse(['message' => $e->getMessage()], Http::STATUS_BAD_REQUEST); - } catch (\OutOfBoundsException $e) { + } catch (\OutOfBoundsException) { return new DataResponse([], Http::STATUS_FORBIDDEN); } diff --git a/lib/Controller/ActivitiesController.php b/lib/Controller/ActivitiesController.php index 6eac42a96..95970db1c 100644 --- a/lib/Controller/ActivitiesController.php +++ b/lib/Controller/ActivitiesController.php @@ -16,6 +16,7 @@ use OCP\AppFramework\Controller; use OCP\AppFramework\Http\TemplateResponse; use OCP\AppFramework\Services\IInitialState; +use OCP\Config\IUserConfig; use OCP\EventDispatcher\IEventDispatcher; use OCP\IConfig; use OCP\IRequest; @@ -33,6 +34,7 @@ public function __construct( private readonly IInitialState $initialState, private readonly IURLGenerator $urlGenerator, private readonly IManager $activityManager, + private readonly IUserConfig $userConfig, ) { parent::__construct($appName, $request); } @@ -88,7 +90,7 @@ protected function getPersonalSettingsLink(): string { * Link to RSS feed if there is a RSS token, empty string otherwise */ protected function getRSSLink(): string { - $rssToken = $this->config->getUserValue($this->userId, 'activity', 'rsstoken'); + $rssToken = $this->userConfig->getValueString($this->userId, 'activity', 'rsstoken'); if ($rssToken) { return $this->urlGenerator->linkToRouteAbsolute('activity.Feed.show', ['token' => $rssToken]); } else { diff --git a/lib/Controller/FeedController.php b/lib/Controller/FeedController.php index fb8cebc21..99a686ac4 100644 --- a/lib/Controller/FeedController.php +++ b/lib/Controller/FeedController.php @@ -18,7 +18,7 @@ use OCP\AppFramework\Http\Attribute\NoCSRFRequired; use OCP\AppFramework\Http\Attribute\PublicPage; use OCP\AppFramework\Http\TemplateResponse; -use OCP\IConfig; +use OCP\Config\IUserConfig; use OCP\IL10N; use OCP\IRequest; use OCP\IURLGenerator; @@ -38,8 +38,8 @@ public function __construct( protected IURLGenerator $urlGenerator, protected IManager $activityManager, protected IFactory $l10nFactory, - protected IConfig $config, protected ThemingDefaults $themingDefaults, + private readonly IUserConfig $userConfig, ) { parent::__construct($appName, $request); } @@ -51,7 +51,7 @@ public function show(string $filter = 'all'): TemplateResponse { $response = new TemplateResponse('activity', 'rss', [], ''); try { $user = $this->activityManager->getCurrentUserId(); - $userLang = $this->config->getUserValue($user, 'core', 'lang'); + $userLang = $this->userConfig->getValueString($user, 'core', 'lang'); // Overwrite user and language in the helper $this->l = $this->l10nFactory->get('activity', $userLang); diff --git a/lib/Controller/SettingsController.php b/lib/Controller/SettingsController.php index 4d54c2f49..16d1c63f8 100644 --- a/lib/Controller/SettingsController.php +++ b/lib/Controller/SettingsController.php @@ -13,7 +13,8 @@ use OCP\Activity\IManager; use OCP\AppFramework\Controller; use OCP\AppFramework\Http\DataResponse; -use OCP\IConfig; +use OCP\Config\IUserConfig; +use OCP\IAppConfig; use OCP\IL10N; use OCP\IRequest; use OCP\IURLGenerator; @@ -26,13 +27,14 @@ class SettingsController extends Controller { public function __construct( string $appName, IRequest $request, - protected IConfig $config, protected ISecureRandom $random, protected IURLGenerator $urlGenerator, protected IManager $manager, protected UserSettings $userSettings, protected IL10N $l10n, CurrentUser $currentUser, + private readonly IAppConfig $appConfig, + private readonly IUserConfig $userConfig, ) { parent::__construct($appName, $request); $this->user = (string)$currentUser->getUID(); @@ -55,14 +57,14 @@ public function personal( ) { $settings = $this->manager->getSettings(); foreach ($settings as $setting) { - $this->config->setUserValue( + $this->userConfig->setValueString( $this->user, 'activity', 'notify_notification_' . $setting->getIdentifier(), (string)(int)$this->request->getParam($setting->getIdentifier() . '_notification', false) ); if ($setting->canChangeMail()) { - $this->config->setUserValue( + $this->userConfig->setValueString( $this->user, 'activity', 'notify_email_' . $setting->getIdentifier(), (string)(int)$this->request->getParam($setting->getIdentifier() . '_email', false) @@ -79,22 +81,22 @@ public function personal( $email_batch_time = 0; } - $this->config->setUserValue( + $this->userConfig->setValueString( $this->user, 'activity', 'notify_setting_batchtime', (string)$email_batch_time ); - $this->config->setUserValue( + $this->userConfig->setValueString( $this->user, 'activity', 'notify_setting_self', (string)(int)$notify_setting_self ); - $this->config->setUserValue( + $this->userConfig->setValueString( $this->user, 'activity', 'notify_setting_selfemail', (string)(int)$notify_setting_selfemail ); - $this->config->setUserValue( + $this->userConfig->setValueString( $this->user, 'activity', 'notify_setting_activity_digest', (string)(int)$activity_digest @@ -119,14 +121,14 @@ public function admin( $notify_setting_selfemail = false) { $settings = $this->manager->getSettings(); foreach ($settings as $setting) { - $this->config->setAppValue( + $this->appConfig->setValueString( 'activity', 'notify_notification_' . $setting->getIdentifier(), (string)(int)$this->request->getParam($setting->getIdentifier() . '_notification', false) ); if ($setting->canChangeMail()) { - $this->config->setAppValue( + $this->appConfig->setValueString( 'activity', 'notify_email_' . $setting->getIdentifier(), (string)(int)$this->request->getParam($setting->getIdentifier() . '_email', false) @@ -143,17 +145,17 @@ public function admin( $email_batch_time = 0; } - $this->config->setAppValue( + $this->appConfig->setValueString( 'activity', 'notify_setting_batchtime', (string)$email_batch_time ); - $this->config->setAppValue( + $this->appConfig->setValueString( 'activity', 'notify_setting_self', (string)(int)$notify_setting_self ); - $this->config->setAppValue( + $this->appConfig->setValueString( 'activity', 'notify_setting_selfemail', (string)(int)$notify_setting_selfemail @@ -181,13 +183,13 @@ public function feed(bool $enable) { // Check for collisions while (!empty($conflicts)) { $token = $this->random->generate(30, ISecureRandom::CHAR_UPPER . ISecureRandom::CHAR_LOWER . ISecureRandom::CHAR_DIGITS); - $conflicts = $this->config->getUsersForUserValue('activity', 'rsstoken', $token); + $conflicts = iterator_to_array($this->userConfig->searchUsersByValueString('activity', 'rsstoken', $token)); } $tokenUrl = $this->urlGenerator->linkToRouteAbsolute('activity.Feed.show', ['token' => $token]); } - $this->config->setUserValue($this->user, 'activity', 'rsstoken', $token); + $this->userConfig->setValueString($this->user, 'activity', 'rsstoken', $token); return new DataResponse([ 'data' => [ diff --git a/lib/DigestSender.php b/lib/DigestSender.php index e497366d7..528b5f9af 100644 --- a/lib/DigestSender.php +++ b/lib/DigestSender.php @@ -10,6 +10,8 @@ use OCP\Activity\IEvent; use OCP\Activity\IManager; +use OCP\Config\IUserConfig; +use OCP\Config\ValueType; use OCP\Defaults; use OCP\IConfig; use OCP\IDateTimeFormatter; @@ -37,14 +39,18 @@ public function __construct( private readonly IFactory $l10nFactory, private readonly IDateTimeFormatter $dateTimeFormatter, private readonly LoggerInterface $logger, + private readonly IUserConfig $userConfig, ) { } public function sendDigests(int $now): void { $users = $this->getDigestUsers(); - $userLanguages = $this->config->getUserValueForUsers('core', 'lang', $users); - $userTimezones = $this->config->getUserValueForUsers('core', 'timezone', $users); - $digestDate = $this->config->getUserValueForUsers('activity', 'digest', $users); + /** @var array */ + $userLanguages = $this->userConfig->getValuesByUsers('core', 'lang', ValueType::STRING, userIds: $users); + /** @var array */ + $userTimezones = $this->userConfig->getValuesByUsers('core', 'timezone', ValueType::STRING, userIds: $users); + /** @var array */ + $digestDate = $this->userConfig->getValuesByUsers('activity', 'digest', ValueType::STRING, userIds: $users); $defaultLanguage = $this->config->getSystemValue('default_language', 'en'); $defaultTimeZone = date_default_timezone_get(); $timezoneDigestDay = []; @@ -103,7 +109,7 @@ public function sendDigests(int $now): void { } // We still update the digest time after an failed email, // so it hopefully works tomorrow - $this->config->setUserValue($userObject->getUID(), 'activity', 'digest', $timezoneDigestDay[$timezone]); + $this->userConfig->setValueString($userObject->getUID(), 'activity', 'digest', $timezoneDigestDay[$timezone]); } $this->activityManager->setRequirePNG(false); @@ -115,11 +121,11 @@ public function sendDigests(int $now): void { * @return string[] */ private function getDigestUsers(): array { - return $this->config->getUsersForUserValue('activity', 'notify_setting_activity_digest', '1'); + return iterator_to_array($this->userConfig->searchUsersByValueString('activity', 'notify_setting_activity_digest', '1'), false); } private function getLastSendActivity(string $user, int $now): int { - $lastSend = (int)$this->config->getUserValue($user, 'activity', 'activity_digest_last_send', 0); + $lastSend = (int)$this->userConfig->getValueString($user, 'activity', 'activity_digest_last_send', '0'); if ($lastSend > 0) { return $lastSend; } @@ -135,7 +141,7 @@ private function updateLastSentForUser(IUser $user, int $now): void { ['max' => $lastActivityId] = $this->data->getActivitySince($uid, $lastSend, true); $lastActivityId = (int)$lastActivityId; - $this->config->setUserValue($uid, 'activity', 'activity_digest_last_send', (string)$lastActivityId); + $this->userConfig->setValueString($uid, 'activity', 'activity_digest_last_send', (string)$lastActivityId); } public function sendDigestForUser(IUser $user, int $now, string $timezone, string $language): void { @@ -222,7 +228,7 @@ public function sendDigestForUser(IUser $user, int $now, string $timezone, strin } try { $this->mailer->send($message); - $this->config->setUserValue($uid, 'activity', 'activity_digest_last_send', (string)$lastActivityId); + $this->userConfig->setValueString($uid, 'activity', 'activity_digest_last_send', (string)$lastActivityId); } catch (\Exception $e) { $this->logger->error($e->getMessage()); return; diff --git a/lib/Listener/SetUserDefaults.php b/lib/Listener/SetUserDefaults.php index 80f7861fa..72e8eed08 100644 --- a/lib/Listener/SetUserDefaults.php +++ b/lib/Listener/SetUserDefaults.php @@ -9,10 +9,10 @@ namespace OCA\Activity\Listener; use OCP\Config\Exceptions\UnknownKeyException; +use OCP\Config\IUserConfig; use OCP\EventDispatcher\Event; use OCP\EventDispatcher\IEventListener; use OCP\IAppConfig; -use OCP\IConfig; use OCP\IUser; use OCP\User\Events\PostLoginEvent; @@ -22,8 +22,8 @@ class SetUserDefaults implements IEventListener { public function __construct( - private readonly IConfig $config, private readonly IAppConfig $appConfig, + private readonly IUserConfig $userConfig, ) { } @@ -38,7 +38,7 @@ public function handle(Event $event): void { } private function setDefaultsForUser(IUser $user): void { - if ($this->config->getUserValue($user->getUID(), 'activity', 'configured', 'no') === 'yes') { + if ($this->userConfig->getValueString($user->getUID(), 'activity', 'configured', 'no') === 'yes') { // Already has settings return; } @@ -48,13 +48,13 @@ private function setDefaultsForUser(IUser $user): void { continue; } - if ($this->config->getUserValue($user->getUID(), 'activity', $key, null) !== null) { + if ($this->userConfig->hasKey($user->getUID(), 'activity', $key)) { // Already has this setting continue; } try { - $this->config->setUserValue( + $this->userConfig->setValueString( $user->getUID(), 'activity', $key, @@ -66,6 +66,6 @@ private function setDefaultsForUser(IUser $user): void { } // Mark settings as configured - $this->config->setUserValue($user->getUID(), 'activity', 'configured', 'yes'); + $this->userConfig->setValueString($user->getUID(), 'activity', 'configured', 'yes'); } } diff --git a/lib/MailQueueHandler.php b/lib/MailQueueHandler.php index b0e9449de..173767445 100644 --- a/lib/MailQueueHandler.php +++ b/lib/MailQueueHandler.php @@ -9,6 +9,8 @@ use OCP\Activity\IEvent; use OCP\Activity\IManager; +use OCP\Config\IUserConfig; +use OCP\Config\ValueType; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\Defaults; use OCP\IAppConfig; @@ -59,6 +61,7 @@ public function __construct( protected GroupHelper $groupHelper, protected UserSettings $userSettings, protected IEmailValidator $emailValidator, + private readonly IUserConfig $userConfig, ) { } @@ -83,9 +86,12 @@ public function sendEmails(int $limit, int $sendTime, bool $forceSending = false return 0; } - $userLanguages = $this->config->getUserValueForUsers('core', 'lang', $affectedUsers); - $userTimezones = $this->config->getUserValueForUsers('core', 'timezone', $affectedUsers); - $userEnabled = $this->config->getUserValueForUsers('core', 'enabled', $affectedUsers); + /** @var array */ + $userLanguages = $this->userConfig->getValuesByUsers('core', 'lang', ValueType::STRING, userIds: $affectedUsers); + /** @var array */ + $userTimezones = $this->userConfig->getValuesByUsers('core', 'timezone', ValueType::STRING, userIds: $affectedUsers); + /** @var array */ + $userEnabled = $this->userConfig->getValuesByUsers('core', 'enabled', ValueType::STRING, userIds: $affectedUsers); // Send Email $default_lang = $this->config->getSystemValue('default_language', 'en'); diff --git a/lib/UserSettings.php b/lib/UserSettings.php index 5be4cd2e0..c524e0d84 100644 --- a/lib/UserSettings.php +++ b/lib/UserSettings.php @@ -11,7 +11,8 @@ use OCP\Activity\ActivitySettings; use OCP\Activity\Exceptions\SettingNotFoundException; use OCP\Activity\IManager; -use OCP\IConfig; +use OCP\Config\IUserConfig; +use OCP\IAppConfig; /** * Class UserSettings @@ -32,7 +33,8 @@ class UserSettings { public function __construct( protected IManager $manager, - protected IConfig $config, + private readonly IAppConfig $appConfig, + private readonly IUserConfig $userConfig, ) { } @@ -46,7 +48,7 @@ public function __construct( * @param string $type One of the activity types, 'batchtime' or 'self' */ public function getUserSetting(string $user, string $method, string $type): bool|int { - if ($method === 'email' && $this->config->getAppValue('activity', 'enable_email', 'yes') === 'no') { + if ($method === 'email' && $this->appConfig->getValueString('activity', 'enable_email', 'yes') === 'no') { return false; } @@ -56,19 +58,19 @@ public function getUserSetting(string $user, string $method, string $type): bool } if (is_bool($defaultSetting)) { - return (bool)$this->config->getUserValue( + return (bool)$this->userConfig->getValueString( $user, 'activity', 'notify_' . $method . '_' . $type, - $defaultSetting + (string)(int)$defaultSetting ); } - return (int)$this->config->getUserValue( + return (int)$this->userConfig->getValueString( $user, 'activity', 'notify_' . $method . '_' . $type, - $defaultSetting + (string)$defaultSetting ); } @@ -79,14 +81,14 @@ public function getUserSetting(string $user, string $method, string $type): bool public function getAdminSetting(string $method, string $type): bool|int { $defaultSetting = $this->getDefaultSetting($method, $type); if (is_bool($defaultSetting)) { - return (bool)$this->config->getAppValue( + return (bool)$this->appConfig->getValueString( 'activity', 'notify_' . $method . '_' . $type, (string)$defaultSetting ); } - return (int)$this->config->getAppValue( + return (int)$this->appConfig->getValueString( 'activity', 'notify_' . $method . '_' . $type, (string)$defaultSetting @@ -176,7 +178,7 @@ public function filterUsersBySetting(array $users, string $method, string $type) return []; } - if ($method === 'email' && $this->config->getAppValue('activity', 'enable_email', 'yes') === 'no') { + if ($method === 'email' && $this->appConfig->getValueString('activity', 'enable_email', 'yes') === 'no') { return []; } @@ -186,7 +188,7 @@ public function filterUsersBySetting(array $users, string $method, string $type) } $filteredUsers = []; - $potentialUsers = $this->config->getUserValueForUsers('activity', 'notify_' . $method . '_' . $type, $users); + $potentialUsers = $this->userConfig->getValuesByUsers('activity', 'notify_' . $method . '_' . $type, userIds: $users); foreach ($potentialUsers as $user => $value) { if ($value) { $filteredUsers[$user] = true; @@ -196,7 +198,7 @@ public function filterUsersBySetting(array $users, string $method, string $type) // Get the batch time setting from the database if ($method === 'email') { - $potentialUsers = $this->config->getUserValueForUsers('activity', 'notify_setting_batchtime', array_keys($filteredUsers)); + $potentialUsers = $this->userConfig->getValuesByUsers('activity', 'notify_setting_batchtime', userIds: array_keys($filteredUsers)); foreach ($potentialUsers as $user => $value) { $filteredUsers[$user] = $value; } diff --git a/rector.php b/rector.php index 484e82f3b..8f6198591 100644 --- a/rector.php +++ b/rector.php @@ -5,8 +5,10 @@ * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ +use Nextcloud\Rector\Set\NextcloudSets; use Rector\Config\RectorConfig; use Rector\Php80\Rector\Class_\ClassPropertyAssignToConstructorPromotionRector; +use Rector\Php81\Rector\FuncCall\NullToStrictStringFuncCallArgRector; return RectorConfig::configure() ->withPaths([ @@ -15,13 +17,16 @@ ]) ->withSkip([ __DIR__ . '/tests/stubs', + NullToStrictStringFuncCallArgRector::class, ]) ->withPreparedSets( deadCode: true, typeDeclarations: true, )->withPhpSets( php82: true, - )->withConfiguredRule(ClassPropertyAssignToConstructorPromotionRector::class, [ + )->withSets([ + NextcloudSets::NEXTCLOUD_35, + ])->withConfiguredRule(ClassPropertyAssignToConstructorPromotionRector::class, [ 'inline_public' => true, 'rename_property' => true, ]); diff --git a/tests/Controller/APIv1ControllerTest.php b/tests/Controller/APIv1ControllerTest.php index 367ebb7dd..d2b4e12b8 100644 --- a/tests/Controller/APIv1ControllerTest.php +++ b/tests/Controller/APIv1ControllerTest.php @@ -37,7 +37,9 @@ use OCP\Activity\IExtension; use OCP\Activity\IManager; use OCP\AppFramework\Utility\ITimeFactory; +use OCP\Config\IUserConfig; use OCP\Files\IRootFolder; +use OCP\IAppConfig; use OCP\IConfig; use OCP\IDBConnection; use OCP\IL10N; @@ -197,6 +199,14 @@ public function testGet(string $user, int $start, int $count, array $expected): $config ->method('getUserValue') ->willReturnArgument(3); + $appConfig = $this->createMock(IAppConfig::class); + $appConfig + ->method('getValueString') + ->willReturnArgument(2); + $userConfig = $this->createMock(IUserConfig::class); + $userConfig + ->method('getValueString') + ->willReturnArgument(3); $l = $this->createMock(IL10N::class); $l @@ -235,7 +245,7 @@ public function testGet(string $user, int $start, int $count, array $expected): $this->createMock(IRequest::class), $data, new GroupHelper($l, $activityManager, $this->createMock(IValidator::class), $this->createMock(LoggerInterface::class)), - new UserSettings($activityManager, $config), + new UserSettings($activityManager, $appConfig, $userConfig), $currentUser, Server::get(IDBConnection::class), ); diff --git a/tests/Controller/ActivitiesControllerTest.php b/tests/Controller/ActivitiesControllerTest.php index 3b79d2525..c30a07158 100644 --- a/tests/Controller/ActivitiesControllerTest.php +++ b/tests/Controller/ActivitiesControllerTest.php @@ -28,6 +28,7 @@ use OCA\Activity\Tests\TestCase; use OCP\Activity\IManager; use OCP\AppFramework\Services\IInitialState; +use OCP\Config\IUserConfig; use OCP\EventDispatcher\IEventDispatcher; use OCP\IConfig; use OCP\IRequest; @@ -43,6 +44,7 @@ class ActivitiesControllerTest extends TestCase { protected MockObject&IRequest $request; protected IConfig&MockObject $config; + protected IUserConfig&MockObject $userConfig; protected Data&MockObject $data; protected IEventDispatcher&MockObject $eventDispatcher; protected IInitialState&MockObject $initialState; @@ -54,6 +56,7 @@ protected function setUp(): void { parent::setUp(); $this->config = $this->createMock(IConfig::class); + $this->userConfig = $this->createMock(IUserConfig::class); $this->data = $this->createMock(Data::class); $this->eventDispatcher = $this->createMock(IEventDispatcher::class); $this->request = $this->createMock(IRequest::class); @@ -76,6 +79,7 @@ protected function getController(array $methods = []): ActivitiesController { $this->initialState, $this->urlGenerator, $this->activityManager, + $this->userConfig, ); } @@ -90,6 +94,7 @@ protected function getController(array $methods = []): ActivitiesController { $this->initialState, $this->urlGenerator, $this->activityManager, + $this->userConfig, ]) ->onlyMethods($methods) ->getMock(); diff --git a/tests/Controller/FeedControllerTest.php b/tests/Controller/FeedControllerTest.php index 5112b111c..c7af55c59 100644 --- a/tests/Controller/FeedControllerTest.php +++ b/tests/Controller/FeedControllerTest.php @@ -32,7 +32,7 @@ use OCA\Theming\ThemingDefaults; use OCP\Activity\IManager; use OCP\AppFramework\Http\TemplateResponse; -use OCP\IConfig; +use OCP\Config\IUserConfig; use OCP\IRequest; use OCP\IURLGenerator; use OCP\IUser; @@ -45,7 +45,7 @@ use UnexpectedValueException; class FeedControllerTest extends TestCase { - protected IConfig&MockObject $config; + protected IUserConfig&MockObject $userConfig; protected MockObject&IRequest $request; protected Data&MockObject $data; protected GroupHelper&MockObject $helper; @@ -61,7 +61,7 @@ protected function setUp(): void { $this->data = $this->createMock(Data::class); $this->helper = $this->createMock(GroupHelper::class); $this->userSettings = $this->createMock(UserSettings::class); - $this->config = $this->createMock(IConfig::class); + $this->userConfig = $this->createMock(IUserConfig::class); $this->request = $this->createMock(IRequest::class); $this->session = $this->createMock(IUserSession::class); $this->manager = $this->createMock(IManager::class); @@ -78,8 +78,8 @@ protected function setUp(): void { $urlGenerator, $this->manager, Server::get(IFactory::class), - $this->config, $this->themingDefault, + $this->userConfig, ); } diff --git a/tests/DigestSenderTest.php b/tests/DigestSenderTest.php index e278153f3..7734f95eb 100644 --- a/tests/DigestSenderTest.php +++ b/tests/DigestSenderTest.php @@ -14,6 +14,7 @@ use OCA\Activity\UserSettings; use OCP\Activity\IEvent; use OCP\Activity\IManager; +use OCP\Config\IUserConfig; use OCP\Defaults; use OCP\IConfig; use OCP\IDateTimeFormatter; @@ -28,6 +29,7 @@ class DigestSenderTest extends TestCase { protected IConfig&MockObject $config; + protected IUserConfig&MockObject $userConfig; protected Data&MockObject $data; protected IMailer&MockObject $mailer; protected IManager&MockObject $activityManager; @@ -39,6 +41,7 @@ protected function setUp(): void { parent::setUp(); $this->config = $this->createMock(IConfig::class); + $this->userConfig = $this->createMock(IUserConfig::class); $this->data = $this->createMock(Data::class); $this->mailer = $this->createMock(IMailer::class); $this->activityManager = $this->createMock(IManager::class); @@ -62,13 +65,14 @@ protected function setUp(): void { $l10nFactory, $this->createMock(IDateTimeFormatter::class), $this->logger, + $this->userConfig, ); } protected function expectDigestUsers(array $users, array $timezones): void { - $this->config->method('getUsersForUserValue') - ->willReturn($users); - $this->config->method('getUserValueForUsers') + $this->userConfig->method('searchUsersByValueString') + ->willReturnCallback(static fn (): \Generator => yield from $users); + $this->userConfig->method('getValuesByUsers') ->willReturnCallback(static fn (string $app, string $key): array => ($app === 'core' && $key === 'timezone') ? $timezones : []); } @@ -130,8 +134,8 @@ public function testUserWithoutEmailIsSkippedBeforeBuildingTheDigest(): void { $this->mailer->expects($this->never()) ->method('send'); // The marker still moves so enabling an address later does not flood them - $this->config->expects($this->once()) - ->method('setUserValue') + $this->userConfig->expects($this->once()) + ->method('setValueString') ->with('noMailUser', 'activity', 'activity_digest_last_send', $this->anything()); $this->digestSender->sendDigests(1700000000); @@ -140,7 +144,7 @@ public function testUserWithoutEmailIsSkippedBeforeBuildingTheDigest(): void { public function testCurrentUserIsResetWhenThereIsNothingToSend(): void { $user = $this->createUser('someUser', 'user@example.com'); - $this->config->method('getUserValue') + $this->userConfig->method('getValueString') ->willReturn('5'); $this->data->method('getActivitySince') ->willReturn(['count' => 0, 'max' => 5]); diff --git a/tests/Listener/SetUserDefaultsTest.php b/tests/Listener/SetUserDefaultsTest.php index b7f0094b3..f7b4f970c 100644 --- a/tests/Listener/SetUserDefaultsTest.php +++ b/tests/Listener/SetUserDefaultsTest.php @@ -25,8 +25,8 @@ namespace OCA\Activity\Tests\Listener; use OCA\Activity\Listener\SetUserDefaults; +use OCP\Config\IUserConfig; use OCP\IAppConfig; -use OCP\IConfig; use OCP\IUser; use OCP\User\Events\PostLoginEvent; use PHPUnit\Framework\Attributes\DataProvider; @@ -34,7 +34,7 @@ use PHPUnit\Framework\TestCase; class SetUserDefaultsTest extends TestCase { - private IConfig&MockObject $config; + private IUserConfig&MockObject $userConfig; private IAppConfig&MockObject $appConfig; private SetUserDefaults $listener; private PostLoginEvent $event; @@ -42,41 +42,43 @@ class SetUserDefaultsTest extends TestCase { public const UID = 'myuser'; public function setUp(): void { - $this->config = $this->createMock(IConfig::class); + $this->userConfig = $this->createMock(IUserConfig::class); $this->appConfig = $this->createMock(IAppConfig::class); $user = $this->createMock(IUser::class); $user->expects($this->atLeast(1))->method('getUID')->willReturn(self::UID); $this->event = new PostLoginEvent($user, self::UID, 'somepassword', true); - $this->listener = new SetUserDefaults($this->config, $this->appConfig); + $this->listener = new SetUserDefaults($this->appConfig, $this->userConfig); } public function testSettingUserDefaultsAlreadyConfigured(): void { - $this->config->expects($this->once())->method('getUserValue')->with(self::UID, 'activity', 'configured', 'no')->willReturn('yes'); + $this->userConfig->expects($this->once())->method('getValueString')->with(self::UID, 'activity', 'configured', 'no')->willReturn('yes'); $this->appConfig->expects($this->never())->method('getKeys'); $this->listener->handle($this->event); } #[DataProvider('dataForTestSettingUserDefaultsNotConfigured')] - public function testSettingUserDefaultsNotConfigured(string $key, array $getUserValueArgs, array $getUserValueReturns, array $setUserValuesArgs): void { - $matcher = $this->exactly(count($getUserValueArgs)); - $this->config - ->expects($matcher) - ->method('getUserValue') - ->willReturnCallback(function () use ($matcher, $getUserValueReturns) { - $invocation = $matcher->numberOfInvocations(); - return $getUserValueReturns[$invocation - 1]; - }); - $matcher = $this->exactly(count($setUserValuesArgs)); - $this->config - ->expects($matcher) - ->method('setUserValue') - ->willReturnCallback(function () use ($matcher, $setUserValuesArgs) { - $invocation = $matcher->numberOfInvocations(); - return $setUserValuesArgs[$invocation - 1]; + public function testSettingUserDefaultsNotConfigured(string $key, ?bool $hasKey, array $setValueStringArgs): void { + $this->userConfig + ->expects($this->once()) + ->method('getValueString') + ->with(self::UID, 'activity', 'configured', 'no') + ->willReturn('no'); + $this->userConfig + ->expects($hasKey === null ? $this->never() : $this->once()) + ->method('hasKey') + ->with(self::UID, 'activity', $key) + ->willReturn((bool)$hasKey); + $calls = []; + $this->userConfig + ->expects($this->exactly(count($setValueStringArgs))) + ->method('setValueString') + ->willReturnCallback(function (string $userId, string $app, string $key, string $value) use (&$calls): bool { + $calls[] = [$userId, $app, $key, $value]; + return true; }); $this->appConfig - ->expects($this->exactly(count($setUserValuesArgs) > 1 ? 1 : 0)) + ->expects($this->exactly(count($setValueStringArgs) > 1 ? 1 : 0)) ->method('getValueString') ->with('activity', $key, '') ->willReturn('defaultAppValue'); @@ -86,26 +88,25 @@ public function testSettingUserDefaultsNotConfigured(string $key, array $getUser ->willReturn([$key]); $this->listener->handle($this->event); + + $this->assertSame($setValueStringArgs, $calls); } public static function dataForTestSettingUserDefaultsNotConfigured(): array { return [ [ 'not_notify', - [[self::UID, 'activity', 'configured', 'no']], - ['no'], + null, [[self::UID, 'activity', 'configured', 'yes']] ], [ 'notify_something', - [[self::UID, 'activity', 'configured', 'no'], [self::UID, 'activity', 'notify_something', null]], - ['no', 'not_null'], + true, [[self::UID, 'activity', 'configured', 'yes']] ], [ 'notify_something', - [[self::UID, 'activity', 'configured', 'no'], [self::UID, 'activity', 'notify_something', null]], - ['no', null], + false, [[self::UID, 'activity', 'notify_something', 'defaultAppValue'], [self::UID, 'activity', 'configured', 'yes']] ], ]; diff --git a/tests/MailQueueHandlerTest.php b/tests/MailQueueHandlerTest.php index 9cdfce3d1..01d2340ff 100644 --- a/tests/MailQueueHandlerTest.php +++ b/tests/MailQueueHandlerTest.php @@ -34,6 +34,7 @@ use OCA\Activity\UserSettings; use OCP\Activity\IEvent; use OCP\Activity\IManager; +use OCP\Config\IUserConfig; use OCP\IAppConfig; use OCP\IConfig; use OCP\IDateTimeFormatter; @@ -68,6 +69,7 @@ class MailQueueHandlerTest extends TestCase { protected IValidator&MockObject $richObjectValidator; protected IAppConfig&MockObject $appConfig; protected IConfig&MockObject $config; + protected IUserConfig&MockObject $userConfig; protected MockObject&LoggerInterface $logger; protected IDateTimeFormatter&MockObject $dateTimeFormatter; @@ -84,6 +86,7 @@ protected function setUp(): void { $this->lFactory = $this->createMock(IFactory::class); $this->appConfig = $this->createMock(IAppConfig::class); $this->config = $this->createMock(IConfig::class); + $this->userConfig = $this->createMock(IUserConfig::class); $this->logger = $this->createMock(LoggerInterface::class); $this->dateTimeFormatter = $this->createMock(IDateTimeFormatter::class); $this->data = $this->createMock(Data::class); @@ -150,6 +153,7 @@ protected function setUp(): void { $this->groupHelper, $this->userSettings, $this->emailValidator, + $this->userConfig, ); } @@ -322,7 +326,7 @@ public function testSendEmailsDeletesQueueOnMailerFailure(): void { $this->lFactory->method('get') ->willReturn($this->createMock(IL10N::class)); - $this->config->method('getUserValueForUsers') + $this->userConfig->method('getValuesByUsers') ->willReturn([]); $this->config->method('getSystemValue') ->willReturn('en'); @@ -362,7 +366,7 @@ public function testSendEmailsDeletesQueueOnSendReturnFalse(): void { $this->userManager->method('get') ->willReturn($userObject); - $this->config->method('getUserValueForUsers') + $this->userConfig->method('getValuesByUsers') ->willReturn([]); $this->config->method('getSystemValue') ->willReturn('en'); diff --git a/tests/UserSettingsTest.php b/tests/UserSettingsTest.php index 4c0d35310..b89496730 100644 --- a/tests/UserSettingsTest.php +++ b/tests/UserSettingsTest.php @@ -27,20 +27,23 @@ use OCP\Activity\ActivitySettings; use OCP\Activity\Exceptions\SettingNotFoundException; use OCP\Activity\IManager; -use OCP\IConfig; +use OCP\Config\IUserConfig; +use OCP\IAppConfig; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\MockObject\MockObject; class UserSettingsTest extends TestCase { protected UserSettings $userSettings; protected IManager&MockObject $activityManager; - protected IConfig&MockObject $config; + protected IAppConfig&MockObject $appConfig; + protected IUserConfig&MockObject $userConfig; protected function setUp(): void { parent::setUp(); $this->activityManager = $this->createMock(IManager::class); - $this->config = $this->createMock(IConfig::class); - $this->userSettings = new UserSettings($this->activityManager, $this->config); + $this->appConfig = $this->createMock(IAppConfig::class); + $this->userConfig = $this->createMock(IUserConfig::class); + $this->userSettings = new UserSettings($this->activityManager, $this->appConfig, $this->userConfig); } public static function getDefaultSettingData(): array { diff --git a/vendor-bin/rector/composer.json b/vendor-bin/rector/composer.json index 8592ef65e..6520c223f 100644 --- a/vendor-bin/rector/composer.json +++ b/vendor-bin/rector/composer.json @@ -1,5 +1,6 @@ { "require-dev": { - "rector/rector": "^2.0" + "rector/rector": "^2.0", + "nextcloud/rector": "^0.5.2" } } diff --git a/vendor-bin/rector/composer.lock b/vendor-bin/rector/composer.lock index a26fc1d9a..bea70d2e7 100644 --- a/vendor-bin/rector/composer.lock +++ b/vendor-bin/rector/composer.lock @@ -4,9 +4,127 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "57211dc5c619d183df6361f5d7d57fe5", + "content-hash": "faa1d517d94063e2d9198e288793e3c0", "packages": [], "packages-dev": [ + { + "name": "nextcloud/ocp", + "version": "v35.0.1", + "source": { + "type": "git", + "url": "https://github.com/nextcloud-deps/ocp.git", + "reference": "088e38c04842e027ff255d90b689817b74a54e60" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nextcloud-deps/ocp/zipball/088e38c04842e027ff255d90b689817b74a54e60", + "reference": "088e38c04842e027ff255d90b689817b74a54e60", + "shasum": "" + }, + "require": { + "php": "~8.3 || ~8.4 || ~8.5", + "psr/clock": "^1.0", + "psr/container": "^2.0.2", + "psr/event-dispatcher": "^1.0", + "psr/http-client": "^1.0.3", + "psr/log": "^3.0.2", + "symfony/polyfill-intl-normalizer": "^1.38", + "symfony/polyfill-php84": "^1.38", + "symfony/polyfill-php85": "^1.41", + "symfony/polyfill-php86": "^1.41" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-stable35": "35.0.0-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "AGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Christoph Wurst", + "email": "christoph@winzerhof-wurst.at" + }, + { + "name": "Joas Schilling", + "email": "coding@schilljs.com" + } + ], + "description": "Composer package containing Nextcloud's public OCP API and the unstable NCU API", + "support": { + "issues": "https://github.com/nextcloud-deps/ocp/issues", + "source": "https://github.com/nextcloud-deps/ocp/tree/v35.0.1" + }, + "time": "2026-09-23T02:18:13+00:00" + }, + { + "name": "nextcloud/rector", + "version": "v0.5.2", + "source": { + "type": "git", + "url": "https://github.com/nextcloud-libraries/rector.git", + "reference": "9967d62256cd7507f1104491fb2c427456481b56" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nextcloud-libraries/rector/zipball/9967d62256cd7507f1104491fb2c427456481b56", + "reference": "9967d62256cd7507f1104491fb2c427456481b56", + "shasum": "" + }, + "require": { + "nextcloud/ocp": ">=27", + "php": "^8.1", + "rector/rector": "^2.0.4", + "webmozart/assert": "^1.11" + }, + "require-dev": { + "phpunit/phpunit": "^10.5", + "ramsey/devtools": "^2.0" + }, + "type": "library", + "extra": { + "captainhook": { + "force-install": true + }, + "ramsey/devtools": { + "memory-limit": "-1", + "command-prefix": "dev" + }, + "ramsey/conventional-commits": { + "configFile": "conventional-commits.json" + } + }, + "autoload": { + "psr-4": { + "OCP\\": "vendor/nextcloud/ocp/OCP", + "Nextcloud\\Rector\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "AGPL-3.0-or-later" + ], + "authors": [ + { + "name": "Christoph Wurst", + "email": "christoph@winzerhof-wurst.at", + "homepage": "https://wuc.me" + } + ], + "description": "Rector upgrade rules for Nextcloud", + "keywords": [ + "nextcloud", + "refactoring" + ], + "support": { + "issues": "https://github.com/nextcloud-libraries/rector/issues", + "source": "https://github.com/nextcloud-libraries/rector/tree/v0.5.2" + }, + "time": "2026-06-15T06:39:12+00:00" + }, { "name": "phpstan/phpstan", "version": "2.2.8", @@ -71,6 +189,312 @@ ], "time": "2026-08-04T22:21:45+00:00" }, + { + "name": "psr/clock", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/clock.git", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Clock\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for reading the clock.", + "homepage": "https://github.com/php-fig/clock", + "keywords": [ + "clock", + "now", + "psr", + "psr-20", + "time" + ], + "support": { + "issues": "https://github.com/php-fig/clock/issues", + "source": "https://github.com/php-fig/clock/tree/1.0.0" + }, + "time": "2022-11-25T14:36:26+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "psr/http-client", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client" + }, + "time": "2023-09-23T14:17:50+00:00" + }, + { + "name": "psr/http-message", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" + }, + { + "name": "psr/log", + "version": "3.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.2" + }, + "time": "2024-09-11T13:17:53+00:00" + }, { "name": "rector/rector", "version": "2.5.8", @@ -130,6 +554,389 @@ } ], "time": "2026-07-27T06:19:16+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.43.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "ebd57ccc7edb93d632e184efbe7693d2517bab3b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/ebd57ccc7edb93d632e184efbe7693d2517bab3b", + "reference": "ebd57ccc7edb93d632e184efbe7693d2517bab3b", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.43.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-09-23T22:02:09+00:00" + }, + { + "name": "symfony/polyfill-php84", + "version": "v1.43.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php84.git", + "reference": "22c6548499cbe5c5bb072fc0c42312dff062a015" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/22c6548499cbe5c5bb072fc0c42312dff062a015", + "reference": "22c6548499cbe5c5bb072fc0c42312dff062a015", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php84\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php84/tree/v1.43.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-09-23T22:01:09+00:00" + }, + { + "name": "symfony/polyfill-php85", + "version": "v1.43.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php85.git", + "reference": "86a42cbcc07adc331481a5ab310a4024bfc227f6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/86a42cbcc07adc331481a5ab310a4024bfc227f6", + "reference": "86a42cbcc07adc331481a5ab310a4024bfc227f6", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php85\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.5+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php85/tree/v1.43.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-09-25T15:46:47+00:00" + }, + { + "name": "symfony/polyfill-php86", + "version": "v1.43.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php86.git", + "reference": "8840df45b24f33a49dc419fa18dc0057231ae1d8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php86/zipball/8840df45b24f33a49dc419fa18dc0057231ae1d8", + "reference": "8840df45b24f33a49dc419fa18dc0057231ae1d8", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php86\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.6+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php86/tree/v1.43.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-09-25T15:46:47+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.12.1", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "9be6926d8b485f55b9229203f962b51ed377ba68" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/9be6926d8b485f55b9229203f962b51ed377ba68", + "reference": "9be6926d8b485f55b9229203f962b51ed377ba68", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-date": "*", + "ext-filter": "*", + "php": "^7.2 || ^8.0" + }, + "suggest": { + "ext-intl": "", + "ext-simplexml": "", + "ext-spl": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.12.1" + }, + "time": "2025-10-29T15:56:20+00:00" } ], "aliases": [], From da8984d43140551443c5e64607cd3aaa14d010ff Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Thu, 1 Oct 2026 16:20:51 +0200 Subject: [PATCH 08/11] refactor: Port trash hook to node event Signed-off-by: Carl Schwan --- lib/AppInfo/Application.php | 4 +- lib/FilesHooks.php | 8 ++-- lib/FilesHooksStatic.php | 8 ---- lib/Listener/NodeRestoredListener.php | 34 +++++++++++++++ psalm.xml | 1 + tests/FilesHooksTest.php | 4 +- tests/Listener/NodeRestoredListenerTest.php | 48 +++++++++++++++++++++ tests/stubs/oca_files_trashbin_events.php | 8 ++++ 8 files changed, 100 insertions(+), 15 deletions(-) create mode 100644 lib/Listener/NodeRestoredListener.php create mode 100644 tests/Listener/NodeRestoredListenerTest.php create mode 100644 tests/stubs/oca_files_trashbin_events.php diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 177de631f..7cb32a730 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -18,6 +18,7 @@ use OCA\Activity\GroupHelper; use OCA\Activity\Listener\AddMissingIndicesListener; use OCA\Activity\Listener\LoadSidebarScripts; +use OCA\Activity\Listener\NodeRestoredListener; use OCA\Activity\Listener\SetUserDefaults; use OCA\Activity\Listener\ShareEventListener; use OCA\Activity\Listener\UserDeleted; @@ -25,6 +26,7 @@ use OCA\Activity\NotificationGenerator; use OCA\Activity\UserSettings; use OCA\Files\Event\LoadSidebar; +use OCA\Files_Trashbin\Events\NodeRestoredEvent; use OCP\Activity\IManager; use OCP\AppFramework\App; use OCP\AppFramework\Bootstrap\IBootContext; @@ -167,10 +169,10 @@ private function registerFilesActivity(IRegistrationContext $context): void { Util::connectHook('OC_Filesystem', 'delete', FilesHooksStatic::class, 'fileDelete'); Util::connectHook('OC_Filesystem', 'rename', FilesHooksStatic::class, 'fileMove'); Util::connectHook('OC_Filesystem', 'post_rename', FilesHooksStatic::class, 'fileMovePost'); - Util::connectHook('\OCA\Files_Trashbin\Trashbin', 'post_restore', FilesHooksStatic::class, 'fileRestore'); $context->registerEventListener(ShareCreatedEvent::class, ShareEventListener::class); $context->registerEventListener(BeforeShareDeletedEvent::class, ShareEventListener::class); $context->registerEventListener(ShareDeletedFromSelfEvent::class, ShareEventListener::class); + $context->registerEventListener(NodeRestoredEvent::class, NodeRestoredListener::class); } } diff --git a/lib/FilesHooks.php b/lib/FilesHooks.php index 00360619b..87ea4ed2a 100644 --- a/lib/FilesHooks.php +++ b/lib/FilesHooks.php @@ -106,12 +106,12 @@ public function fileDelete($path): void { } /** - * Store the restore hook events + * Store the restore events * - * @param string $path Path of the file that has been restored + * @param Node $node The node that has been restored */ - public function fileRestore($path): void { - $this->addNotificationsForFileAction($path, Files::TYPE_SHARE_RESTORED, 'restored_self', 'restored_by'); + public function fileRestore(Node $node): void { + $this->addNotificationsForFileAction($this->getVisiblePath($node->getPath()), Files::TYPE_SHARE_RESTORED, 'restored_self', 'restored_by'); } private function getFileChangeActivitySettings(int $fileId, array $users, string $type = Files::TYPE_FILE_CHANGED): array { diff --git a/lib/FilesHooksStatic.php b/lib/FilesHooksStatic.php index 82171020a..a0fc1d0cd 100644 --- a/lib/FilesHooksStatic.php +++ b/lib/FilesHooksStatic.php @@ -55,12 +55,4 @@ public static function fileMove(array $params): void { public static function fileMovePost(array $params): void { self::getHooks()->fileMovePost($params['oldpath'], $params['newpath']); } - - /** - * Store the restore hook events - * @param array $params The hook params - */ - public static function fileRestore(array $params): void { - self::getHooks()->fileRestore($params['filePath']); - } } diff --git a/lib/Listener/NodeRestoredListener.php b/lib/Listener/NodeRestoredListener.php new file mode 100644 index 000000000..e7b8cc881 --- /dev/null +++ b/lib/Listener/NodeRestoredListener.php @@ -0,0 +1,34 @@ + + */ +class NodeRestoredListener implements IEventListener { + public function __construct( + private readonly FilesHooks $fileHooks, + ) { + } + + #[\Override] + public function handle(Event $event): void { + if (!$event instanceof NodeRestoredEvent) { + return; + } + + $this->fileHooks->fileRestore($event->getTarget()); + } +} diff --git a/psalm.xml b/psalm.xml index 07b18a1e5..ab7974dba 100644 --- a/psalm.xml +++ b/psalm.xml @@ -30,6 +30,7 @@ + diff --git a/tests/FilesHooksTest.php b/tests/FilesHooksTest.php index d8db7f525..ca0338c6d 100644 --- a/tests/FilesHooksTest.php +++ b/tests/FilesHooksTest.php @@ -259,9 +259,9 @@ public function testFileRestore(): void { $filesHooks->expects($this->once()) ->method('addNotificationsForFileAction') - ->with('path', Files::TYPE_SHARE_RESTORED, 'restored_self', 'restored_by'); + ->with('/folder/file.txt', Files::TYPE_SHARE_RESTORED, 'restored_self', 'restored_by'); - $filesHooks->fileRestore('path'); + $filesHooks->fileRestore($this->getNodeMock(42, '/user/files/folder/file.txt')); } public function testAddNotificationsForFileActionPartFile(): void { diff --git a/tests/Listener/NodeRestoredListenerTest.php b/tests/Listener/NodeRestoredListenerTest.php new file mode 100644 index 000000000..ea3e2d8e4 --- /dev/null +++ b/tests/Listener/NodeRestoredListenerTest.php @@ -0,0 +1,48 @@ +filesHooks = $this->createMock(FilesHooks::class); + $this->listener = new NodeRestoredListener($this->filesHooks); + } + + public function testHandleNodeRestoredEvent(): void { + $source = $this->createMock(File::class); + $target = $this->createMock(File::class); + + $this->filesHooks->expects($this->once()) + ->method('fileRestore') + ->with($target); + + $this->listener->handle(new NodeRestoredEvent($source, $target)); + } + + public function testHandleIgnoresOtherEvents(): void { + $this->filesHooks->expects($this->never()) + ->method('fileRestore'); + + $this->listener->handle(new Event()); + } +} diff --git a/tests/stubs/oca_files_trashbin_events.php b/tests/stubs/oca_files_trashbin_events.php new file mode 100644 index 000000000..a2f333175 --- /dev/null +++ b/tests/stubs/oca_files_trashbin_events.php @@ -0,0 +1,8 @@ + Date: Thu, 1 Oct 2026 16:58:41 +0200 Subject: [PATCH 09/11] fix(ci): Don't try to install viewer anymore Signed-off-by: Carl Schwan --- playwright/start-server.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playwright/start-server.mjs b/playwright/start-server.mjs index da9d9f06e..80ca597c4 100644 --- a/playwright/start-server.mjs +++ b/playwright/start-server.mjs @@ -27,7 +27,7 @@ process.on('SIGINT', stop) const ip = await startNextcloud(process.env.BRANCH ?? 'master', undefined, { exposePort: 8081 }) await waitOnNextcloud(ip) -await configureNextcloud(['viewer', 'activity']) +await configureNextcloud(['activity']) await runOcc(['config:system:set', 'no_unsupported_browser_warning', '--value', 'true', '--type', 'boolean']) await runOcc(['config:system:set', 'appstoreenabled', '--value', 'false', '--type', 'boolean']) await runExec(['php', '-r', '$db = new SQLite3("data/owncloud.db");$db->busyTimeout(5000);$db->exec("PRAGMA journal_mode = wal;");']) From 5548413211a7420c97f6497511930cad34ab7537 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Thu, 1 Oct 2026 17:07:51 +0200 Subject: [PATCH 10/11] fix(playwright): Use role locators to create folders The upload picker's "New folder" entry no longer matches the old data-cy selector, so use the accessible roles like the server's own helpers and wait for the MKCOL request specifically. Assisted-by: ClaudeCode:claude-opus-5-5 Signed-off-by: Carl Schwan --- playwright/e2e/filesUtils.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/playwright/e2e/filesUtils.ts b/playwright/e2e/filesUtils.ts index cb1165e86..1c008e78f 100644 --- a/playwright/e2e/filesUtils.ts +++ b/playwright/e2e/filesUtils.ts @@ -80,13 +80,17 @@ export async function navigateToFolder(page: Page, dirPath: string) { } export async function createFolder(page: Page, dirName: string) { - const mkcolResponse = page.waitForResponse(/\/remote\.php\/dav\/files\//) - - await page.locator('[data-cy-upload-picker] .action-item__menutoggle').first().click() - await page.locator('[data-cy-upload-picker-menu-entry="newFolder"] button').click() - await expect(page.locator('[data-cy-files-new-node-dialog]')).toBeVisible() - await page.locator('[data-cy-files-new-node-dialog-input]').fill(dirName) - await page.locator('[data-cy-files-new-node-dialog-submit]').click() + const mkcolResponse = page.waitForResponse((response) => response.request().method() === 'MKCOL' && /\/remote\.php\/dav\/files\//.test(response.url())) + + await page.locator('[data-cy-upload-picker]') + .getByRole('button', { name: 'New' }) + .first() + .click() + await page.getByRole('menuitem', { name: 'New folder' }).click() + const dialog = page.getByRole('dialog', { name: /create new folder/i }) + await expect(dialog).toBeVisible() + await dialog.getByRole('textbox', { name: 'Folder name' }).fill(dirName) + await dialog.getByRole('button', { name: 'Create' }).click() await mkcolResponse await expect(getRowForFile(page, dirName)).toBeVisible() From bf513c0c981ccd31f03f909bad1f230e2d8d5196 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Thu, 1 Oct 2026 17:15:59 +0200 Subject: [PATCH 11/11] fix(playwright): Fix flaky tests Signed-off-by: Carl Schwan --- playwright/e2e/filesUtils.ts | 11 +++++++---- playwright/e2e/sidebarUtils.ts | 9 +++++++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/playwright/e2e/filesUtils.ts b/playwright/e2e/filesUtils.ts index 1c008e78f..4a4c8985e 100644 --- a/playwright/e2e/filesUtils.ts +++ b/playwright/e2e/filesUtils.ts @@ -53,6 +53,9 @@ export const toggleMenuAction = async ( await menuItem.click({ force: true }) } +const waitForDavRequest = (page: Page, method: string) => + page.waitForResponse((response) => response.request().method() === method && /\/(remote|public)\.php\/dav\/files\//.test(response.url())) + export const getFileListRow = (page: Page, filename: string) => page.locator(`[data-cy-files-list-row-name="${cssEscape(filename)}"]`) @@ -63,7 +66,7 @@ export async function renameFile(page: Page, fileName: string, newName: string) await triggerActionForFile(page, fileName, 'rename') - const moveResponse = page.waitForResponse(/\/(remote|public)\.php\/dav\/files\//) + const moveResponse = waitForDavRequest(page, 'MOVE') await getRowForFile(page, fileName).locator('[data-cy-files-list-row-name] input').clear() await getRowForFile(page, fileName).locator('[data-cy-files-list-row-name] input').fill(`${newName}`) await getRowForFile(page, fileName).locator('[data-cy-files-list-row-name] input').press('Enter') @@ -73,14 +76,14 @@ export async function renameFile(page: Page, fileName: string, newName: string) export async function navigateToFolder(page: Page, dirPath: string) { const directories = dirPath.split('/').filter(Boolean) for (const directory of directories) { - const navResponse = page.waitForResponse(/\/remote\.php\/dav\/files\//) + const navResponse = waitForDavRequest(page, 'PROPFIND') await getRowForFile(page, directory).locator('[data-cy-files-list-row-name-link]').click() await navResponse } } export async function createFolder(page: Page, dirName: string) { - const mkcolResponse = page.waitForResponse((response) => response.request().method() === 'MKCOL' && /\/remote\.php\/dav\/files\//.test(response.url())) + const mkcolResponse = waitForDavRequest(page, 'MKCOL') await page.locator('[data-cy-upload-picker]') .getByRole('button', { name: 'New' }) @@ -101,7 +104,7 @@ export async function moveFile(page: Page, fileName: string, dirName: string) { await page.locator('.file-picker').waitFor() - const moveResponse = page.waitForResponse(/\/(remote|public)\.php\/dav\/files\//) + const moveResponse = waitForDavRequest(page, 'MOVE') if (dirName === '/') { await page.locator('.file-picker').getByRole('button', { name: 'Home' }).click() diff --git a/playwright/e2e/sidebarUtils.ts b/playwright/e2e/sidebarUtils.ts index c80aa06fb..9239f74a7 100644 --- a/playwright/e2e/sidebarUtils.ts +++ b/playwright/e2e/sidebarUtils.ts @@ -25,8 +25,13 @@ export async function showActivityTab(page: Page, fileName: string) { await showSidebarForFile(page, fileName) - await page.locator('#app-sidebar-vue').getByRole('tab', { name: 'Activity' }).click({ force: true }) - await expect(page.locator('#app-sidebar-vue').getByRole('tabpanel', { name: 'Activity' })).toBeVisible() + // The sidebar can still switch to its default tab right after opening, so retry until the selection sticks + const activityTab = page.locator('#app-sidebar-vue').getByRole('tab', { name: 'Activity' }) + await expect(async () => { + await activityTab.click() + await expect(activityTab).toHaveAttribute('aria-selected', 'true', { timeout: 1000 }) + await expect(page.locator('#app-sidebar-vue').getByRole('tabpanel', { name: 'Activity' })).toBeVisible({ timeout: 1000 }) + }).toPass() await activitiesResponse }