aboutsummaryrefslogtreecommitdiffstats
path: root/build
diff options
context:
space:
mode:
Diffstat (limited to 'build')
-rw-r--r--build/ca-bundle-etag.txt2
-rw-r--r--build/files-checker.php3
-rw-r--r--build/gen-coverage-badge.php1
-rw-r--r--build/integration/config/behat.yml24
-rw-r--r--build/integration/dav_features/carddav.feature2
-rw-r--r--build/integration/dav_features/dav-v2.feature1
-rw-r--r--build/integration/dav_features/principal-property-search.feature13
-rw-r--r--build/integration/dav_features/webdav-related.feature1
-rw-r--r--build/integration/features/bootstrap/AppConfiguration.php1
-rw-r--r--build/integration/features/bootstrap/Avatar.php13
-rw-r--r--build/integration/features/bootstrap/BasicStructure.php2
-rw-r--r--build/integration/features/bootstrap/CalDavContext.php1
-rw-r--r--build/integration/features/bootstrap/CapabilitiesContext.php1
-rw-r--r--build/integration/features/bootstrap/CardDavContext.php1
-rw-r--r--build/integration/features/bootstrap/ChecksumsContext.php1
-rw-r--r--build/integration/features/bootstrap/CommandLine.php1
-rw-r--r--build/integration/features/bootstrap/CommandLineContext.php14
-rw-r--r--build/integration/features/bootstrap/CommentsContext.php1
-rw-r--r--build/integration/features/bootstrap/ContactsMenu.php1
-rw-r--r--build/integration/features/bootstrap/ConversionsContext.php1
-rw-r--r--build/integration/features/bootstrap/DavFeatureContext.php1
-rw-r--r--build/integration/features/bootstrap/Download.php1
-rw-r--r--build/integration/features/bootstrap/ExternalStorage.php1
-rw-r--r--build/integration/features/bootstrap/FakeSMTPHelper.php1
-rw-r--r--build/integration/features/bootstrap/FeatureContext.php1
-rw-r--r--build/integration/features/bootstrap/FederationContext.php1
-rw-r--r--build/integration/features/bootstrap/FilesDropContext.php5
-rw-r--r--build/integration/features/bootstrap/LDAPContext.php1
-rw-r--r--build/integration/features/bootstrap/Mail.php1
-rw-r--r--build/integration/features/bootstrap/MetadataContext.php1
-rw-r--r--build/integration/features/bootstrap/PrincipalPropertySearchContext.php141
-rw-r--r--build/integration/features/bootstrap/Provisioning.php1
-rw-r--r--build/integration/features/bootstrap/RateLimitingContext.php1
-rw-r--r--build/integration/features/bootstrap/RemoteContext.php1
-rw-r--r--build/integration/features/bootstrap/RoutingContext.php19
-rw-r--r--build/integration/features/bootstrap/Search.php1
-rw-r--r--build/integration/features/bootstrap/SetupContext.php1
-rw-r--r--build/integration/features/bootstrap/ShareesContext.php1
-rw-r--r--build/integration/features/bootstrap/Sharing.php15
-rw-r--r--build/integration/features/bootstrap/SharingContext.php2
-rw-r--r--build/integration/features/bootstrap/TagsContext.php7
-rw-r--r--build/integration/features/bootstrap/TalkContext.php1
-rw-r--r--build/integration/features/bootstrap/Theming.php49
-rw-r--r--build/integration/features/bootstrap/Trashbin.php1
-rw-r--r--build/integration/features/bootstrap/WebDav.php1
-rw-r--r--build/integration/features/provisioning-v1.feature13
-rw-r--r--build/integration/files_features/transfer-ownership.feature51
-rw-r--r--build/integration/files_features/windows_compatibility.feature2
-rw-r--r--build/integration/filesdrop_features/filesdrop.feature88
-rw-r--r--build/integration/routing_features/apps-and-routes.feature52
-rwxr-xr-xbuild/integration/run.sh2
-rw-r--r--build/integration/sharing_features/sharing-v1-part2.feature34
-rw-r--r--build/integration/theming_features/theming.feature131
-rw-r--r--build/license.php7
-rw-r--r--build/psalm-baseline.xml223
-rw-r--r--build/psalm/OcpSinceChecker.php92
-rw-r--r--build/rector.php11
57 files changed, 745 insertions, 302 deletions
diff --git a/build/ca-bundle-etag.txt b/build/ca-bundle-etag.txt
index f09d49137b5..a3148994b07 100644
--- a/build/ca-bundle-etag.txt
+++ b/build/ca-bundle-etag.txt
@@ -1 +1 @@
-"38f2f-62eefa8c2e7b3"
+"366fb-635889cca07d9"
diff --git a/build/files-checker.php b/build/files-checker.php
index 4cbd0874247..4dfc7267e23 100644
--- a/build/files-checker.php
+++ b/build/files-checker.php
@@ -11,6 +11,7 @@ $expectedFiles = [
'..',
'.devcontainer',
'.editorconfig',
+ '.envrc',
'.eslintignore',
'.eslintrc.js',
'.git',
@@ -61,6 +62,8 @@ $expectedFiles = [
'cypress.d.ts',
'cypress',
'dist',
+ 'flake.lock',
+ 'flake.nix',
'index.html',
'index.php',
'lib',
diff --git a/build/gen-coverage-badge.php b/build/gen-coverage-badge.php
index 73d2a37e025..602ab83b281 100644
--- a/build/gen-coverage-badge.php
+++ b/build/gen-coverage-badge.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
diff --git a/build/integration/config/behat.yml b/build/integration/config/behat.yml
index 45db5105838..0a3fe4fd823 100644
--- a/build/integration/config/behat.yml
+++ b/build/integration/config/behat.yml
@@ -80,6 +80,8 @@ default:
- CommandLineContext:
baseUrl: http://localhost:8080
ocPath: ../../
+ - PrincipalPropertySearchContext:
+ baseUrl: http://localhost:8080
federation:
paths:
- "%paths.base%/../federation_features"
@@ -253,4 +255,24 @@ default:
admin:
- admin
- admin
- regular_user_password: 123456 \ No newline at end of file
+ regular_user_password: 123456
+ routing:
+ paths:
+ - "%paths.base%/../routing_features"
+ contexts:
+ - RoutingContext:
+ baseUrl: http://localhost:8080
+ admin:
+ - admin
+ - admin
+ regular_user_password: 123456
+ theming:
+ paths:
+ - "%paths.base%/../theming_features"
+ contexts:
+ - FeatureContext:
+ baseUrl: http://localhost:8080
+ admin:
+ - admin
+ - admin
+ regular_user_password: 123456
diff --git a/build/integration/dav_features/carddav.feature b/build/integration/dav_features/carddav.feature
index f1ea46af886..35e85639817 100644
--- a/build/integration/dav_features/carddav.feature
+++ b/build/integration/dav_features/carddav.feature
@@ -49,7 +49,6 @@ Feature: carddav
|X-Frame-Options|SAMEORIGIN|
|X-Permitted-Cross-Domain-Policies|none|
|X-Robots-Tag|noindex, nofollow|
- |X-XSS-Protection|1; mode=block|
Scenario: Exporting the picture of ones own contact
Given "admin" creates an addressbook named "MyAddressbook" with statuscode "201"
@@ -63,7 +62,6 @@ Feature: carddav
|X-Frame-Options|SAMEORIGIN|
|X-Permitted-Cross-Domain-Policies|none|
|X-Robots-Tag|noindex, nofollow|
- |X-XSS-Protection|1; mode=block|
Scenario: Create addressbook request for non-existing addressbook of another user
Given user "user0" exists
diff --git a/build/integration/dav_features/dav-v2.feature b/build/integration/dav_features/dav-v2.feature
index 9eae9a1b5fd..dbd2295497f 100644
--- a/build/integration/dav_features/dav-v2.feature
+++ b/build/integration/dav_features/dav-v2.feature
@@ -41,7 +41,6 @@ Feature: dav-v2
|X-Frame-Options|SAMEORIGIN|
|X-Permitted-Cross-Domain-Policies|none|
|X-Robots-Tag|noindex, nofollow|
- |X-XSS-Protection|1; mode=block|
And Downloaded content should start with "Welcome to your Nextcloud account!"
Scenario: Doing a GET with a web login should work without CSRF token on the new backend
diff --git a/build/integration/dav_features/principal-property-search.feature b/build/integration/dav_features/principal-property-search.feature
new file mode 100644
index 00000000000..b2195489263
--- /dev/null
+++ b/build/integration/dav_features/principal-property-search.feature
@@ -0,0 +1,13 @@
+# SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+# SPDX-License-Identifier: AGPL-3.0-or-later
+
+Feature: principal-property-search
+ Background:
+ Given user "user0" exists
+ Given As an "admin"
+ Given invoking occ with "app:enable --force testing"
+
+ Scenario: Find a principal by a given displayname
+ When searching for a principal matching "user0"
+ Then The search HTTP status code should be "207"
+ And The search response should contain "<d:href>/remote.php/dav/principals/users/user0/</d:href>"
diff --git a/build/integration/dav_features/webdav-related.feature b/build/integration/dav_features/webdav-related.feature
index f439330838c..12fd3d44c4f 100644
--- a/build/integration/dav_features/webdav-related.feature
+++ b/build/integration/dav_features/webdav-related.feature
@@ -291,7 +291,6 @@ Feature: webdav-related
|X-Frame-Options|SAMEORIGIN|
|X-Permitted-Cross-Domain-Policies|none|
|X-Robots-Tag|noindex, nofollow|
- |X-XSS-Protection|1; mode=block|
And Downloaded content should start with "Welcome to your Nextcloud account!"
Scenario: Doing a GET with a web login should work without CSRF token on the old backend
diff --git a/build/integration/features/bootstrap/AppConfiguration.php b/build/integration/features/bootstrap/AppConfiguration.php
index 5f39c58ffeb..e8580ed537b 100644
--- a/build/integration/features/bootstrap/AppConfiguration.php
+++ b/build/integration/features/bootstrap/AppConfiguration.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
diff --git a/build/integration/features/bootstrap/Avatar.php b/build/integration/features/bootstrap/Avatar.php
index ffc391c0d45..beebf1c024a 100644
--- a/build/integration/features/bootstrap/Avatar.php
+++ b/build/integration/features/bootstrap/Avatar.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
@@ -240,10 +241,10 @@ trait Avatar {
}
private function isSameColor(array $firstColor, array $secondColor, int $allowedDelta = 1) {
- if ($this->isSameColorComponent($firstColor['red'], $secondColor['red'], $allowedDelta) &&
- $this->isSameColorComponent($firstColor['green'], $secondColor['green'], $allowedDelta) &&
- $this->isSameColorComponent($firstColor['blue'], $secondColor['blue'], $allowedDelta) &&
- $this->isSameColorComponent($firstColor['alpha'], $secondColor['alpha'], $allowedDelta)) {
+ if ($this->isSameColorComponent($firstColor['red'], $secondColor['red'], $allowedDelta)
+ && $this->isSameColorComponent($firstColor['green'], $secondColor['green'], $allowedDelta)
+ && $this->isSameColorComponent($firstColor['blue'], $secondColor['blue'], $allowedDelta)
+ && $this->isSameColorComponent($firstColor['alpha'], $secondColor['alpha'], $allowedDelta)) {
return true;
}
@@ -251,8 +252,8 @@ trait Avatar {
}
private function isSameColorComponent(int $firstColorComponent, int $secondColorComponent, int $allowedDelta) {
- if ($firstColorComponent >= ($secondColorComponent - $allowedDelta) &&
- $firstColorComponent <= ($secondColorComponent + $allowedDelta)) {
+ if ($firstColorComponent >= ($secondColorComponent - $allowedDelta)
+ && $firstColorComponent <= ($secondColorComponent + $allowedDelta)) {
return true;
}
diff --git a/build/integration/features/bootstrap/BasicStructure.php b/build/integration/features/bootstrap/BasicStructure.php
index a8c232d6fe7..59a4312913e 100644
--- a/build/integration/features/bootstrap/BasicStructure.php
+++ b/build/integration/features/bootstrap/BasicStructure.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
@@ -19,6 +20,7 @@ trait BasicStructure {
use Avatar;
use Download;
use Mail;
+ use Theming;
/** @var string */
private $currentUser = '';
diff --git a/build/integration/features/bootstrap/CalDavContext.php b/build/integration/features/bootstrap/CalDavContext.php
index 80f8c53fc4e..459c35089fa 100644
--- a/build/integration/features/bootstrap/CalDavContext.php
+++ b/build/integration/features/bootstrap/CalDavContext.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
diff --git a/build/integration/features/bootstrap/CapabilitiesContext.php b/build/integration/features/bootstrap/CapabilitiesContext.php
index ae32056e17f..7d09ab6ddcf 100644
--- a/build/integration/features/bootstrap/CapabilitiesContext.php
+++ b/build/integration/features/bootstrap/CapabilitiesContext.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
diff --git a/build/integration/features/bootstrap/CardDavContext.php b/build/integration/features/bootstrap/CardDavContext.php
index a59f0d56f96..733c98dca02 100644
--- a/build/integration/features/bootstrap/CardDavContext.php
+++ b/build/integration/features/bootstrap/CardDavContext.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
diff --git a/build/integration/features/bootstrap/ChecksumsContext.php b/build/integration/features/bootstrap/ChecksumsContext.php
index 3392f8545d9..c8abf91127e 100644
--- a/build/integration/features/bootstrap/ChecksumsContext.php
+++ b/build/integration/features/bootstrap/ChecksumsContext.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
diff --git a/build/integration/features/bootstrap/CommandLine.php b/build/integration/features/bootstrap/CommandLine.php
index 84b3dfd447f..924d723daa6 100644
--- a/build/integration/features/bootstrap/CommandLine.php
+++ b/build/integration/features/bootstrap/CommandLine.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
diff --git a/build/integration/features/bootstrap/CommandLineContext.php b/build/integration/features/bootstrap/CommandLineContext.php
index 5ea8d12a970..e7764356270 100644
--- a/build/integration/features/bootstrap/CommandLineContext.php
+++ b/build/integration/features/bootstrap/CommandLineContext.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
@@ -109,19 +110,6 @@ class CommandLineContext implements \Behat\Behat\Context\Context {
}
/**
- * @When /^transferring ownership of path "([^"]+)" from "([^"]+)" to "([^"]+)" with received shares$/
- */
- public function transferringOwnershipPathWithIncomingShares($path, $user1, $user2) {
- $path = '--path=' . $path;
- if ($this->runOcc(['files:transfer-ownership', $path, $user1, $user2, '--transfer-incoming-shares=1']) === 0) {
- $this->lastTransferPath = $this->findLastTransferFolderForUser($user1, $user2);
- } else {
- // failure
- $this->lastTransferPath = null;
- }
- }
-
- /**
* @When /^using received transfer folder of "([^"]+)" as dav path$/
*/
public function usingTransferFolderAsDavPath($user) {
diff --git a/build/integration/features/bootstrap/CommentsContext.php b/build/integration/features/bootstrap/CommentsContext.php
index 17795a48fb4..53001b1c204 100644
--- a/build/integration/features/bootstrap/CommentsContext.php
+++ b/build/integration/features/bootstrap/CommentsContext.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
diff --git a/build/integration/features/bootstrap/ContactsMenu.php b/build/integration/features/bootstrap/ContactsMenu.php
index 4fc3c03c5e9..f6bf6b9422b 100644
--- a/build/integration/features/bootstrap/ContactsMenu.php
+++ b/build/integration/features/bootstrap/ContactsMenu.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/build/integration/features/bootstrap/ConversionsContext.php b/build/integration/features/bootstrap/ConversionsContext.php
index 099a2263630..ccd14c460f8 100644
--- a/build/integration/features/bootstrap/ConversionsContext.php
+++ b/build/integration/features/bootstrap/ConversionsContext.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
diff --git a/build/integration/features/bootstrap/DavFeatureContext.php b/build/integration/features/bootstrap/DavFeatureContext.php
index acca52ccafc..ec6085cff98 100644
--- a/build/integration/features/bootstrap/DavFeatureContext.php
+++ b/build/integration/features/bootstrap/DavFeatureContext.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/build/integration/features/bootstrap/Download.php b/build/integration/features/bootstrap/Download.php
index 2a66f7c3d89..549a033346e 100644
--- a/build/integration/features/bootstrap/Download.php
+++ b/build/integration/features/bootstrap/Download.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/build/integration/features/bootstrap/ExternalStorage.php b/build/integration/features/bootstrap/ExternalStorage.php
index b1e4c92810b..8fe2653a026 100644
--- a/build/integration/features/bootstrap/ExternalStorage.php
+++ b/build/integration/features/bootstrap/ExternalStorage.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/build/integration/features/bootstrap/FakeSMTPHelper.php b/build/integration/features/bootstrap/FakeSMTPHelper.php
index caf2139faab..32387869edd 100644
--- a/build/integration/features/bootstrap/FakeSMTPHelper.php
+++ b/build/integration/features/bootstrap/FakeSMTPHelper.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/build/integration/features/bootstrap/FeatureContext.php b/build/integration/features/bootstrap/FeatureContext.php
index c91c5e7cfa3..ab37556f931 100644
--- a/build/integration/features/bootstrap/FeatureContext.php
+++ b/build/integration/features/bootstrap/FeatureContext.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
diff --git a/build/integration/features/bootstrap/FederationContext.php b/build/integration/features/bootstrap/FederationContext.php
index 38fb6dd6b39..95dc8119ad6 100644
--- a/build/integration/features/bootstrap/FederationContext.php
+++ b/build/integration/features/bootstrap/FederationContext.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
diff --git a/build/integration/features/bootstrap/FilesDropContext.php b/build/integration/features/bootstrap/FilesDropContext.php
index 00c2cd346df..0c437f28a72 100644
--- a/build/integration/features/bootstrap/FilesDropContext.php
+++ b/build/integration/features/bootstrap/FilesDropContext.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
@@ -43,8 +44,8 @@ class FilesDropContext implements Context, SnippetAcceptingContext {
$this->response = $e->getResponse();
}
}
-
-
+
+
/**
* @When Dropping file :path with :content as :nickName
*/
diff --git a/build/integration/features/bootstrap/LDAPContext.php b/build/integration/features/bootstrap/LDAPContext.php
index f0181b36c71..986dced77a1 100644
--- a/build/integration/features/bootstrap/LDAPContext.php
+++ b/build/integration/features/bootstrap/LDAPContext.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/build/integration/features/bootstrap/Mail.php b/build/integration/features/bootstrap/Mail.php
index d28c24730ba..d48ed6399c5 100644
--- a/build/integration/features/bootstrap/Mail.php
+++ b/build/integration/features/bootstrap/Mail.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/build/integration/features/bootstrap/MetadataContext.php b/build/integration/features/bootstrap/MetadataContext.php
index 893c08a5467..32042590c86 100644
--- a/build/integration/features/bootstrap/MetadataContext.php
+++ b/build/integration/features/bootstrap/MetadataContext.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/build/integration/features/bootstrap/PrincipalPropertySearchContext.php b/build/integration/features/bootstrap/PrincipalPropertySearchContext.php
new file mode 100644
index 00000000000..9dfd9379240
--- /dev/null
+++ b/build/integration/features/bootstrap/PrincipalPropertySearchContext.php
@@ -0,0 +1,141 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+require __DIR__ . '/../../vendor/autoload.php';
+
+use Behat\Behat\Context\Context;
+use GuzzleHttp\BodySummarizer;
+use GuzzleHttp\Client;
+use GuzzleHttp\HandlerStack;
+use GuzzleHttp\Middleware;
+use GuzzleHttp\Utils;
+use Psr\Http\Message\ResponseInterface;
+
+class PrincipalPropertySearchContext implements Context {
+ private string $baseUrl;
+ private Client $client;
+ private ResponseInterface $response;
+
+ public function __construct(string $baseUrl) {
+ $this->baseUrl = $baseUrl;
+
+ // in case of ci deployment we take the server url from the environment
+ $testServerUrl = getenv('TEST_SERVER_URL');
+ if ($testServerUrl !== false) {
+ $this->baseUrl = substr($testServerUrl, 0, -5);
+ }
+ }
+
+ /** @BeforeScenario */
+ public function setUpScenario(): void {
+ $this->client = $this->createGuzzleInstance();
+ }
+
+ /**
+ * Create a Guzzle client with a higher truncateAt value to read full error responses.
+ */
+ private function createGuzzleInstance(): Client {
+ $bodySummarizer = new BodySummarizer(2048);
+
+ $stack = new HandlerStack(Utils::chooseHandler());
+ $stack->push(Middleware::httpErrors($bodySummarizer), 'http_errors');
+ $stack->push(Middleware::redirect(), 'allow_redirects');
+ $stack->push(Middleware::cookies(), 'cookies');
+ $stack->push(Middleware::prepareBody(), 'prepare_body');
+
+ return new Client(['handler' => $stack]);
+ }
+
+ /**
+ * @When searching for a principal matching :match
+ * @param string $match
+ * @throws \Exception
+ */
+ public function principalPropertySearch(string $match) {
+ $davUrl = $this->baseUrl . '/remote.php/dav/';
+ $user = 'admin';
+ $password = 'admin';
+
+ $this->response = $this->client->request(
+ 'REPORT',
+ $davUrl,
+ [
+ 'body' => '<x0:principal-property-search xmlns:x0="DAV:" test="anyof">
+ <x0:property-search>
+ <x0:prop>
+ <x0:displayname/>
+ <x2:email-address xmlns:x2="http://sabredav.org/ns"/>
+ </x0:prop>
+ <x0:match>' . $match . '</x0:match>
+ </x0:property-search>
+ <x0:prop>
+ <x0:displayname/>
+ <x1:calendar-user-type xmlns:x1="urn:ietf:params:xml:ns:caldav"/>
+ <x1:calendar-user-address-set xmlns:x1="urn:ietf:params:xml:ns:caldav"/>
+ <x0:principal-URL/>
+ <x0:alternate-URI-set/>
+ <x2:email-address xmlns:x2="http://sabredav.org/ns"/>
+ <x3:language xmlns:x3="http://nextcloud.com/ns"/>
+ <x1:calendar-home-set xmlns:x1="urn:ietf:params:xml:ns:caldav"/>
+ <x1:schedule-inbox-URL xmlns:x1="urn:ietf:params:xml:ns:caldav"/>
+ <x1:schedule-outbox-URL xmlns:x1="urn:ietf:params:xml:ns:caldav"/>
+ <x1:schedule-default-calendar-URL xmlns:x1="urn:ietf:params:xml:ns:caldav"/>
+ <x3:resource-type xmlns:x3="http://nextcloud.com/ns"/>
+ <x3:resource-vehicle-type xmlns:x3="http://nextcloud.com/ns"/>
+ <x3:resource-vehicle-make xmlns:x3="http://nextcloud.com/ns"/>
+ <x3:resource-vehicle-model xmlns:x3="http://nextcloud.com/ns"/>
+ <x3:resource-vehicle-is-electric xmlns:x3="http://nextcloud.com/ns"/>
+ <x3:resource-vehicle-range xmlns:x3="http://nextcloud.com/ns"/>
+ <x3:resource-vehicle-seating-capacity xmlns:x3="http://nextcloud.com/ns"/>
+ <x3:resource-contact-person xmlns:x3="http://nextcloud.com/ns"/>
+ <x3:resource-contact-person-vcard xmlns:x3="http://nextcloud.com/ns"/>
+ <x3:room-type xmlns:x3="http://nextcloud.com/ns"/>
+ <x3:room-seating-capacity xmlns:x3="http://nextcloud.com/ns"/>
+ <x3:room-building-address xmlns:x3="http://nextcloud.com/ns"/>
+ <x3:room-building-story xmlns:x3="http://nextcloud.com/ns"/>
+ <x3:room-building-room-number xmlns:x3="http://nextcloud.com/ns"/>
+ <x3:room-features xmlns:x3="http://nextcloud.com/ns"/>
+ </x0:prop>
+ <x0:apply-to-principal-collection-set/>
+</x0:principal-property-search>
+',
+ 'auth' => [
+ $user,
+ $password,
+ ],
+ 'headers' => [
+ 'Content-Type' => 'application/xml; charset=UTF-8',
+ 'Depth' => '0',
+ ],
+ ]
+ );
+ }
+
+ /**
+ * @Then The search HTTP status code should be :code
+ * @param string $code
+ * @throws \Exception
+ */
+ public function theHttpStatusCodeShouldBe(string $code): void {
+ if ((int)$code !== $this->response->getStatusCode()) {
+ throw new \Exception('Expected ' . (int)$code . ' got ' . $this->response->getStatusCode());
+ }
+ }
+
+ /**
+ * @Then The search response should contain :needle
+ * @param string $needle
+ * @throws \Exception
+ */
+ public function theResponseShouldContain(string $needle): void {
+ $body = $this->response->getBody()->getContents();
+
+ if (str_contains($body, $needle) === false) {
+ throw new \Exception('Response does not contain "' . $needle . '"');
+ }
+ }
+}
diff --git a/build/integration/features/bootstrap/Provisioning.php b/build/integration/features/bootstrap/Provisioning.php
index 446ffc0318e..935ad2a4a1d 100644
--- a/build/integration/features/bootstrap/Provisioning.php
+++ b/build/integration/features/bootstrap/Provisioning.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
diff --git a/build/integration/features/bootstrap/RateLimitingContext.php b/build/integration/features/bootstrap/RateLimitingContext.php
index ca198dc5514..15c8c5c8379 100644
--- a/build/integration/features/bootstrap/RateLimitingContext.php
+++ b/build/integration/features/bootstrap/RateLimitingContext.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/build/integration/features/bootstrap/RemoteContext.php b/build/integration/features/bootstrap/RemoteContext.php
index ae9da4b3614..6102f686ea7 100644
--- a/build/integration/features/bootstrap/RemoteContext.php
+++ b/build/integration/features/bootstrap/RemoteContext.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/build/integration/features/bootstrap/RoutingContext.php b/build/integration/features/bootstrap/RoutingContext.php
new file mode 100644
index 00000000000..762570547e0
--- /dev/null
+++ b/build/integration/features/bootstrap/RoutingContext.php
@@ -0,0 +1,19 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+use Behat\Behat\Context\Context;
+use Behat\Behat\Context\SnippetAcceptingContext;
+
+require __DIR__ . '/../../vendor/autoload.php';
+
+class RoutingContext implements Context, SnippetAcceptingContext {
+ use Provisioning;
+ use AppConfiguration;
+ use CommandLine;
+
+ protected function resetAppConfigs(): void {
+ }
+}
diff --git a/build/integration/features/bootstrap/Search.php b/build/integration/features/bootstrap/Search.php
index 47259be769c..49a4fe92822 100644
--- a/build/integration/features/bootstrap/Search.php
+++ b/build/integration/features/bootstrap/Search.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/build/integration/features/bootstrap/SetupContext.php b/build/integration/features/bootstrap/SetupContext.php
index 96cb00d8601..aa131cec597 100644
--- a/build/integration/features/bootstrap/SetupContext.php
+++ b/build/integration/features/bootstrap/SetupContext.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/build/integration/features/bootstrap/ShareesContext.php b/build/integration/features/bootstrap/ShareesContext.php
index e152a749bfa..37e0e63e547 100644
--- a/build/integration/features/bootstrap/ShareesContext.php
+++ b/build/integration/features/bootstrap/ShareesContext.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
diff --git a/build/integration/features/bootstrap/Sharing.php b/build/integration/features/bootstrap/Sharing.php
index 2ea7de3c6e0..039ee7d1121 100644
--- a/build/integration/features/bootstrap/Sharing.php
+++ b/build/integration/features/bootstrap/Sharing.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
@@ -561,18 +562,18 @@ trait Sharing {
];
$expectedFields = array_merge($defaultExpectedFields, $body->getRowsHash());
- if (!array_key_exists('uid_file_owner', $expectedFields) &&
- array_key_exists('uid_owner', $expectedFields)) {
+ if (!array_key_exists('uid_file_owner', $expectedFields)
+ && array_key_exists('uid_owner', $expectedFields)) {
$expectedFields['uid_file_owner'] = $expectedFields['uid_owner'];
}
- if (!array_key_exists('displayname_file_owner', $expectedFields) &&
- array_key_exists('displayname_owner', $expectedFields)) {
+ if (!array_key_exists('displayname_file_owner', $expectedFields)
+ && array_key_exists('displayname_owner', $expectedFields)) {
$expectedFields['displayname_file_owner'] = $expectedFields['displayname_owner'];
}
- if (array_key_exists('share_type', $expectedFields) &&
- $expectedFields['share_type'] == 10 /* IShare::TYPE_ROOM */ &&
- array_key_exists('share_with', $expectedFields)) {
+ if (array_key_exists('share_type', $expectedFields)
+ && $expectedFields['share_type'] == 10 /* IShare::TYPE_ROOM */
+ && array_key_exists('share_with', $expectedFields)) {
if ($expectedFields['share_with'] === 'private_conversation') {
$expectedFields['share_with'] = 'REGEXP /^private_conversation_[0-9a-f]{6}$/';
} else {
diff --git a/build/integration/features/bootstrap/SharingContext.php b/build/integration/features/bootstrap/SharingContext.php
index 8ef617adbfb..fe4d3bb6bf1 100644
--- a/build/integration/features/bootstrap/SharingContext.php
+++ b/build/integration/features/bootstrap/SharingContext.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
@@ -29,6 +30,7 @@ class SharingContext implements Context, SnippetAcceptingContext {
$this->deleteServerConfig('core', 'shareapi_expire_after_n_days');
$this->deleteServerConfig('core', 'link_defaultExpDays');
$this->deleteServerConfig('files_sharing', 'outgoing_server2server_share_enabled');
+ $this->deleteServerConfig('core', 'shareapi_allow_view_without_download');
$this->runOcc(['config:system:delete', 'share_folder']);
}
diff --git a/build/integration/features/bootstrap/TagsContext.php b/build/integration/features/bootstrap/TagsContext.php
index 262271e3710..c64626de68d 100644
--- a/build/integration/features/bootstrap/TagsContext.php
+++ b/build/integration/features/bootstrap/TagsContext.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
@@ -255,9 +256,9 @@ class TagsContext implements \Behat\Behat\Context\Context {
foreach ($table->getRowsHash() as $rowDisplayName => $row) {
foreach ($tags as $key => $tag) {
if (
- $tag['display-name'] === $rowDisplayName &&
- $tag['user-visible'] === $row[0] &&
- $tag['user-assignable'] === $row[1]
+ $tag['display-name'] === $rowDisplayName
+ && $tag['user-visible'] === $row[0]
+ && $tag['user-assignable'] === $row[1]
) {
unset($tags[$key]);
}
diff --git a/build/integration/features/bootstrap/TalkContext.php b/build/integration/features/bootstrap/TalkContext.php
index fe248e1af7c..6f351c30ccf 100644
--- a/build/integration/features/bootstrap/TalkContext.php
+++ b/build/integration/features/bootstrap/TalkContext.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
diff --git a/build/integration/features/bootstrap/Theming.php b/build/integration/features/bootstrap/Theming.php
new file mode 100644
index 00000000000..f44a6533a1b
--- /dev/null
+++ b/build/integration/features/bootstrap/Theming.php
@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+require __DIR__ . '/../../vendor/autoload.php';
+
+trait Theming {
+
+ private bool $undoAllThemingChangesAfterScenario = false;
+
+ /**
+ * @AfterScenario
+ */
+ public function undoAllThemingChanges() {
+ if (!$this->undoAllThemingChangesAfterScenario) {
+ return;
+ }
+
+ $this->loggingInUsingWebAs('admin');
+ $this->sendingAToWithRequesttoken('POST', '/index.php/apps/theming/ajax/undoAllChanges');
+
+ $this->undoAllThemingChangesAfterScenario = false;
+ }
+
+ /**
+ * @When logged in admin uploads theming image for :key from file :source
+ *
+ * @param string $key
+ * @param string $source
+ */
+ public function loggedInAdminUploadsThemingImageForFromFile(string $key, string $source) {
+ $this->undoAllThemingChangesAfterScenario = true;
+
+ $file = \GuzzleHttp\Psr7\Utils::streamFor(fopen($source, 'r'));
+
+ $this->sendingAToWithRequesttoken('POST', '/index.php/apps/theming/ajax/uploadImage?key=' . $key,
+ [
+ 'multipart' => [
+ [
+ 'name' => 'image',
+ 'contents' => $file
+ ]
+ ]
+ ]);
+ $this->theHTTPStatusCodeShouldBe('200');
+ }
+}
diff --git a/build/integration/features/bootstrap/Trashbin.php b/build/integration/features/bootstrap/Trashbin.php
index 6c8fd5e4fb6..dfcc23289a7 100644
--- a/build/integration/features/bootstrap/Trashbin.php
+++ b/build/integration/features/bootstrap/Trashbin.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2017 ownCloud GmbH
diff --git a/build/integration/features/bootstrap/WebDav.php b/build/integration/features/bootstrap/WebDav.php
index e9e08844767..2cb37002ac0 100644
--- a/build/integration/features/bootstrap/WebDav.php
+++ b/build/integration/features/bootstrap/WebDav.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
diff --git a/build/integration/features/provisioning-v1.feature b/build/integration/features/provisioning-v1.feature
index bffe7a5887f..6c35e5a68f1 100644
--- a/build/integration/features/provisioning-v1.feature
+++ b/build/integration/features/provisioning-v1.feature
@@ -187,6 +187,18 @@ Feature: provisioning
| timezoneOffset | 0 |
| pronouns | NULL |
+ Scenario: Edit a user with mixed case emails
+ Given As an "admin"
+ And user "brand-new-user" exists
+ And sending "PUT" to "/cloud/users/brand-new-user" with
+ | key | email |
+ | value | mixed-CASE@Nextcloud.com |
+ And the OCS status code should be "100"
+ And the HTTP status code should be "200"
+ Then user "brand-new-user" has
+ | id | brand-new-user |
+ | email | mixed-case@nextcloud.com |
+
Scenario: Edit a user account properties scopes
Given user "brand-new-user" exists
And As an "brand-new-user"
@@ -452,6 +464,7 @@ Feature: provisioning
Then groups returned are
| España |
| admin |
+ | hidden_group |
| new-group |
Scenario: create a subadmin
diff --git a/build/integration/files_features/transfer-ownership.feature b/build/integration/files_features/transfer-ownership.feature
index 4e5407cadbb..6f7a7944166 100644
--- a/build/integration/files_features/transfer-ownership.feature
+++ b/build/integration/files_features/transfer-ownership.feature
@@ -184,10 +184,10 @@ Feature: transfer-ownership
And As an "user2"
Then Downloaded content when downloading file "/test/somefile.txt" with range "bytes=0-6" should be "This is"
And using old dav path
- And as "user0" the folder "/test" exists
+ And as "user0" the folder "/test" does not exist
And using received transfer folder of "user1" as dav path
- And as "user1" the folder "/test" does not exist
- And As an "user0"
+ And as "user1" the folder "/test" exists
+ And As an "user1"
And Getting info of last share
And the OCS status code should be "100"
And Share fields of last share match with
@@ -210,13 +210,12 @@ Feature: transfer-ownership
And user "user1" accepts last share
When transferring ownership from "user0" to "user1"
And the command was successful
- And As an "user1"
- Then Downloaded content when downloading file "/test/somefile.txt" with range "bytes=0-6" should be "This is"
And using old dav path
- And as "user0" the folder "/test" exists
+ Then as "user0" the folder "/test" does not exist
+ When As an "user1"
And using received transfer folder of "user1" as dav path
- And as "user1" the folder "/test" does not exist
- And As an "user1"
+ Then as "user1" the folder "/test" exists
+ And Downloaded content when downloading file "/test/somefile.txt" with range "bytes=0-6" should be "This is"
And Getting info of last share
And the OCS status code should be "100"
And Share fields of last share match with
@@ -242,10 +241,10 @@ Feature: transfer-ownership
And As an "user2"
Then Downloaded content when downloading file "/test/somefile.txt" with range "bytes=0-6" should be "This is"
And using old dav path
- And as "user0" the folder "/test" exists
+ And as "user0" the folder "/test" does not exist
And using received transfer folder of "user1" as dav path
- And as "user1" the folder "/test" does not exist
- And As an "user0"
+ And as "user1" the folder "/test" exists
+ And As an "user1"
And Getting info of last share
And the OCS status code should be "100"
And Share fields of last share match with
@@ -253,7 +252,7 @@ Feature: transfer-ownership
| uid_file_owner | user3 |
| share_with | group1 |
- Scenario: transferring ownership does not transfer received shares
+ Scenario: transferring ownership transfers received shares
Given user "user0" exists
And user "user1" exists
And user "user2" exists
@@ -264,16 +263,16 @@ Feature: transfer-ownership
And the command was successful
And As an "user1"
And using received transfer folder of "user1" as dav path
- Then as "user1" the folder "/test" does not exist
+ Then as "user1" the folder "/test" exists
And using old dav path
- And as "user0" the folder "/test" exists
+ And as "user0" the folder "/test" does not exist
And As an "user2"
And Getting info of last share
And the OCS status code should be "100"
And Share fields of last share match with
| uid_owner | user2 |
| uid_file_owner | user2 |
- | share_with | user0 |
+ | share_with | user1 |
@local_storage
Scenario: transferring ownership does not transfer external storage
@@ -516,26 +515,6 @@ Feature: transfer-ownership
Then the command failed with exit code 1
And the command error output contains the text "Moving a storage (user0/files/test) into another storage (user1) is not allowed"
- Scenario: transferring ownership does not transfer received shares
- Given user "user0" exists
- And user "user1" exists
- And user "user2" exists
- And User "user2" created a folder "/test"
- And User "user0" created a folder "/sub"
- And folder "/test" of user "user2" is shared with user "user0" with permissions 31
- And user "user0" accepts last share
- And User "user0" moved folder "/test" to "/sub/test"
- When transferring ownership of path "sub" from "user0" to "user1"
- And the command was successful
- And As an "user1"
- And using received transfer folder of "user1" as dav path
- Then as "user1" the folder "/sub" exists
- And as "user1" the folder "/sub/test" does not exist
- And using old dav path
- And as "user0" the folder "/sub" does not exist
- And Getting info of last share
- And the OCS status code should be "404"
-
Scenario: transferring ownership transfers received shares into subdir when requested
Given user "user0" exists
And user "user1" exists
@@ -548,7 +527,7 @@ Feature: transfer-ownership
And User "user0" moved folder "/transfer-share" to "/sub/transfer-share"
And folder "/do-not-transfer" of user "user2" is shared with user "user0" with permissions 31
And user "user0" accepts last share
- When transferring ownership of path "sub" from "user0" to "user1" with received shares
+ When transferring ownership of path "sub" from "user0" to "user1"
And the command was successful
And As an "user1"
And using received transfer folder of "user1" as dav path
diff --git a/build/integration/files_features/windows_compatibility.feature b/build/integration/files_features/windows_compatibility.feature
index 2fdd37d67a1..feaaca1ed3a 100644
--- a/build/integration/files_features/windows_compatibility.feature
+++ b/build/integration/files_features/windows_compatibility.feature
@@ -54,7 +54,7 @@ Feature: Windows compatible filenames
And invoking occ with "files:windows-compatible-filenames --enable"
And invoking occ with "files:sanitize-filenames user0"
Then as "user0" the file "/2*2=4.txt" does not exist
- And as "user0" the file "/2 2=4.txt" exists
+ And as "user0" the file "/2_2=4.txt" exists
Scenario: renaming a file with invalid character and replacement setup
Given As an "admin"
diff --git a/build/integration/filesdrop_features/filesdrop.feature b/build/integration/filesdrop_features/filesdrop.feature
index eacd19a4b13..7618a31a1d0 100644
--- a/build/integration/filesdrop_features/filesdrop.feature
+++ b/build/integration/filesdrop_features/filesdrop.feature
@@ -44,7 +44,7 @@ Feature: FilesDrop
And Updating last share with
| permissions | 4 |
When Dropping file "/folder/a.txt" with "abc"
- Then the HTTP status code should be "405"
+ Then the HTTP status code should be "400"
Scenario: Files drop forbid MKCOL without a nickname
Given user "user0" exists
@@ -57,7 +57,7 @@ Feature: FilesDrop
And Updating last share with
| permissions | 4 |
When Creating folder "folder" in drop
- Then the HTTP status code should be "405"
+ Then the HTTP status code should be "400"
Scenario: Files drop allows MKCOL with a nickname
Given user "user0" exists
@@ -83,7 +83,7 @@ Feature: FilesDrop
And Updating last share with
| permissions | 4 |
When dropping file "/folder/a.txt" with "abc"
- Then the HTTP status code should be "405"
+ Then the HTTP status code should be "400"
Scenario: Files request drop
Given user "user0" exists
@@ -99,6 +99,47 @@ Feature: FilesDrop
And Downloading file "/drop/Alice/folder/a.txt"
Then Downloaded content should be "abc"
+ Scenario: File drop uploading folder with name of file
+ Given user "user0" exists
+ And As an "user0"
+ And user "user0" created a folder "/drop"
+ And as "user0" creating a share with
+ | path | drop |
+ | shareType | 4 |
+ | permissions | 4 |
+ | attributes | [{"scope":"fileRequest","key":"enabled","value":true}] |
+ | shareWith | |
+ When Dropping file "/folder" with "its a file" as "Alice"
+ Then the HTTP status code should be "201"
+ When Dropping file "/folder/a.txt" with "abc" as "Alice"
+ Then the HTTP status code should be "201"
+ When Downloading file "/drop/Alice/folder"
+ Then the HTTP status code should be "200"
+ And Downloaded content should be "its a file"
+ When Downloading file "/drop/Alice/folder (2)/a.txt"
+ Then Downloaded content should be "abc"
+
+ Scenario: File drop uploading file with name of folder
+ Given user "user0" exists
+ And As an "user0"
+ And user "user0" created a folder "/drop"
+ And as "user0" creating a share with
+ | path | drop |
+ | shareType | 4 |
+ | permissions | 4 |
+ | attributes | [{"scope":"fileRequest","key":"enabled","value":true}] |
+ | shareWith | |
+ When Dropping file "/folder/a.txt" with "abc" as "Alice"
+ Then the HTTP status code should be "201"
+ When Dropping file "/folder" with "its a file" as "Alice"
+ Then the HTTP status code should be "201"
+ When Downloading file "/drop/Alice/folder/a.txt"
+ Then the HTTP status code should be "200"
+ And Downloaded content should be "abc"
+ When Downloading file "/drop/Alice/folder (2)"
+ Then the HTTP status code should be "200"
+ And Downloaded content should be "its a file"
+
Scenario: Put file same file multiple times via files drop
Given user "user0" exists
And As an "user0"
@@ -154,4 +195,43 @@ Feature: FilesDrop
| attributes | [{"scope":"fileRequest","key":"enabled","value":true}] |
| shareWith | |
When Dropping file "/folder/a.txt" with "abc"
- Then the HTTP status code should be "405"
+ Then the HTTP status code should be "400"
+
+ Scenario: Files request drop with invalid nickname with slashes
+ Given user "user0" exists
+ And As an "user0"
+ And user "user0" created a folder "/drop"
+ And as "user0" creating a share with
+ | path | drop |
+ | shareType | 4 |
+ | permissions | 4 |
+ | attributes | [{"scope":"fileRequest","key":"enabled","value":true}] |
+ | shareWith | |
+ When Dropping file "/folder/a.txt" with "abc" as "Alice/Bob/Mallory"
+ Then the HTTP status code should be "400"
+
+ Scenario: Files request drop with invalid nickname with forbidden characters
+ Given user "user0" exists
+ And As an "user0"
+ And user "user0" created a folder "/drop"
+ And as "user0" creating a share with
+ | path | drop |
+ | shareType | 4 |
+ | permissions | 4 |
+ | attributes | [{"scope":"fileRequest","key":"enabled","value":true}] |
+ | shareWith | |
+ When Dropping file "/folder/a.txt" with "abc" as ".htaccess"
+ Then the HTTP status code should be "400"
+
+ Scenario: Files request drop with invalid nickname with forbidden characters
+ Given user "user0" exists
+ And As an "user0"
+ And user "user0" created a folder "/drop"
+ And as "user0" creating a share with
+ | path | drop |
+ | shareType | 4 |
+ | permissions | 4 |
+ | attributes | [{"scope":"fileRequest","key":"enabled","value":true}] |
+ | shareWith | |
+ When Dropping file "/folder/a.txt" with "abc" as ".Mallory"
+ Then the HTTP status code should be "400"
diff --git a/build/integration/routing_features/apps-and-routes.feature b/build/integration/routing_features/apps-and-routes.feature
new file mode 100644
index 00000000000..954ea73bfac
--- /dev/null
+++ b/build/integration/routing_features/apps-and-routes.feature
@@ -0,0 +1,52 @@
+# SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+# SPDX-License-Identifier: AGPL-3.0-or-later
+Feature: appmanagement
+ Background:
+ Given using api version "2"
+ And user "user1" exists
+ And user "user2" exists
+ And group "group1" exists
+ And user "user1" belongs to group "group1"
+
+ Scenario: Enable app and test route
+ Given As an "admin"
+ And sending "DELETE" to "/cloud/apps/weather_status"
+ And app "weather_status" is disabled
+ When sending "GET" to "/apps/weather_status/api/v1/location"
+ Then the OCS status code should be "998"
+ And the HTTP status code should be "404"
+ When sending "POST" to "/cloud/apps/weather_status"
+ Then the OCS status code should be "200"
+ And the HTTP status code should be "200"
+ And app "weather_status" is enabled
+ When sending "GET" to "/apps/weather_status/api/v1/location"
+ Then the OCS status code should be "200"
+ And the HTTP status code should be "200"
+ Given As an "user1"
+ When sending "GET" to "/apps/weather_status/api/v1/location"
+ Then the OCS status code should be "200"
+ And the HTTP status code should be "200"
+ Given As an "user2"
+ When sending "GET" to "/apps/weather_status/api/v1/location"
+ Then the OCS status code should be "200"
+ And the HTTP status code should be "200"
+
+ Scenario: Enable app only for some groups
+ Given As an "admin"
+ And sending "DELETE" to "/cloud/apps/weather_status"
+ And app "weather_status" is disabled
+ When sending "GET" to "/apps/weather_status/api/v1/location"
+ Then the OCS status code should be "998"
+ And the HTTP status code should be "404"
+ Given invoking occ with "app:enable weather_status --groups group1"
+ Then the command was successful
+ Given As an "user2"
+ When sending "GET" to "/apps/weather_status/api/v1/location"
+ Then the HTTP status code should be "412"
+ Given As an "user1"
+ When sending "GET" to "/apps/weather_status/api/v1/location"
+ Then the OCS status code should be "200"
+ And the HTTP status code should be "200"
+ Given As an "admin"
+ And sending "DELETE" to "/cloud/apps/weather_status"
+ And app "weather_status" is disabled
diff --git a/build/integration/run.sh b/build/integration/run.sh
index cbd3cceb3d1..30dd0646b10 100755
--- a/build/integration/run.sh
+++ b/build/integration/run.sh
@@ -18,6 +18,8 @@ HIDE_OC_LOGS=$2
INSTALLED=$($OCC status | grep installed: | cut -d " " -f 5)
if [ "$INSTALLED" == "true" ]; then
+ # Disable appstore to avoid spamming from CI
+ $OCC config:system:set appstoreenabled --value=false --type=boolean
# Disable bruteforce protection because the integration tests do trigger them
$OCC config:system:set auth.bruteforce.protection.enabled --value false --type bool
# Disable rate limit protection because the integration tests do trigger them
diff --git a/build/integration/sharing_features/sharing-v1-part2.feature b/build/integration/sharing_features/sharing-v1-part2.feature
index 46bcdf486d2..a6e4c67165a 100644
--- a/build/integration/sharing_features/sharing-v1-part2.feature
+++ b/build/integration/sharing_features/sharing-v1-part2.feature
@@ -1265,7 +1265,9 @@ Feature: sharing
|{http://open-collaboration-services.org/ns}share-permissions |
Then the single response should contain a property "{http://open-collaboration-services.org/ns}share-permissions" with value "19"
- Scenario: Cannot download a file when it's shared view-only
+ Scenario: Cannot download a file when it's shared view-only without shareapi_allow_view_without_download
+ Given As an "admin"
+ And parameter "shareapi_allow_view_without_download" of app "core" is set to "no"
Given user "user0" exists
And user "user1" exists
And User "user0" moves file "/textfile0.txt" to "/document.odt"
@@ -1274,8 +1276,15 @@ Feature: sharing
When As an "user1"
And Downloading file "/document.odt"
Then the HTTP status code should be "403"
+ Then As an "admin"
+ And parameter "shareapi_allow_view_without_download" of app "core" is set to "yes"
+ Then As an "user1"
+ And Downloading file "/document.odt"
+ Then the HTTP status code should be "200"
- Scenario: Cannot download a file when its parent is shared view-only
+ Scenario: Cannot download a file when its parent is shared view-only without shareapi_allow_view_without_download
+ Given As an "admin"
+ And parameter "shareapi_allow_view_without_download" of app "core" is set to "no"
Given user "user0" exists
And user "user1" exists
And User "user0" created a folder "/sharedviewonly"
@@ -1285,8 +1294,15 @@ Feature: sharing
When As an "user1"
And Downloading file "/sharedviewonly/document.odt"
Then the HTTP status code should be "403"
+ Then As an "admin"
+ And parameter "shareapi_allow_view_without_download" of app "core" is set to "yes"
+ Then As an "user1"
+ And Downloading file "/sharedviewonly/document.odt"
+ Then the HTTP status code should be "200"
- Scenario: Cannot copy a file when it's shared view-only
+ Scenario: Cannot copy a file when it's shared view-only even with shareapi_allow_view_without_download enabled
+ Given As an "admin"
+ And parameter "shareapi_allow_view_without_download" of app "core" is set to "no"
Given user "user0" exists
And user "user1" exists
And User "user0" moves file "/textfile0.txt" to "/document.odt"
@@ -1294,8 +1310,15 @@ Feature: sharing
And user "user1" accepts last share
When User "user1" copies file "/document.odt" to "/copyforbidden.odt"
Then the HTTP status code should be "403"
+ Then As an "admin"
+ And parameter "shareapi_allow_view_without_download" of app "core" is set to "yes"
+ Then As an "user1"
+ And User "user1" copies file "/document.odt" to "/copyforbidden.odt"
+ Then the HTTP status code should be "403"
Scenario: Cannot copy a file when its parent is shared view-only
+ Given As an "admin"
+ And parameter "shareapi_allow_view_without_download" of app "core" is set to "no"
Given user "user0" exists
And user "user1" exists
And User "user0" created a folder "/sharedviewonly"
@@ -1304,5 +1327,10 @@ Feature: sharing
And user "user1" accepts last share
When User "user1" copies file "/sharedviewonly/document.odt" to "/copyforbidden.odt"
Then the HTTP status code should be "403"
+ Then As an "admin"
+ And parameter "shareapi_allow_view_without_download" of app "core" is set to "yes"
+ Then As an "user1"
+ And User "user1" copies file "/sharedviewonly/document.odt" to "/copyforbidden.odt"
+ Then the HTTP status code should be "403"
# See sharing-v1-part3.feature
diff --git a/build/integration/theming_features/theming.feature b/build/integration/theming_features/theming.feature
new file mode 100644
index 00000000000..2ae5d4f75c3
--- /dev/null
+++ b/build/integration/theming_features/theming.feature
@@ -0,0 +1,131 @@
+# SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
+# SPDX-License-Identifier: AGPL-3.0-or-later
+Feature: theming
+
+ Background:
+ Given user "user0" exists
+
+ Scenario: themed stylesheets are available for users
+ Given As an "user0"
+ When sending "GET" with exact url to "/index.php/apps/theming/theme/default.css"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/theme/light.css"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/theme/dark.css"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/theme/light-highcontrast.css"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/theme/dark-highcontrast.css"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/theme/opendyslexic.css"
+ Then the HTTP status code should be "200"
+
+ Scenario: themed stylesheets are available for guests
+ Given As an "anonymous"
+ When sending "GET" with exact url to "/index.php/apps/theming/theme/default.css"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/theme/light.css"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/theme/dark.css"
+ Then the HTTP status code should be "200"
+ # Themes that can not be explicitly set by a guest could have been
+ # globally set too through "enforce_theme".
+ When sending "GET" with exact url to "/index.php/apps/theming/theme/light-highcontrast.css"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/theme/dark-highcontrast.css"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/theme/opendyslexic.css"
+ Then the HTTP status code should be "200"
+
+ Scenario: themed stylesheets are available for disabled users
+ Given As an "admin"
+ And assure user "user0" is disabled
+ And As an "user0"
+ When sending "GET" with exact url to "/index.php/apps/theming/theme/default.css"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/theme/light.css"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/theme/dark.css"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/theme/light-highcontrast.css"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/theme/dark-highcontrast.css"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/theme/opendyslexic.css"
+ Then the HTTP status code should be "200"
+
+ Scenario: themed images are available for users
+ Given Logging in using web as "admin"
+ And logged in admin uploads theming image for "background" from file "data/clouds.jpg"
+ And logged in admin uploads theming image for "logo" from file "data/coloured-pattern-non-square.png"
+ And logged in admin uploads theming image for "logoheader" from file "data/coloured-pattern-non-square.png"
+ And As an "user0"
+ When sending "GET" with exact url to "/index.php/apps/theming/image/background"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/image/logo"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/image/logoheader"
+ Then the HTTP status code should be "200"
+
+ Scenario: themed images are available for guests
+ Given Logging in using web as "admin"
+ And logged in admin uploads theming image for "background" from file "data/clouds.jpg"
+ And logged in admin uploads theming image for "logo" from file "data/coloured-pattern-non-square.png"
+ And logged in admin uploads theming image for "logoheader" from file "data/coloured-pattern-non-square.png"
+ And As an "anonymous"
+ When sending "GET" with exact url to "/index.php/apps/theming/image/background"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/image/logo"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/image/logoheader"
+ Then the HTTP status code should be "200"
+
+ Scenario: themed images are available for disabled users
+ Given Logging in using web as "admin"
+ And logged in admin uploads theming image for "background" from file "data/clouds.jpg"
+ And logged in admin uploads theming image for "logo" from file "data/coloured-pattern-non-square.png"
+ And logged in admin uploads theming image for "logoheader" from file "data/coloured-pattern-non-square.png"
+ And As an "admin"
+ And assure user "user0" is disabled
+ And As an "user0"
+ When sending "GET" with exact url to "/index.php/apps/theming/image/background"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/image/logo"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/image/logoheader"
+ Then the HTTP status code should be "200"
+
+ Scenario: themed icons are available for users
+ Given As an "user0"
+ When sending "GET" with exact url to "/index.php/apps/theming/favicon"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/icon"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/favicon/dashboard"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/icon/dashboard"
+ Then the HTTP status code should be "200"
+
+ Scenario: themed icons are available for guests
+ Given As an "anonymous"
+ When sending "GET" with exact url to "/index.php/apps/theming/favicon"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/icon"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/favicon/dashboard"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/icon/dashboard"
+ Then the HTTP status code should be "200"
+
+ Scenario: themed icons are available for disabled users
+ Given As an "admin"
+ And assure user "user0" is disabled
+ And As an "user0"
+ When sending "GET" with exact url to "/index.php/apps/theming/favicon"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/icon"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/favicon/dashboard"
+ Then the HTTP status code should be "200"
+ When sending "GET" with exact url to "/index.php/apps/theming/icon/dashboard"
+ Then the HTTP status code should be "200"
diff --git a/build/license.php b/build/license.php
index b21f5b263fc..bb6351181c6 100644
--- a/build/license.php
+++ b/build/license.php
@@ -1,4 +1,5 @@
<?php
+
/**
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
@@ -220,7 +221,7 @@ With help from many libraries and frameworks including:
$index++;
continue;
}
-
+
if (strpos($line, '<?php declare(strict_types') !== false) {
$isStrict = true;
array_splice($lines, $index, 1);
@@ -262,7 +263,7 @@ With help from many libraries and frameworks including:
private function getCopyrightNotices($path, $file) {
$licenseHeaderCopyrightAtLines = trim(shell_exec("grep -ni 'copyright' $path | cut -d ':' -f 1"));
$lineByLine = explode(PHP_EOL, $file);
-
+
$copyrightNotice = [];
if (trim($licenseHeaderCopyrightAtLines !== '')) {
$copyrightNotice = array_map(function ($line) use ($lineByLine) {
@@ -355,7 +356,7 @@ With help from many libraries and frameworks including:
}
$authors = $this->filterAuthors($authors);
-
+
if ($gitRoot) {
$authors = array_map([$this, 'checkCoreMailMap'], $authors);
$authors = array_unique($authors);
diff --git a/build/psalm-baseline.xml b/build/psalm-baseline.xml
index 8d409314d4d..7172c6f33f4 100644
--- a/build/psalm-baseline.xml
+++ b/build/psalm-baseline.xml
@@ -209,7 +209,6 @@
<code><![CDATA[IAppContainer]]></code>
</DeprecatedInterface>
<DeprecatedMethod>
- <code><![CDATA[\OC_App::loadApps(['dav'])]]></code>
<code><![CDATA[getServer]]></code>
<code><![CDATA[getServer]]></code>
<code><![CDATA[getURLGenerator]]></code>
@@ -675,6 +674,12 @@
</ParamNameMismatch>
</file>
<file src="apps/dav/lib/Connector/Sabre/File.php">
+ <DeprecatedClass>
+ <code><![CDATA[Files::streamCopy($data, $target, true)]]></code>
+ </DeprecatedClass>
+ <DeprecatedMethod>
+ <code><![CDATA[Files::streamCopy($data, $target, true)]]></code>
+ </DeprecatedMethod>
<LessSpecificReturnStatement>
<code><![CDATA[$this->node]]></code>
</LessSpecificReturnStatement>
@@ -776,12 +781,6 @@
<code><![CDATA[setAppValue]]></code>
</DeprecatedMethod>
</file>
- <file src="apps/dav/lib/Controller/ExampleContentController.php">
- <DeprecatedMethod>
- <code><![CDATA[getAppValue]]></code>
- <code><![CDATA[setAppValue]]></code>
- </DeprecatedMethod>
- </file>
<file src="apps/dav/lib/Controller/InvitationResponseController.php">
<UndefinedPropertyAssignment>
<code><![CDATA[$vEvent->DTSTAMP]]></code>
@@ -1015,11 +1014,6 @@
<code><![CDATA[getAppValue]]></code>
</DeprecatedMethod>
</file>
- <file src="apps/dav/lib/Settings/ExampleContentSettings.php">
- <DeprecatedMethod>
- <code><![CDATA[getAppValue]]></code>
- </DeprecatedMethod>
- </file>
<file src="apps/dav/lib/SetupChecks/NeedsSystemAddressBookSync.php">
<DeprecatedMethod>
<code><![CDATA[getAppValue]]></code>
@@ -1344,7 +1338,6 @@
</DeprecatedInterface>
<DeprecatedMethod>
<code><![CDATA[Util::connectHook('\OCP\Config', 'js', '\OCA\Files\App', 'extendJsConfig')]]></code>
- <code><![CDATA[\OC_Helper::getFileTemplateManager()]]></code>
<code><![CDATA[getUserFolder]]></code>
<code><![CDATA[getUserFolder]]></code>
</DeprecatedMethod>
@@ -1396,7 +1389,6 @@
</file>
<file src="apps/files/lib/Helper.php">
<UndefinedInterfaceMethod>
- <code><![CDATA[$file]]></code>
<code><![CDATA[$i]]></code>
<code><![CDATA[$i]]></code>
<code><![CDATA[$i]]></code>
@@ -1469,11 +1461,6 @@
<code><![CDATA[class-string<IStorage>]]></code>
</MoreSpecificReturnType>
</file>
- <file src="apps/files_external/lib/Lib/Backend/SMB.php">
- <DeprecatedClass>
- <code><![CDATA[new KerberosApacheAuth()]]></code>
- </DeprecatedClass>
- </file>
<file src="apps/files_external/lib/Lib/Storage/SFTP.php">
<InternalMethod>
<code><![CDATA[put]]></code>
@@ -1588,9 +1575,6 @@
<code><![CDATA[new QueryException()]]></code>
<code><![CDATA[new QueryException()]]></code>
</DeprecatedClass>
- <DeprecatedInterface>
- <code><![CDATA[private]]></code>
- </DeprecatedInterface>
</file>
<file src="apps/files_sharing/lib/Controller/ShareAPIController.php">
<DeprecatedClass>
@@ -1965,14 +1949,13 @@
</file>
<file src="apps/files_versions/lib/Storage.php">
<DeprecatedClass>
+ <code><![CDATA[Files::streamCopy($source, $target, true)]]></code>
<code><![CDATA[\OC_Util::setupFS($uid)]]></code>
</DeprecatedClass>
<DeprecatedMethod>
+ <code><![CDATA[Files::streamCopy($source, $target, true)]]></code>
<code><![CDATA[dispatch]]></code>
</DeprecatedMethod>
- <RedundantCondition>
- <code><![CDATA[$storage1->instanceOfStorage('\OC\Files\ObjectStore\ObjectStoreStorage')]]></code>
- </RedundantCondition>
</file>
<file src="apps/oauth2/lib/Controller/OauthApiController.php">
<NoInterfaceProperties>
@@ -2003,8 +1986,6 @@
<code><![CDATA[implementsActions]]></code>
<code><![CDATA[implementsActions]]></code>
<code><![CDATA[implementsActions]]></code>
- <code><![CDATA[setEMailAddress]]></code>
- <code><![CDATA[setEMailAddress]]></code>
</DeprecatedMethod>
<TypeDoesNotContainNull>
<code><![CDATA[$groupid === null]]></code>
@@ -2594,14 +2575,6 @@
<code><![CDATA[$gid]]></code>
</ParamNameMismatch>
</file>
- <file src="apps/user_ldap/lib/Helper.php">
- <DeprecatedMethod>
- <code><![CDATA[execute]]></code>
- <code><![CDATA[getAppKeys]]></code>
- <code><![CDATA[getAppValue]]></code>
- <code><![CDATA[getAppValue]]></code>
- </DeprecatedMethod>
- </file>
<file src="apps/user_ldap/lib/Jobs/CleanUp.php">
<DeprecatedMethod>
<code><![CDATA[getAppValue]]></code>
@@ -2870,12 +2843,6 @@
<code><![CDATA[dispatch]]></code>
</DeprecatedMethod>
</file>
- <file src="core/Application.php">
- <DeprecatedMethod>
- <code><![CDATA[getServer]]></code>
- <code><![CDATA[registerService]]></code>
- </DeprecatedMethod>
- </file>
<file src="core/BackgroundJobs/BackgroundCleanupUpdaterBackupsJob.php">
<DeprecatedClass>
<code><![CDATA[Files::rmdirr($dir)]]></code>
@@ -2950,8 +2917,6 @@
</file>
<file src="core/Command/Db/ConvertType.php">
<DeprecatedMethod>
- <code><![CDATA[\OC_App::getAllApps()]]></code>
- <code><![CDATA[\OC_App::loadApp($app)]]></code>
<code><![CDATA[execute]]></code>
<code><![CDATA[getName]]></code>
<code><![CDATA[getPrimaryKeyColumns]]></code>
@@ -3363,18 +3328,6 @@
<code><![CDATA[version_compare($first, $second, $operator)]]></code>
</NullableReturnStatement>
</file>
- <file src="lib/private/App/InfoParser.php">
- <InvalidArrayOffset>
- <code><![CDATA[$array[$element][]]]></code>
- <code><![CDATA[$array[$element][]]]></code>
- </InvalidArrayOffset>
- <InvalidReturnStatement>
- <code><![CDATA[(string)$xml]]></code>
- </InvalidReturnStatement>
- <InvalidReturnType>
- <code><![CDATA[array]]></code>
- </InvalidReturnType>
- </file>
<file src="lib/private/AppConfig.php">
<NullableReturnStatement>
<code><![CDATA[$this->fastCache[$app][$key] ?? $default]]></code>
@@ -3386,18 +3339,12 @@
</UndefinedMethod>
</file>
<file src="lib/private/AppFramework/DependencyInjection/DIContainer.php">
- <ImplementedReturnTypeMismatch>
- <code><![CDATA[boolean|null]]></code>
- </ImplementedReturnTypeMismatch>
<InvalidReturnStatement>
<code><![CDATA[$this->server]]></code>
</InvalidReturnStatement>
<InvalidReturnType>
<code><![CDATA[\OCP\IServerContainer]]></code>
</InvalidReturnType>
- <UndefinedInterfaceMethod>
- <code><![CDATA[getAppDataDir]]></code>
- </UndefinedInterfaceMethod>
</file>
<file src="lib/private/AppFramework/Http/Dispatcher.php">
<NullArgument>
@@ -3453,53 +3400,9 @@
</MoreSpecificImplementedParamType>
</file>
<file src="lib/private/AppFramework/Utility/SimpleContainer.php">
- <LessSpecificReturnStatement>
- <code><![CDATA[$class->newInstance()]]></code>
- <code><![CDATA[$class->newInstanceArgs(array_map(function (ReflectionParameter $parameter) {
- $parameterType = $parameter->getType();
-
- $resolveName = $parameter->getName();
-
- // try to find out if it is a class or a simple parameter
- if ($parameterType !== null && ($parameterType instanceof ReflectionNamedType) && !$parameterType->isBuiltin()) {
- $resolveName = $parameterType->getName();
- }
-
- try {
- $builtIn = $parameter->hasType() && ($parameter->getType() instanceof ReflectionNamedType)
- && $parameter->getType()->isBuiltin();
- return $this->query($resolveName, !$builtIn);
- } catch (QueryException $e) {
- // Service not found, use the default value when available
- if ($parameter->isDefaultValueAvailable()) {
- return $parameter->getDefaultValue();
- }
-
- if ($parameterType !== null && ($parameterType instanceof ReflectionNamedType) && !$parameterType->isBuiltin()) {
- $resolveName = $parameter->getName();
- try {
- return $this->query($resolveName);
- } catch (QueryException $e2) {
- // Pass null if typed and nullable
- if ($parameter->allowsNull() && ($parameterType instanceof ReflectionNamedType)) {
- return null;
- }
-
- // don't lose the error we got while trying to query by type
- throw new QueryException($e->getMessage(), (int)$e->getCode(), $e);
- }
- }
-
- throw $e;
- }
- }, $constructor->getParameters()))]]></code>
- </LessSpecificReturnStatement>
<MissingTemplateParam>
<code><![CDATA[ArrayAccess]]></code>
</MissingTemplateParam>
- <MoreSpecificReturnType>
- <code><![CDATA[\stdClass]]></code>
- </MoreSpecificReturnType>
<RedundantCast>
<code><![CDATA[(int)$e->getCode()]]></code>
</RedundantCast>
@@ -3882,14 +3785,6 @@
<code><![CDATA[wrap]]></code>
</UndefinedInterfaceMethod>
</file>
- <file src="lib/private/Files/Mount/ObjectHomeMountProvider.php">
- <InvalidNullableReturnType>
- <code><![CDATA[\OCP\Files\Mount\IMountPoint]]></code>
- </InvalidNullableReturnType>
- <NullableReturnStatement>
- <code><![CDATA[null]]></code>
- </NullableReturnStatement>
- </file>
<file src="lib/private/Files/Node/File.php">
<InvalidReturnStatement>
<code><![CDATA[$this->view->hash($type, $this->path, $raw)]]></code>
@@ -4017,12 +3912,6 @@
<code><![CDATA[ArrayCache]]></code>
<code><![CDATA[ArrayCache]]></code>
</InvalidClass>
- <InvalidReturnStatement>
- <code><![CDATA[$response->getBody()]]></code>
- </InvalidReturnStatement>
- <InvalidReturnType>
- <code><![CDATA[fopen]]></code>
- </InvalidReturnType>
</file>
<file src="lib/private/Files/Storage/Local.php">
<TypeDoesNotContainNull>
@@ -4099,12 +3988,6 @@
</TypeDoesNotContainNull>
</file>
<file src="lib/private/Group/Group.php">
- <InvalidArgument>
- <code><![CDATA[bool]]></code>
- </InvalidArgument>
- <InvalidOperand>
- <code><![CDATA[$hide]]></code>
- </InvalidOperand>
<LessSpecificReturnStatement>
<code><![CDATA[$users]]></code>
</LessSpecificReturnStatement>
@@ -4139,16 +4022,6 @@
<code><![CDATA[isAdmin]]></code>
</UndefinedInterfaceMethod>
</file>
- <file src="lib/private/Http/Client/Response.php">
- <InvalidNullableReturnType>
- <code><![CDATA[string|resource]]></code>
- </InvalidNullableReturnType>
- <NullableReturnStatement>
- <code><![CDATA[$this->stream ?
- $this->response->getBody()->detach():
- $this->response->getBody()->getContents()]]></code>
- </NullableReturnStatement>
- </file>
<file src="lib/private/Installer.php">
<InvalidArgument>
<code><![CDATA[false]]></code>
@@ -4192,31 +4065,12 @@
</InvalidOperand>
</file>
<file src="lib/private/Lockdown/Filesystem/NullCache.php">
- <InvalidNullableReturnType>
- <code><![CDATA[get]]></code>
- </InvalidNullableReturnType>
<InvalidReturnStatement>
<code><![CDATA[[]]]></code>
</InvalidReturnStatement>
<InvalidReturnType>
<code><![CDATA[getIncomplete]]></code>
</InvalidReturnType>
- <NullableReturnStatement>
- <code><![CDATA[$file !== '' ? null :
- new CacheEntry([
- 'fileid' => -1,
- 'parent' => -1,
- 'name' => '',
- 'path' => '',
- 'size' => '0',
- 'mtime' => time(),
- 'storage_mtime' => time(),
- 'etag' => '',
- 'mimetype' => FileInfo::MIMETYPE_FOLDER,
- 'mimepart' => 'httpd',
- 'permissions' => Constants::PERMISSION_READ
- ])]]></code>
- </NullableReturnStatement>
</file>
<file src="lib/private/Lockdown/Filesystem/NullStorage.php">
<TooManyArguments>
@@ -4306,12 +4160,6 @@
</ImplementedReturnTypeMismatch>
</file>
<file src="lib/private/Remote/Instance.php">
- <InvalidReturnStatement>
- <code><![CDATA[$request->getBody()]]></code>
- </InvalidReturnStatement>
- <InvalidReturnType>
- <code><![CDATA[bool|string]]></code>
- </InvalidReturnType>
<InvalidScalarArgument>
<code><![CDATA[$response]]></code>
</InvalidScalarArgument>
@@ -4347,9 +4195,6 @@
</ParamNameMismatch>
</file>
<file src="lib/private/Route/Router.php">
- <InvalidClass>
- <code><![CDATA[\OC_APP]]></code>
- </InvalidClass>
<InvalidNullableReturnType>
<code><![CDATA[string]]></code>
</InvalidNullableReturnType>
@@ -4379,14 +4224,11 @@
</file>
<file src="lib/private/Server.php">
<ImplementedReturnTypeMismatch>
- <code><![CDATA[\OCP\Calendar\Resource\IManager]]></code>
- <code><![CDATA[\OCP\Calendar\Room\IManager]]></code>
<code><![CDATA[\OCP\Files\Folder|null]]></code>
</ImplementedReturnTypeMismatch>
<LessSpecificReturnStatement>
<code><![CDATA[$this->get(IFile::class)]]></code>
<code><![CDATA[$this->get(IGroupManager::class)]]></code>
- <code><![CDATA[$this->get(INavigationManager::class)]]></code>
<code><![CDATA[$this->get(IUserManager::class)]]></code>
<code><![CDATA[$this->get(IUserSession::class)]]></code>
<code><![CDATA[$this->get(\OCP\Encryption\IManager::class)]]></code>
@@ -4395,13 +4237,9 @@
<code><![CDATA[\OC\Encryption\File]]></code>
<code><![CDATA[\OC\Encryption\Manager]]></code>
<code><![CDATA[\OC\Group\Manager]]></code>
- <code><![CDATA[\OC\NavigationManager]]></code>
<code><![CDATA[\OC\User\Manager]]></code>
<code><![CDATA[\OC\User\Session]]></code>
</MoreSpecificReturnType>
- <UndefinedDocblockClass>
- <code><![CDATA[\OC\OCSClient]]></code>
- </UndefinedDocblockClass>
</file>
<file src="lib/private/ServerContainer.php">
<InvalidPropertyAssignmentValue>
@@ -4468,39 +4306,13 @@
</UndefinedInterfaceMethod>
</file>
<file src="lib/private/Share20/ProviderFactory.php">
- <InvalidNullableReturnType>
- <code><![CDATA[FederatedShareProvider]]></code>
- <code><![CDATA[ShareByMailProvider]]></code>
- </InvalidNullableReturnType>
<InvalidReturnStatement>
<code><![CDATA[$provider]]></code>
<code><![CDATA[$provider]]></code>
- <code><![CDATA[$this->shareByCircleProvider]]></code>
</InvalidReturnStatement>
<InvalidReturnType>
<code><![CDATA[getProviderForType]]></code>
</InvalidReturnType>
- <NullableReturnStatement>
- <code><![CDATA[null]]></code>
- <code><![CDATA[null]]></code>
- <code><![CDATA[null]]></code>
- <code><![CDATA[null]]></code>
- <code><![CDATA[null]]></code>
- <code><![CDATA[null]]></code>
- </NullableReturnStatement>
- <ParamNameMismatch>
- <code><![CDATA[$shareProviderClass]]></code>
- </ParamNameMismatch>
- <UndefinedClass>
- <code><![CDATA[\OCA\Circles\ShareByCircleProvider]]></code>
- </UndefinedClass>
- <UndefinedDocblockClass>
- <code><![CDATA[RoomShareProvider]]></code>
- <code><![CDATA[\OCA\Circles\ShareByCircleProvider]]></code>
- <code><![CDATA[\OCA\Talk\Share\RoomShareProvider]]></code>
- <code><![CDATA[private $roomShareProvider = null;]]></code>
- <code><![CDATA[private $shareByCircleProvider = null;]]></code>
- </UndefinedDocblockClass>
</file>
<file src="lib/private/Share20/Share.php">
<LessSpecificReturnStatement>
@@ -4620,9 +4432,6 @@
<code><![CDATA[$matches[0][$last_match]]]></code>
<code><![CDATA[$matches[1][$last_match]]]></code>
</InvalidArrayOffset>
- <InvalidScalarArgument>
- <code><![CDATA[$path]]></code>
- </InvalidScalarArgument>
<UndefinedInterfaceMethod>
<code><![CDATA[getQuota]]></code>
</UndefinedInterfaceMethod>
@@ -4640,14 +4449,6 @@
<code><![CDATA[array{X-Request-Id: string, Cache-Control: string, Content-Security-Policy: string, Feature-Policy: string, X-Robots-Tag: string, Last-Modified?: string, ETag?: string, ...H}]]></code>
</MoreSpecificReturnType>
</file>
- <file src="lib/public/Color.php">
- <LessSpecificReturnStatement>
- <code><![CDATA[$step]]></code>
- </LessSpecificReturnStatement>
- <MoreSpecificReturnType>
- <code><![CDATA[array{0: int, 1: int, 2: int}]]></code>
- </MoreSpecificReturnType>
- </file>
<file src="lib/public/Preview/BeforePreviewFetchedEvent.php">
<LessSpecificReturnStatement>
<code><![CDATA[$this->mode]]></code>
@@ -4662,14 +4463,6 @@
<code><![CDATA[getRequest]]></code>
</DeprecatedMethod>
</file>
- <file src="ocs/v1.php">
- <DeprecatedMethod>
- <code><![CDATA[OC_App::loadApps()]]></code>
- <code><![CDATA[OC_App::loadApps(['authentication'])]]></code>
- <code><![CDATA[OC_App::loadApps(['extended_authentication'])]]></code>
- <code><![CDATA[OC_App::loadApps(['session'])]]></code>
- </DeprecatedMethod>
- </file>
<file src="public.php">
<DeprecatedMethod>
<code><![CDATA[getAppValue]]></code>
diff --git a/build/psalm/OcpSinceChecker.php b/build/psalm/OcpSinceChecker.php
index 959e70e0c4c..c533e944550 100644
--- a/build/psalm/OcpSinceChecker.php
+++ b/build/psalm/OcpSinceChecker.php
@@ -20,7 +20,18 @@ class OcpSinceChecker implements Psalm\Plugin\EventHandler\AfterClassLikeVisitIn
$classLike = $event->getStmt();
$statementsSource = $event->getStatementsSource();
- self::checkClassComment($classLike, $statementsSource);
+ if (!str_contains($statementsSource->getFilePath(), '/lib/public/')) {
+ return;
+ }
+
+ $isTesting = str_contains($statementsSource->getFilePath(), '/lib/public/Notification/')
+ || str_contains($statementsSource->getFilePath(), 'CalendarEventStatus');
+
+ if ($isTesting) {
+ self::checkStatementAttributes($classLike, $statementsSource);
+ } else {
+ self::checkClassComment($classLike, $statementsSource);
+ }
foreach ($classLike->stmts as $stmt) {
if ($stmt instanceof ClassConst) {
@@ -32,11 +43,64 @@ class OcpSinceChecker implements Psalm\Plugin\EventHandler\AfterClassLikeVisitIn
}
if ($stmt instanceof EnumCase) {
- self::checkStatementComment($stmt, $statementsSource, 'enum');
+ if ($isTesting) {
+ self::checkStatementAttributes($classLike, $statementsSource);
+ } else {
+ self::checkStatementComment($stmt, $statementsSource, 'enum');
+ }
}
}
}
+ private static function checkStatementAttributes(ClassLike $stmt, FileSource $statementsSource): void {
+ $hasAppFrameworkAttribute = false;
+ $mustBeConsumable = false;
+ $isConsumable = false;
+ foreach ($stmt->attrGroups as $attrGroup) {
+ foreach ($attrGroup->attrs as $attr) {
+ if (in_array($attr->name->getLast(), [
+ 'Catchable',
+ 'Consumable',
+ 'Dispatchable',
+ 'Implementable',
+ 'Listenable',
+ 'Throwable',
+ ], true)) {
+ $hasAppFrameworkAttribute = true;
+ self::checkAttributeHasValidSinceVersion($attr, $statementsSource);
+ }
+ if (in_array($attr->name->getLast(), [
+ 'Catchable',
+ 'Consumable',
+ 'Listenable',
+ ], true)) {
+ $isConsumable = true;
+ }
+ if ($attr->name->getLast() === 'ExceptionalImplementable') {
+ $mustBeConsumable = true;
+ }
+ }
+ }
+
+ if ($mustBeConsumable && !$isConsumable) {
+ IssueBuffer::maybeAdd(
+ new InvalidDocblock(
+ 'Attribute OCP\\AppFramework\\Attribute\\ExceptionalImplementable is only valid on classes that also have OCP\\AppFramework\\Attribute\\Consumable',
+ new CodeLocation($statementsSource, $stmt)
+ )
+ );
+ }
+
+ if (!$hasAppFrameworkAttribute) {
+ IssueBuffer::maybeAdd(
+ new InvalidDocblock(
+ 'At least one of the OCP\\AppFramework\\Attribute attributes is required',
+ new CodeLocation($statementsSource, $stmt)
+ )
+ );
+ }
+ }
+
private static function checkClassComment(ClassLike $stmt, FileSource $statementsSource): void {
$docblock = $stmt->getDocComment();
@@ -124,4 +188,28 @@ class OcpSinceChecker implements Psalm\Plugin\EventHandler\AfterClassLikeVisitIn
);
}
}
+
+ private static function checkAttributeHasValidSinceVersion(\PhpParser\Node\Attribute $stmt, FileSource $statementsSource): void {
+ foreach ($stmt->args as $arg) {
+ if ($arg->name?->name === 'since') {
+ if (!$arg->value instanceof \PhpParser\Node\Scalar\String_) {
+ IssueBuffer::maybeAdd(
+ new InvalidDocblock(
+ 'Attribute since argument is not a valid version string',
+ new CodeLocation($statementsSource, $stmt)
+ )
+ );
+ } else {
+ if (!preg_match('/^[1-9][0-9]*(\.[0-9]+){0,3}$/', $arg->value->value)) {
+ IssueBuffer::maybeAdd(
+ new InvalidDocblock(
+ 'Attribute since argument is not a valid version string',
+ new CodeLocation($statementsSource, $stmt)
+ )
+ );
+ }
+ }
+ }
+ }
+ }
}
diff --git a/build/rector.php b/build/rector.php
index 95fcc8d961e..246741bb34f 100644
--- a/build/rector.php
+++ b/build/rector.php
@@ -12,6 +12,9 @@ use PhpParser\Node;
use Rector\CodingStyle\Contract\ClassNameImport\ClassNameImportSkipVoterInterface;
use Rector\Config\RectorConfig;
use Rector\Php80\Rector\Class_\ClassPropertyAssignToConstructorPromotionRector;
+use Rector\PHPUnit\AnnotationsToAttributes\Rector\ClassMethod\DataProviderAnnotationToAttributeRector;
+use Rector\PHPUnit\CodeQuality\Rector\MethodCall\UseSpecificWillMethodRector;
+use Rector\PHPUnit\PHPUnit100\Rector\Class_\StaticDataProviderClassMethodRector;
use Rector\StaticTypeMapper\ValueObject\Type\FullyQualifiedObjectType;
use Rector\ValueObject\Application\File;
@@ -63,9 +66,10 @@ $config = RectorConfig::configure()
$nextcloudDir . '/remote.php',
$nextcloudDir . '/status.php',
$nextcloudDir . '/version.php',
+ $nextcloudDir . '/lib/private/Share20/ProviderFactory.php',
+ $nextcloudDir . '/tests',
// $nextcloudDir . '/config',
// $nextcloudDir . '/lib',
- // $nextcloudDir . '/tests',
// $nextcloudDir . '/themes',
])
->withSkip([
@@ -78,6 +82,11 @@ $config = RectorConfig::configure()
// ->withPhpSets()
->withImportNames(importShortClasses:false)
->withTypeCoverageLevel(0)
+ ->withRules([
+ UseSpecificWillMethodRector::class,
+ StaticDataProviderClassMethodRector::class,
+ DataProviderAnnotationToAttributeRector::class,
+ ])
->withConfiguredRule(ClassPropertyAssignToConstructorPromotionRector::class, [
'inline_public' => true,
'rename_property' => true,