Skip to content

Commit bbd2758

Browse files
committed
fix(MailQueueHandler): check enable_email toggle before sending queued emails
The admin Enable notification emails toggle was only checked when queuing new emails. The sendEmails() background job had no knowledge of it and would send all queued entries regardless. Adds an early-return guard using IAppConfig::getValueString() at the top of sendEmails(), consistent with the checks in UserSettings. The queue is left intact so pending notifications can be delivered if the admin re-enables emails. AI-Assisted-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Anna Larch <anna@nextcloud.com>
1 parent 37215b3 commit bbd2758

16 files changed

Lines changed: 265 additions & 324 deletions

.github/workflows/npm-audit-fix.yml

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -67,22 +67,6 @@ jobs:
6767
npm ci
6868
npm run build --if-present
6969
70-
- name: Generate PR body
71-
if: steps.checkout.outcome == 'success'
72-
run: |
73-
{
74-
printf '%s\n\n' "$NPM_AUDIT_MARKDOWN"
75-
echo '## Full `npm audit` report'
76-
echo ''
77-
echo '```'
78-
npm audit 2>&1 || true
79-
echo '```'
80-
echo ''
81-
echo "**Node.js:** $(node --version) | **npm:** $(npm --version) | **Branch:** ${{ matrix.branches }}"
82-
} > pr-body.md
83-
env:
84-
NPM_AUDIT_MARKDOWN: ${{ steps.npm-audit.outputs.markdown }}
85-
8670
- name: Create Pull Request
8771
if: steps.checkout.outcome == 'success'
8872
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
@@ -94,7 +78,7 @@ jobs:
9478
signoff: true
9579
branch: automated/noid/${{ matrix.branches }}-fix-npm-audit
9680
title: '[${{ matrix.branches }}] Fix npm audit'
97-
body-path: pr-body.md
81+
body: ${{ steps.npm-audit.outputs.markdown }}
9882
labels: |
9983
dependencies
10084
3. to review

composer.lock

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

