diff --git a/lib/Data.php b/lib/Data.php index fa856f26f..357c2d857 100644 --- a/lib/Data.php +++ b/lib/Data.php @@ -39,6 +39,38 @@ 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); + } + + /** + * 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', []); + if (empty($excludedUsers)) { + return false; + } + $author = $event->getAuthor(); + if ($author === '' || !isset($excludedUsers[$author])) { + return false; + } + $rule = $excludedUsers[$author]; + if (is_array($rule)) { + return in_array($event->getType(), $rule, true); + } + return false; + } + /** * Send an event into the activity stream * @@ -46,7 +78,7 @@ public function __construct( * @return int */ public function send(IEvent $event): int { - if ($event->getAffectedUser() === '') { + if (!$this->shouldSend($event)) { return 0; } @@ -104,6 +136,10 @@ public function send(IEvent $event): int { * @throws Exception */ public function bulkSend(IEvent $event, array $affectedUsers): array { + if ($this->isExcludedAuthor($event)) { + return []; + } + $this->connection->beginTransaction(); $activityIds = []; @@ -170,8 +206,7 @@ public function bulkSend(IEvent $event, array $affectedUsers): array { * @return bool */ public function storeMail(IEvent $event, int $latestSendTime): bool { - $affectedUser = $event->getAffectedUser(); - if ($affectedUser === '') { + if (!$this->shouldSend($event)) { return false; } @@ -195,7 +230,7 @@ public function storeMail(IEvent $event, int $latestSendTime): bool { 'amq_appid' => $event->getApp(), 'amq_subject' => $event->getSubject(), 'amq_subjectparams' => json_encode($event->getSubjectParameters()), - 'amq_affecteduser' => $affectedUser, + 'amq_affecteduser' => $event->getAffectedUser(), 'amq_timestamp' => $event->getTimestamp(), 'amq_type' => $event->getType(), 'amq_latest_send' => $latestSendTime, diff --git a/tests/DataTest.php b/tests/DataTest.php index 1ff577a49..a227ea194 100644 --- a/tests/DataTest.php +++ b/tests/DataTest.php @@ -347,6 +347,129 @@ public function testDeleteAffectedUserActivities(): void { $this->deleteTestActivities(); } + public static function dataExcludedAuthor(): array { + return [ + // author+type match → blocked + ['alice', 'target', 'file_created', ['alice' => ['file_created']], false], + // type mismatch → allowed + ['alice', 'target', 'file_created', ['alice' => ['file_deleted']], true], + // different user → allowed + ['bob', 'target', 'file_created', ['alice' => ['file_created']], true], + // empty config → allowed + ['alice', 'target', 'file_created', [], true], + // non-array rule → allowed + ['alice', 'target', 'file_created', ['alice' => 'file_created'], true], + ]; + } + + #[DataProvider('dataExcludedAuthor')] + public function testSendWithExcludedAuthor(string $author, string $affectedUser, string $type, array $excludedUsers, bool $expectedInsert): void { + $this->deleteTestActivities(); + + $this->config->method('getSystemValue') + ->with('activity_log_exclude_users', []) + ->willReturn($excludedUsers); + + $event = $this->realActivityManager->generateEvent(); + $event->setApp('test') + ->setType($type) + ->setAuthor($author) + ->setAffectedUser($affectedUser) + ->setSubject('subject'); + + $result = $this->data->send($event); + $this->assertSame($expectedInsert, $result !== 0); + + $qb = $this->dbConnection->getQueryBuilder(); + $row = $qb->select('user', 'affecteduser') + ->from('activity') + ->where($qb->expr()->eq('app', $qb->createNamedParameter('test'))) + ->orderBy('activity_id', 'DESC') + ->executeQuery() + ->fetch(); + + if ($expectedInsert) { + $this->assertEquals(['user' => $author, 'affecteduser' => $affectedUser], $row); + } else { + $this->assertFalse($row); + } + + $this->deleteTestActivities(); + } + + #[DataProvider('dataExcludedAuthor')] + public function testStoreMailWithExcludedAuthor(string $author, string $affectedUser, string $type, array $excludedUsers, bool $expectedInsert): void { + $this->deleteTestMails(); + + $this->config->method('getSystemValue') + ->with('activity_log_exclude_users', []) + ->willReturn($excludedUsers); + + $time = time(); + $event = $this->realActivityManager->generateEvent(); + $event->setApp('test') + ->setType($type) + ->setAuthor($author) + ->setAffectedUser($affectedUser) + ->setSubject('subject') + ->setTimestamp($time); + + $this->assertSame($expectedInsert, $this->data->storeMail($event, $time + 10)); + + $qb = $this->dbConnection->getQueryBuilder(); + $row = $qb->select('amq_latest_send', 'amq_affecteduser') + ->from('activity_mq') + ->where($qb->expr()->eq('amq_appid', $qb->createNamedParameter('test'))) + ->orderBy('mail_id', 'DESC') + ->executeQuery() + ->fetch(); + + if ($expectedInsert) { + $this->assertEquals(['amq_latest_send' => $time + 10, 'amq_affecteduser' => $affectedUser], $row); + } else { + $this->assertFalse($row); + } + + $this->deleteTestMails(); + } + + #[DataProvider('dataExcludedAuthor')] + public function testBulkSendWithExcludedAuthor(string $author, string $_affectedUser, string $type, array $excludedUsers, bool $expectedInsert): void { + $this->deleteTestActivities(); + + $this->config->method('getSystemValue') + ->with('activity_log_exclude_users', []) + ->willReturn($excludedUsers); + + $event = $this->realActivityManager->generateEvent(); + $event->setApp('test') + ->setType($type) + ->setAuthor($author) + ->setSubject('subject') + ->setTimestamp(time()); + + $bulkUsers = ['user1', 'user2']; + $result = $this->data->bulkSend($event, $bulkUsers); + + if ($expectedInsert) { + $this->assertCount(2, $result); + $this->assertEqualsCanonicalizing($bulkUsers, array_values($result)); + } else { + $this->assertEmpty($result); + } + + $qb = $this->dbConnection->getQueryBuilder(); + $count = (int)$qb->select($qb->func()->count('activity_id', 'count')) + ->from('activity') + ->where($qb->expr()->eq('app', $qb->createNamedParameter('test'))) + ->executeQuery() + ->fetch()['count']; + + $this->assertSame($expectedInsert ? 2 : 0, $count); + + $this->deleteTestActivities(); + } + /** * Delete all testing activities */