Skip to content

Commit 260d025

Browse files
authored
Merge branch 'master' into excludedisabled
2 parents 869f7b2 + 6470514 commit 260d025

50 files changed

Lines changed: 2010 additions & 767 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.devcontainer/codespace.config.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,19 @@
77
$codespaceName = getenv('CODESPACE_NAME');
88
$codespaceDomain = getenv('GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN');
99

10+
// When running under Apache, env vars from the shell profile are not inherited.
11+
// Fall back to the Codespaces shared environment file and the well-known domain.
12+
if (empty($codespaceName)) {
13+
$sharedEnvFile = '/workspaces/.codespaces/shared/environment-variables.json';
14+
if (is_readable($sharedEnvFile)) {
15+
$sharedEnv = json_decode(file_get_contents($sharedEnvFile), true) ?? [];
16+
$codespaceName = $sharedEnv['CODESPACE_NAME'] ?? '';
17+
}
18+
}
19+
if (!empty($codespaceName) && empty($codespaceDomain)) {
20+
$codespaceDomain = 'app.github.dev';
21+
}
22+
1023
$CONFIG = [
1124
'mail_from_address' => 'no-reply',
1225
'mail_smtpmode' => 'smtp',

.github/workflows/cypress.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,10 +105,10 @@ jobs:
105105
matrix:
106106
# Run multiple copies of the current job in parallel
107107
# Please increase the number or runners as your tests suite grows (0 based index for e2e tests)
108-
containers: ['setup', '0', '1', '2', '3', '4', '5', '6', '7']
108+
containers: ['setup', '0', '1', '2', '3', '4', '5', '6']
109109
# Hack as strategy.job-total includes the "setup" and GitHub does not allow math expressions
110110
# Always align this number with the total of e2e runners (max. index + 1)
111-
total-containers: [8]
111+
total-containers: [7]
112112

113113
services:
114114
mysql:

.github/workflows/integration-sqlite.yml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,6 @@ name: Integration sqlite
44

55
on:
66
pull_request:
7-
push:
8-
branches:
9-
- main
10-
- master
11-
- stable*
127

138
permissions:
149
contents: read

AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,7 @@
630630
- zorn-v <zorn7@yandex.ru>
631631
- zulan <git@zulan.net>
632632
- Łukasz Buśko <busko.lukasz@pm.me>
633+
- Michał Roszak <m.roszakos@gmail.com>
633634
- Nextcloud GmbH
634635
- ownCloud GmbH
635636
- ownCloud, Inc.

apps/federation/lib/BackgroundJob/GetSharedSecret.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ protected function parentStart(IJobList $jobList): void {
7575

7676
#[\Override]
7777
protected function run($argument) {
78+
// The DI container caches this instance, so a prior invocation in the
79+
// same cron pass can leave $retainJob = true and cause this row to be
80+
// re-queued unconditionally after start() removes it.
81+
$this->retainJob = false;
7882
$target = $argument['url'];
7983
$created = isset($argument['created']) ? (int)$argument['created'] : $this->time->getTime();
8084
$currentTime = $this->time->getTime();

apps/federation/lib/BackgroundJob/RequestSharedSecret.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@ protected function parentStart(IJobList $jobList): void {
8787
*/
8888
#[\Override]
8989
protected function run($argument) {
90+
// The DI container caches this instance, so a prior invocation in the
91+
// same cron pass can leave $retainJob = true and cause this row to be
92+
// re-queued unconditionally after start() removes it.
93+
$this->retainJob = false;
9094
$target = $argument['url'];
9195
$created = isset($argument['created']) ? (int)$argument['created'] : $this->time->getTime();
9296
$currentTime = $this->time->getTime();

apps/files_reminders/lib/BackgroundJob/ScheduledNotifications.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@
1111

1212
use OCA\FilesReminders\Db\ReminderMapper;
1313
use OCA\FilesReminders\Service\ReminderService;
14-
use OCP\AppFramework\Db\DoesNotExistException;
1514
use OCP\AppFramework\Utility\ITimeFactory;
1615
use OCP\BackgroundJob\TimedJob;
1716
use Psr\Log\LoggerInterface;
17+
use Throwable;
1818

1919
class ScheduledNotifications extends TimedJob {
2020
public function __construct(
@@ -37,8 +37,11 @@ public function run($argument) {
3737
foreach ($reminders as $reminder) {
3838
try {
3939
$this->reminderService->send($reminder);
40-
} catch (DoesNotExistException $e) {
41-
$this->logger->debug('Could not send notification for reminder with id ' . $reminder->getId());
40+
} catch (Throwable $e) {
41+
// A single broken reminder (e.g. orphaned user record) must not
42+
// stall the rest of the queue, which is ordered by due_date ASC
43+
// and would otherwise re-hit the same row on every cron tick.
44+
$this->logger->error('Could not send notification for reminder with id ' . $reminder->getId(), ['exception' => $e]);
4245
}
4346
}
4447
}

apps/files_versions/lib/Listener/FileEventsListener.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,9 @@ public function write_hook(Node $node): void {
208208
}
209209

210210
$path = $this->getPathForNode($node);
211+
if ($path === null) {
212+
return;
213+
}
211214
$result = Storage::store($path);
212215

213216
// Store the result of the version creation so it can be used in post_write_hook.
@@ -312,6 +315,9 @@ public function remove_hook(Node $node): void {
312315
$node = $this->versionsDeleted[$path];
313316
$relativePath = $this->getPathForNode($node);
314317
unset($this->versionsDeleted[$path]);
318+
if ($relativePath === null) {
319+
return;
320+
}
315321
Storage::delete($relativePath);
316322
// If no new version was stored in the FS, no new version should be added in the DB.
317323
// So we simply update the associated version.
@@ -325,6 +331,9 @@ public function remove_hook(Node $node): void {
325331
*/
326332
public function pre_remove_hook(Node $node): void {
327333
$path = $this->getPathForNode($node);
334+
if ($path === null) {
335+
return;
336+
}
328337
Storage::markDeletedFile($path);
329338
$this->versionsDeleted[$node->getPath()] = $node;
330339
}
@@ -345,6 +354,9 @@ public function rename_hook(Node $source, Node $target): void {
345354

346355
$oldPath = $this->getPathForNode($source);
347356
$newPath = $this->getPathForNode($target);
357+
if ($oldPath === null || $newPath === null) {
358+
return;
359+
}
348360
Storage::renameOrCopy($oldPath, $newPath, 'rename');
349361
}
350362

@@ -364,6 +376,9 @@ public function copy_hook(Node $source, Node $target): void {
364376

365377
$oldPath = $this->getPathForNode($source);
366378
$newPath = $this->getPathForNode($target);
379+
if ($oldPath === null || $newPath === null) {
380+
return;
381+
}
367382
Storage::renameOrCopy($oldPath, $newPath, 'copy');
368383
}
369384

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
/**
5+
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
6+
* SPDX-License-Identifier: AGPL-3.0-or-later
7+
*/
8+
9+
namespace OCA\Files_Versions\Tests\Listener;
10+
11+
use OCA\Files_Versions\Listener\FileEventsListener;
12+
use OCA\Files_Versions\Versions\IVersionManager;
13+
use OCP\Files\File;
14+
use OCP\Files\IMimeTypeLoader;
15+
use OCP\Files\IRootFolder;
16+
use OCP\IUserSession;
17+
use PHPUnit\Framework\MockObject\MockObject;
18+
use Psr\Log\LoggerInterface;
19+
use Test\TestCase;
20+
21+
class FileEventsListenerTest extends TestCase {
22+
private IRootFolder&MockObject $rootFolder;
23+
private IVersionManager&MockObject $versionManager;
24+
private IMimeTypeLoader&MockObject $mimeTypeLoader;
25+
private IUserSession&MockObject $userSession;
26+
private LoggerInterface&MockObject $logger;
27+
private FileEventsListener $listener;
28+
29+
protected function setUp(): void {
30+
parent::setUp();
31+
32+
$this->rootFolder = $this->createMock(IRootFolder::class);
33+
$this->versionManager = $this->createMock(IVersionManager::class);
34+
$this->mimeTypeLoader = $this->createMock(IMimeTypeLoader::class);
35+
$this->userSession = $this->createMock(IUserSession::class);
36+
$this->logger = $this->createMock(LoggerInterface::class);
37+
38+
$this->listener = new FileEventsListener(
39+
$this->rootFolder,
40+
$this->versionManager,
41+
$this->mimeTypeLoader,
42+
$this->userSession,
43+
$this->logger,
44+
);
45+
}
46+
47+
private function createUnresolvableFile(): File&MockObject {
48+
$this->userSession->method('getUser')->willReturn(null);
49+
50+
$node = $this->createMock(File::class);
51+
$node->method('getOwner')->willReturn(null);
52+
$node->method('getPath')->willReturn('/test.txt');
53+
$node->method('getId')->willReturn(42);
54+
$node->method('getSize')->willReturn(100);
55+
$node->method('getMTime')->willReturn(1234567890);
56+
57+
return $node;
58+
}
59+
60+
private function getPrivateProperty(string $property): mixed {
61+
$ref = new \ReflectionProperty(FileEventsListener::class, $property);
62+
$ref->setAccessible(true);
63+
return $ref->getValue($this->listener);
64+
}
65+
66+
public function testGetPathForNodeReturnsNullWhenUnresolvable(): void {
67+
$node = $this->createUnresolvableFile();
68+
69+
$this->logger->expects($this->once())
70+
->method('debug')
71+
->with('Failed to compute path for node', $this->anything());
72+
73+
$method = new \ReflectionMethod(FileEventsListener::class, 'getPathForNode');
74+
$method->setAccessible(true);
75+
76+
$this->assertNull($method->invoke($this->listener, $node));
77+
}
78+
79+
public function testWriteHookSkipsWhenPathUnresolvable(): void {
80+
$node = $this->createUnresolvableFile();
81+
82+
$this->listener->write_hook($node);
83+
84+
$this->assertSame([], $this->getPrivateProperty('writeHookInfo'));
85+
}
86+
87+
public function testPreRemoveHookSkipsWhenPathUnresolvable(): void {
88+
$node = $this->createUnresolvableFile();
89+
90+
$this->listener->pre_remove_hook($node);
91+
92+
$this->assertSame([], $this->getPrivateProperty('versionsDeleted'));
93+
}
94+
}

apps/settings/appinfo/info.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
<admin-section>OCA\Settings\Sections\Admin\Additional</admin-section>
3939
<admin-section>OCA\Settings\Sections\Admin\Delegation</admin-section>
4040
<admin-section>OCA\Settings\Sections\Admin\Groupware</admin-section>
41+
<admin-section>OCA\Settings\Sections\Admin\Office</admin-section>
4142
<admin-section>OCA\Settings\Sections\Admin\Overview</admin-section>
4243
<admin-section>OCA\Settings\Sections\Admin\Presets</admin-section>
4344
<admin-section>OCA\Settings\Sections\Admin\ArtificialIntelligence</admin-section>

0 commit comments

Comments
 (0)