Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions apps/dav/lib/Connector/Sabre/FilesPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,12 @@ function (mixed $value) use ($accessRight, $knownMetadata, $node, $mutation, $fi
throw new FilesMetadataException('you do not have enough rights to update \'' . $metadataKey . '\' on this node');
}

if ($value === null) {
$metadata->unset($metadataKey);
$filesMetadataManager->saveMetadata($metadata);
return true;
}

// If the metadata is unknown, it defaults to string.
try {
$type = $knownMetadata->getType($metadataKey);
Expand Down
6 changes: 6 additions & 0 deletions build/integration/config/behat.yml
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,12 @@ default:
- CommandLineContext:
baseUrl: http://localhost:8080
ocPath: ../../
- MetadataContext:
baseUrl: http://localhost:8080
admin:
- admin
- admin
regular_user_password: 123456
files_conversion:
paths:
- "%paths.base%/../file_conversions"
Expand Down
123 changes: 123 additions & 0 deletions build/integration/features/bootstrap/MetadataContext.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
<?php
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

use Behat\Behat\Context\Context;
use Behat\Step\Then;
use Behat\Step\When;
use PHPUnit\Framework\Assert;
use Sabre\DAV\Client as SClient;

require __DIR__ . '/../../vendor/autoload.php';

class MetadataContext implements Context {
private string $davPath = '/remote.php/dav';

public function __construct(
private string $baseUrl,
private array $admin,
private string $regular_user_password,
) {
// 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);
}
}

#[When('User :user sets the :metadataKey prop with value :metadataValue on :fileName')]
public function userSetsProp(string $user, string $metadataKey, string $metadataValue, string $fileName) {
$client = new SClient([
'baseUri' => $this->baseUrl,
'userName' => $user,
'password' => '123456',
'authType' => SClient::AUTH_BASIC,
]);

$body = '<?xml version="1.0"?>
<d:propertyupdate xmlns:d="DAV:" xmlns:nc="http://nextcloud.com/ns">
<d:set>
<d:prop>
<nc:' . $metadataKey . '>' . $metadataValue . '</nc:' . $metadataKey . '>
</d:prop>
</d:set>
</d:propertyupdate>';

$davUrl = $this->getDavUrl($user, $fileName);
$client->request('PROPPATCH', $this->baseUrl . $davUrl, $body);
}

#[When('User :user deletes the :metadataKey prop on :fileName')]
public function userDeletesProp(string $user, string $metadataKey, string $fileName) {
$client = new SClient([
'baseUri' => $this->baseUrl,
'userName' => $user,
'password' => '123456',
'authType' => SClient::AUTH_BASIC,
]);

$body = '<?xml version="1.0"?>
<d:propertyupdate xmlns:d="DAV:" xmlns:nc="http://nextcloud.com/ns">
<d:remove>
<d:prop>
<nc:' . $metadataKey . '></nc:' . $metadataKey . '>
</d:prop>
</d:remove>
</d:propertyupdate>';

$davUrl = $this->getDavUrl($user, $fileName);
$client->request('PROPPATCH', $this->baseUrl . $davUrl, $body);
}

#[Then('User :user should see the prop :metadataKey equal to :metadataValue for file :fileName')]
public function checkPropForFile(string $user, string $metadataKey, string $metadataValue, string $fileName) {
$client = new SClient([
'baseUri' => $this->baseUrl,
'userName' => $user,
'password' => '123456',
'authType' => SClient::AUTH_BASIC,
]);

$body = '<?xml version="1.0"?>
<d:propfind xmlns:d="DAV:" xmlns:nc="http://nextcloud.com/ns">
<d:prop>
<nc:' . $metadataKey . '></nc:' . $metadataKey . '>
</d:prop>
</d:propfind>';

$davUrl = $this->getDavUrl($user, $fileName);
$response = $client->request('PROPFIND', $this->baseUrl . $davUrl, $body);
$parsedResponse = $client->parseMultistatus($response['body']);

Assert::assertEquals($parsedResponse[$davUrl]['200']['{http://nextcloud.com/ns}' . $metadataKey], $metadataValue);
}

#[Then('User :user should not see the prop :metadataKey for file :fileName')]
public function checkPropDoesNotExistsForFile(string $user, string $metadataKey, string $fileName) {
$client = new SClient([
'baseUri' => $this->baseUrl,
'userName' => $user,
'password' => '123456',
'authType' => SClient::AUTH_BASIC,
]);

$body = '<?xml version="1.0"?>
<d:propfind xmlns:d="DAV:" xmlns:nc="http://nextcloud.com/ns">
<d:prop>
<nc:' . $metadataKey . '></nc:' . $metadataKey . '>
</d:prop>
</d:propfind>';

$davUrl = $this->getDavUrl($user, $fileName);
$response = $client->request('PROPFIND', $this->baseUrl . $davUrl, $body);
$parsedResponse = $client->parseMultistatus($response['body']);

Assert::assertEquals($parsedResponse[$davUrl]['404']['{http://nextcloud.com/ns}' . $metadataKey], null);
}

private function getDavUrl(string $user, string $fileName) {
return $this->davPath . '/files/' . $user . $fileName;
}
}
16 changes: 16 additions & 0 deletions build/integration/files_features/metadata.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
# SPDX-License-Identifier: AGPL-3.0-only
Feature: metadata

Scenario: Setting metadata works
Given user "user0" exists
When User "user0" uploads file with content "AAA" to "/test.txt"
And User "user0" sets the "metadata-files-live-photo" prop with value "metadata-value" on "/test.txt"
Then User "user0" should see the prop "metadata-files-live-photo" equal to "metadata-value" for file "/test.txt"

Scenario: Deleting metadata works
Given user "user0" exists
When User "user0" uploads file with content "AAA" to "/test.txt"
And User "user0" sets the "metadata-files-live-photo" prop with value "metadata-value" on "/test.txt"
And User "user0" deletes the "metadata-files-live-photo" prop on "/test.txt"
Then User "user0" should not see the prop "metadata-files-live-photo" for file "/test.txt"
Loading