l10n/it.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ OC.L10N.register(
5151
"Loading activities" : "Caricamento delle attività",
5252
"This stream will show events like additions, changes & shares" : "Questo flusso mostrerà gli eventi come aggiunte, cambiamenti e condivisioni",
5353
"No activity yet" : "Ancora nessuna attività",
54-
"New activities" : "Nuove attività",
5554
"Loading more activities" : "Caricamento di altre attività",
5655
"No more activities." : "Nessun'altra attività.",
5756
"Could not enable RSS link" : "Impossibile attivare il collegamento RSS",

l10n/it.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@
4949
"Loading activities" : "Caricamento delle attività",
5050
"This stream will show events like additions, changes & shares" : "Questo flusso mostrerà gli eventi come aggiunte, cambiamenti e condivisioni",
5151
"No activity yet" : "Ancora nessuna attività",
52-
"New activities" : "Nuove attività",
5352
"Loading more activities" : "Caricamento di altre attività",
5453
"No more activities." : "Nessun'altra attività.",
5554
"Could not enable RSS link" : "Impossibile attivare il collegamento RSS",

lib/AppInfo/Application.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
use OCP\AppFramework\Bootstrap\IBootstrap;
3232
use OCP\AppFramework\Bootstrap\IRegistrationContext;
3333
use OCP\DB\Events\AddMissingIndicesEvent;
34+
use OCP\IAppConfig;
3435
use OCP\IConfig;
3536
use OCP\IDateTimeFormatter;
3637
use OCP\IDBConnection;
@@ -117,6 +118,7 @@ public function register(IRegistrationContext $context): void {
117118
$c->get(IFactory::class),
118119
$c->get(IManager::class),
119120
$c->get(IValidator::class),
121+
$c->get(IAppConfig::class),
120122
$c->get(IConfig::class),
121123
$c->get(LoggerInterface::class),
122124
$c->get(Data::class),

lib/Controller/APIv2Controller.php

Lines changed: 67 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,29 @@
2828
use OCP\Notification\IManager as INotificationManager;
2929

3030
class APIv2Controller extends OCSController {
31-
protected string $filter = 'all';
32-
protected int $since = 0;
33-
protected int $limit = 50;
34-
protected string $sort = 'desc';
35-
protected string $objectType = '';
36-
protected int $objectId = 0;
37-
protected string $user = '';
38-
protected bool $loadPreviews = false;
31+
/** @var string */
32+
protected $filter;
33+
34+
/** @var int */
35+
protected $since;
36+
37+
/** @var int */
38+
protected $limit;
39+
40+
/** @var string */
41+
protected $sort;
42+
43+
/** @var string */
44+
protected $objectType;
45+
46+
/** @var int */
47+
protected $objectId;
48+
49+
/** @var string */
50+
protected $user;
51+
52+
/** @var bool */
53+
protected $loadPreviews;
3954

4055
public function __construct(
4156
$appName,
@@ -56,19 +71,26 @@ public function __construct(
5671
}
5772

5873
/**
74+
* @param string $filter
75+
* @param int $since
76+
* @param int $limit
77+
* @param bool $previews
78+
* @param string $objectType
79+
* @param int $objectId
80+
* @param string $sort
5981
* @throws InvalidFilterException when the filter is invalid
6082
* @throws \OutOfBoundsException when no user is given
6183
*/
62-
protected function validateParameters(string $filter, int $since, int $limit, bool $previews, string $objectType, int $objectId, string $sort): void {
63-
$this->filter = $filter;
84+
protected function validateParameters($filter, $since, $limit, $previews, $objectType, $objectId, $sort) {
85+
$this->filter = \is_string($filter) ? $filter : 'all';
6486
if ($this->filter !== $this->data->validateFilter($this->filter)) {
6587
throw new InvalidFilterException('Invalid filter');
6688
}
67-
$this->since = $since;
68-
$this->limit = $limit;
69-
$this->loadPreviews = $previews;
70-
$this->objectType = $objectType;
71-
$this->objectId = $objectId;
89+
$this->since = (int)$since;
90+
$this->limit = (int)$limit;
91+
$this->loadPreviews = (bool)$previews;
92+
$this->objectType = (string)$objectType;
93+
$this->objectId = (int)$objectId;
7294
$this->sort = \in_array($sort, ['asc', 'desc'], true) ? $sort : 'desc';
7395

7496
if (($this->objectType !== '' && $this->objectId === 0) || ($this->objectType === '' && $this->objectId !== 0)) {
@@ -88,15 +110,32 @@ protected function validateParameters(string $filter, int $since, int $limit, bo
88110

89111
/**
90112
* @NoAdminRequired
113+
*
114+
* @param int $since
115+
* @param int $limit
116+
* @param bool $previews
117+
* @param string $object_type
118+
* @param int $object_id
119+
* @param string $sort
120+
* @return DataResponse
91121
*/
92-
public function getDefault(int $since = 0, int $limit = 50, bool $previews = false, string $object_type = '', int $object_id = 0, string $sort = 'desc'): DataResponse {
122+
public function getDefault($since = 0, $limit = 50, $previews = false, $object_type = '', $object_id = 0, $sort = 'desc'): DataResponse {
93123
return $this->get('all', $since, $limit, $previews, $object_type, $object_id, $sort);
94124
}
95125

96126
/**
97127
* @NoAdminRequired
128+
*
129+
* @param string $filter
130+
* @param int $since
131+
* @param int $limit
132+
* @param bool $previews
133+
* @param string $object_type
134+
* @param int $object_id
135+
* @param string $sort
136+
* @return DataResponse
98137
*/
99-
public function getFilter(string $filter, int $since = 0, int $limit = 50, bool $previews = false, string $object_type = '', int $object_id = 0, string $sort = 'desc'): DataResponse {
138+
public function getFilter($filter, $since = 0, $limit = 50, $previews = false, $object_type = '', $object_id = 0, $sort = 'desc'): DataResponse {
100139
return $this->get($filter, $since, $limit, $previews, $object_type, $object_id, $sort);
101140
}
102141

@@ -152,7 +191,17 @@ public function listFilters(): DataResponse {
152191
return new DataResponse($filters);
153192
}
154193

155-
protected function get(string $filter, int $since, int $limit, bool $previews, string $filterObjectType, int $filterObjectId, string $sort): DataResponse {
194+
/**
195+
* @param string $filter
196+
* @param int $since
197+
* @param int $limit
198+
* @param bool $previews
199+
* @param string $filterObjectType
200+
* @param int $filterObjectId
201+
* @param string $sort
202+
* @return DataResponse
203+
*/
204+
protected function get($filter, $since, $limit, $previews, $filterObjectType, $filterObjectId, $sort): DataResponse {
156205
try {
157206
$this->validateParameters($filter, $since, $limit, $previews, $filterObjectType, $filterObjectId, $sort);
158207
} catch (InvalidFilterException $e) {

lib/Data.php

Lines changed: 33 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ public function storeMail(IEvent $event, int $latestSendTime): bool {
226226
* @return array
227227
*
228228
*/
229-
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): array {
229+
public function get(GroupHelper $groupHelper, UserSettings $userSettings, $user, $since, $limit, $sort, $filter, $objectType = '', $objectId = 0, bool $returnEvents = false) {
230230
// get current user
231231
if ($user === '') {
232232
throw new \OutOfBoundsException('Invalid user', 1);
@@ -323,39 +323,36 @@ public function get(GroupHelper $groupHelper, UserSettings $userSettings, string
323323
*
324324
* @throws \OutOfBoundsException If $since is not owned by $user
325325
*/
326-
protected function setOffsetFromSince(IQueryBuilder $query, string $user, int $since, string $sort): array {
327-
if (!$since) {
328-
return $this->getFirstKnownActivityHeader($user, $sort);
329-
}
330-
331-
$queryBuilder = $this->connection->getQueryBuilder();
332-
$queryBuilder->select(['affecteduser', 'timestamp'])
333-
->from('activity')
334-
->where($queryBuilder->expr()->eq('activity_id', $queryBuilder->createNamedParameter($since)));
335-
$result = $queryBuilder->executeQuery();
336-
$activity = $result->fetch();
337-
$result->closeCursor();
338-
339-
if (!$activity) {
340-
return $this->getFirstKnownActivityHeader($user, $sort);
341-
}
342-
343-
if ($activity['affecteduser'] !== $user) {
344-
throw new \OutOfBoundsException('Invalid since', 2);
345-
}
346-
347-
$timestamp = (int)$activity['timestamp'];
348-
if ($sort === 'DESC') {
349-
$query->andWhere($query->expr()->lte('timestamp', $query->createNamedParameter($timestamp)));
350-
$query->andWhere($query->expr()->lt('activity_id', $query->createNamedParameter($since)));
351-
} else {
352-
$query->andWhere($query->expr()->gte('timestamp', $query->createNamedParameter($timestamp)));
353-
$query->andWhere($query->expr()->gt('activity_id', $query->createNamedParameter($since)));
326+
protected function setOffsetFromSince(IQueryBuilder $query, $user, $since, $sort) {
327+
if ($since) {
328+
$queryBuilder = $this->connection->getQueryBuilder();
329+
$queryBuilder->select(['affecteduser', 'timestamp'])
330+
->from('activity')
331+
->where($queryBuilder->expr()->eq('activity_id', $queryBuilder->createNamedParameter((int)$since)));
332+
$result = $queryBuilder->executeQuery();
333+
$activity = $result->fetch();
334+
$result->closeCursor();
335+
336+
if ($activity) {
337+
if ($activity['affecteduser'] !== $user) {
338+
throw new \OutOfBoundsException('Invalid since', 2);
339+
}
340+
$timestamp = (int)$activity['timestamp'];
341+
342+
if ($sort === 'DESC') {
343+
$query->andWhere($query->expr()->lte('timestamp', $query->createNamedParameter($timestamp)));
344+
$query->andWhere($query->expr()->lt('activity_id', $query->createNamedParameter($since)));
345+
} else {
346+
$query->andWhere($query->expr()->gte('timestamp', $query->createNamedParameter($timestamp)));
347+
$query->andWhere($query->expr()->gt('activity_id', $query->createNamedParameter($since)));
348+
}
349+
return [];
350+
}
354351
}
355-
return [];
356-
}
357352

358-
private function getFirstKnownActivityHeader(string $user, string $sort): array {
353+
/**
354+
* Couldn't find the since, so find the oldest one and set the header
355+
*/
359356
$fetchQuery = $this->connection->getQueryBuilder();
360357
$fetchQuery->select('activity_id')
361358
->from('activity')
@@ -367,8 +364,11 @@ private function getFirstKnownActivityHeader(string $user, string $sort): array
367364
$result->closeCursor();
368365

369366
if ($activity !== false) {
370-
return ['X-Activity-First-Known' => (int)$activity['activity_id']];
367+
return [
368+
'X-Activity-First-Known' => (int)$activity['activity_id'],
369+
];
371370
}
371+
372372
return [];
373373
}
374374

lib/FilesHooks.php

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,10 @@ public function fileCreate($path) {
8282
return;
8383
}
8484

85-
if ($this->currentUser->getUserIdentifier() === '' && $this->currentUser->isPublicShareToken()) {
86-
$this->addNotificationsForFileAction($path, Files_Sharing::TYPE_PUBLIC_UPLOAD, '', 'created_public');
87-
} else {
85+
if ($this->currentUser->getUserIdentifier() !== '' || !$this->currentUser->isPublicShareToken()) {
8886
$this->addNotificationsForFileAction($path, Files::TYPE_SHARE_CREATED, 'created_self', 'created_by');
87+
} else {
88+
$this->addNotificationsForFileAction($path, Files_Sharing::TYPE_PUBLIC_UPLOAD, '', 'created_public');
8989
}
9090
}
9191

@@ -807,15 +807,14 @@ protected function shareWithTeam(string $shareWith, Node $fileSource, string $fi
807807
* @throws \OCP\Files\NotFoundException
808808
*/
809809
public function unShare(IShare $share) {
810-
if (!in_array($share->getNodeType(), ['file', 'folder'], true) || $this->isDeletedNode($share->getShareOwner(), $share->getNodeId())) {
811-
return;
812-
}
813-
if ($share->getShareType() === IShare::TYPE_USER) {
814-
$this->unshareFromUser($share);
815-
} elseif ($share->getShareType() === IShare::TYPE_GROUP) {
816-
$this->unshareFromGroup($share);
817-
} elseif ($share->getShareType() === IShare::TYPE_LINK) {
818-
$this->unshareLink($share);
810+
if (in_array($share->getNodeType(), ['file', 'folder'], true) && !$this->isDeletedNode($share->getShareOwner(), $share->getNodeId())) {
811+
if ($share->getShareType() === IShare::TYPE_USER) {
812+
$this->unshareFromUser($share);
813+
} elseif ($share->getShareType() === IShare::TYPE_GROUP) {
814+
$this->unshareFromGroup($share);
815+
} elseif ($share->getShareType() === IShare::TYPE_LINK) {
816+
$this->unshareLink($share);
817+
}
819818
}
820819
}
821820

@@ -826,13 +825,12 @@ public function unShare(IShare $share) {
826825
* @throws \OCP\Files\NotFoundException
827826
*/
828827
public function unShareSelf(IShare $share) {
829-
if (!in_array($share->getNodeType(), ['file', 'folder'], true)) {
830-
return;
831-
}
832-
if ($share->getShareType() === IShare::TYPE_GROUP) {
833-
$this->unshareFromSelfGroup($share);
834-
} elseif ($share->getShareType() === IShare::TYPE_USER) {
835-
$this->unshareFromUser($share);
828+
if (in_array($share->getNodeType(), ['file', 'folder'], true)) {
829+
if ($share->getShareType() === IShare::TYPE_GROUP) {
830+
$this->unshareFromSelfGroup($share);
831+
} elseif ($share->getShareType() === IShare::TYPE_USER) {
832+
$this->unshareFromUser($share);
833+
}
836834
}
837835
}
838836

0 commit comments

Comments
 (0)