Compare commits

..

5 Commits

Author SHA1 Message Date
provokateurin df380d0291 fixup! fix(Share20\Manager): Explicitly setup the filesystem for the user 2026-02-24 10:17:21 +01:00
provokateurin d12c339549 chore: Remove unused \OC\Cache\File
Signed-off-by: provokateurin <kate@provokateurin.de>
2026-02-24 10:16:31 +01:00
provokateurin 19beae15f3 refactor(Server): Deprecate \OCP\ICache service and replace it with a local cache
Signed-off-by: provokateurin <kate@provokateurin.de>
2026-02-24 10:14:21 +01:00
provokateurin 41642aae45 perf(base): Stop setting up the FS for every basic auth request
Signed-off-by: provokateurin <kate@provokateurin.de>
2026-02-24 10:14:21 +01:00
provokateurin 5642b6b336 fix(Share20\Manager): Explicitly setup the filesystem for the user
Signed-off-by: provokateurin <kate@provokateurin.de>
2026-02-24 10:14:21 +01:00
475 changed files with 1524 additions and 4105 deletions
+1 -1
View File
@@ -73,7 +73,7 @@ body:
options:
- "32"
- "33"
- "34 (master)"
- "master"
validations:
required: true
- type: dropdown
-4
View File
@@ -23,7 +23,3 @@
- [ ] [Backports requested](https://github.com/nextcloud/backportbot/#usage) where applicable (ex: critical bugfixes)
- [ ] [Labels added](https://github.com/nextcloud/server/labels) where applicable (ex: bug/enhancement, `3. to review`, feature component)
- [ ] [Milestone added](https://github.com/nextcloud/server/milestones) for target branch/version (ex: 32.x for `stable32`)
## AI (if applicable)
- [ ] The content of this PR was partly or fully generated using AI
-45
View File
@@ -1,45 +0,0 @@
# SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
# SPDX-License-Identifier: AGPL-3.0-or-later
name: Auto-label bug reports
on:
issues:
types: [opened]
jobs:
add-version-label:
if: contains(github.event.issue.title, '[Bug]')
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- name: Extract version number and apply label
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
script: |
const body = context.payload.issue.body || '';
const normalizedBody = body.replace(/\r\n?/g, '\n');
let label = '';
// Extract Nextcloud Server version number from a block like:
// ### Nextcloud Server version
// 32
const versionMatch = normalizedBody.match(/### Nextcloud Server version\s*\n+([0-9]{1,3})\b/);
let nextcloudVersion = null;
if (versionMatch) {
nextcloudVersion = parseInt(versionMatch[1], 10);
label = nextcloudVersion + '-feedback';
}
if (label) {
try {
await github.rest.issues.addLabels({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: [label]
});
} catch (error) {
core.setFailed(`Failed to add label "${label}": ${error.message || error}`);
}
}
+2 -2
View File
@@ -37,13 +37,13 @@ jobs:
persist-credentials: false
- name: Initialize CodeQL
uses: github/codeql-action/init@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4
uses: github/codeql-action/init@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v4.32.2
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
config-file: ./.github/codeql-config.yml
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@89a39a4e59826350b863aa6b6252a07ad50cf83e # v4.32.4
uses: github/codeql-action/analyze@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v4.32.2
with:
category: "/language:${{matrix.language}}"
+3 -3
View File
@@ -171,7 +171,7 @@ jobs:
run: ./node_modules/cypress/bin/cypress install
- name: Run ${{ matrix.containers == 'component' && 'component' || 'E2E' }} cypress tests
uses: cypress-io/github-action@bc22e01685c56e89e7813fd8e26f33dc47f87e15 # v7.1.5
uses: cypress-io/github-action@84d178e4bbce871e23f2ffa3085898cde0e4f0ec # v7.1.2
with:
# We already installed the dependencies in the init job
install: false
@@ -195,7 +195,7 @@ jobs:
SETUP_TESTING: ${{ matrix.containers == 'setup' && 'true' || '' }}
- name: Upload snapshots and videos
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
if: always()
with:
name: snapshots_${{ matrix.containers }}
@@ -218,7 +218,7 @@ jobs:
run: docker exec nextcloud-e2e-test-server_${{ env.APP_NAME }} tar -cvjf - data > data.tar
- name: Upload data archive
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
if: failure() && matrix.containers != 'component'
with:
name: nc_data_${{ matrix.containers }}
+1 -1
View File
@@ -71,7 +71,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Set up Python
uses: LizardByte/actions/actions/setup_python@70bb8d394d1c92f6113aeec6ae9cc959a5763d15 # v2026.227.200013
uses: LizardByte/actions/actions/setup_python@9bf3ef783775e17fe6b8dde3585d94ec570b93c2 # v2026.212.22356
with:
python-version: '2.7'
+3 -3
View File
@@ -63,7 +63,7 @@ jobs:
ref: ${{ github.event.pull_request.head.ref }}
- name: Run before measurements
uses: nextcloud/profiler@6a74c915048285b35b8e1cd96c0835a635945044
uses: nextcloud/profiler@6801ee10fc80f10b444388fb6ca9b36ad8a2ea83
with:
run: |
curl -s -X PROPFIND -u test:test http://localhost:8080/remote.php/dav/files/test
@@ -85,7 +85,7 @@ jobs:
- name: Run after measurements
id: compare
uses: nextcloud/profiler@6a74c915048285b35b8e1cd96c0835a635945044
uses: nextcloud/profiler@6801ee10fc80f10b444388fb6ca9b36ad8a2ea83
with:
run: |
curl -s -X PROPFIND -u test:test http://localhost:8080/remote.php/dav/files/test
@@ -99,7 +99,7 @@ jobs:
- name: Upload profiles
if: always()
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
with:
name: profiles
path: |
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
issues: write
steps:
- uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v9
- uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v9
with:
repo-token: ${{ secrets.COMMAND_BOT_PAT }}
stale-issue-message: >
+1 -1
View File
@@ -88,7 +88,7 @@ jobs:
- name: Upload Security Analysis results to GitHub
if: always()
uses: github/codeql-action/upload-sarif@89a39a4e59826350b863aa6b6252a07ad50cf83e # v3
uses: github/codeql-action/upload-sarif@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v3
with:
sarif_file: results.sarif
@@ -77,7 +77,6 @@ return array(
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarFactory' => $baseDir . '/../lib/CalDAV/Federation/FederatedCalendarFactory.php',
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarImpl' => $baseDir . '/../lib/CalDAV/Federation/FederatedCalendarImpl.php',
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarMapper' => $baseDir . '/../lib/CalDAV/Federation/FederatedCalendarMapper.php',
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarObject' => $baseDir . '/../lib/CalDAV/Federation/FederatedCalendarObject.php',
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarSyncService' => $baseDir . '/../lib/CalDAV/Federation/FederatedCalendarSyncService.php',
'OCA\\DAV\\CalDAV\\Federation\\FederationSharingService' => $baseDir . '/../lib/CalDAV/Federation/FederationSharingService.php',
'OCA\\DAV\\CalDAV\\Federation\\Protocol\\CalendarFederationProtocolV1' => $baseDir . '/../lib/CalDAV/Federation/Protocol/CalendarFederationProtocolV1.php',
@@ -92,7 +92,6 @@ class ComposerStaticInitDAV
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarFactory' => __DIR__ . '/..' . '/../lib/CalDAV/Federation/FederatedCalendarFactory.php',
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarImpl' => __DIR__ . '/..' . '/../lib/CalDAV/Federation/FederatedCalendarImpl.php',
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarMapper' => __DIR__ . '/..' . '/../lib/CalDAV/Federation/FederatedCalendarMapper.php',
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarObject' => __DIR__ . '/..' . '/../lib/CalDAV/Federation/FederatedCalendarObject.php',
'OCA\\DAV\\CalDAV\\Federation\\FederatedCalendarSyncService' => __DIR__ . '/..' . '/../lib/CalDAV/Federation/FederatedCalendarSyncService.php',
'OCA\\DAV\\CalDAV\\Federation\\FederationSharingService' => __DIR__ . '/..' . '/../lib/CalDAV/Federation/FederationSharingService.php',
'OCA\\DAV\\CalDAV\\Federation\\Protocol\\CalendarFederationProtocolV1' => __DIR__ . '/..' . '/../lib/CalDAV/Federation/Protocol/CalendarFederationProtocolV1.php',
-7
View File
@@ -191,7 +191,6 @@ OC.L10N.register(
"{actor} updated contact {card} in address book {addressbook}" : "{actor} оновив(-ла) контакт {card} в адресній книзі {addressbook}",
"You updated contact {card} in address book {addressbook}" : "Ви оновили контакт {card} в адресній книзі {addressbook}",
"A <strong>contact</strong> or <strong>address book</strong> was modified" : "<strong>Контактну</strong> або <strong>адресну книгу</strong> було змінено",
"System address book disabled" : "Системну адресну книгу вимкнено",
"Accounts" : "Облікові записи",
"System address book which holds all accounts" : "Системна адресна книга, в якій містяться всі облікові записи",
"File is not updatable: %1$s" : "Файл не оновлюється: %1$s",
@@ -204,8 +203,6 @@ OC.L10N.register(
"Could not rename part file to final file, canceled by hook" : "Не вдалося перейменувати файл частини на остаточний файл, скасовано підхопленням",
"Could not rename part file to final file" : "Не вдалося перейменувати файл частини на остаточний файл",
"Failed to check file size: %1$s" : "Не вдалося перевірити розмір файлу: %1$s",
"Could not open file: %1$s (%2$d), file does seem to exist" : "Не вдалося відкрити файл: %1$s (%2$d), хоча схоже, що файл наявний",
"Could not open file: %1$s (%2$d), file doesn't seem to exist" : "Не вдалося відкрити файл: %1$s (%2$d), схоже, що файл відсутній",
"Encryption not ready: %1$s" : "Шифрування не готове: %1$s",
"Failed to open file: %1$s" : "Не вдалося відкрити файл: %1$s",
"Failed to unlink: %1$s" : "Не вдалося від’єднати: %1$s",
@@ -230,10 +227,6 @@ OC.L10N.register(
"DAV system address book" : "Системна адресна книга DAV",
"No outstanding DAV system address book sync." : "Немає незавершеної синхронізації системної адресної книги DAV.",
"The DAV system address book sync has not run yet as your instance has more than 1000 users or because an error occurred. Please run it manually by calling \"occ dav:sync-system-addressbook\"." : "Синхронізація системної адресної книги DAV ще не запускалася, оскільки, або ваша система вже має понад 1000 користувачів, або сталася помилка. Будь ласка, запустіть синхронізацію вручну за допомогою команди \"occ dav:sync-system-addressbook\".",
"DAV system address book size" : "Розмір системної адресної книги DAV ",
"The system address book is disabled" : "Системну адресну книгу вимкнено",
"The system address book is enabled, but contains more than the configured limit of %d contacts" : "Системну адресну книгу увімкнено, проте вона містить більше, ніж визначено максимальну кількість у %d контактів.",
"The system address book is enabled and contains less than the configured limit of %d contacts" : "Системну адресну книгу увімкнено, проте вона містить менше, ніж визначено максимальну кількість у %d контактів.",
"WebDAV endpoint" : "Точка доступу WebDAV",
"Could not check that your web server is properly set up to allow file synchronization over WebDAV. Please check manually." : "Неможливо перевірити, чи на вашому вебсервері правильно налаштовано доступ для синхронізації файлів через протокол WebDAV. Перевірте це вручну.",
"Your web server is not yet properly set up to allow file synchronization, because the WebDAV interface seems to be broken." : "Ваш вебсервер не налаштований як треба для синхронізації файлів, схоже інтерфейс WebDAV поламаний.",
-7
View File
@@ -189,7 +189,6 @@
"{actor} updated contact {card} in address book {addressbook}" : "{actor} оновив(-ла) контакт {card} в адресній книзі {addressbook}",
"You updated contact {card} in address book {addressbook}" : "Ви оновили контакт {card} в адресній книзі {addressbook}",
"A <strong>contact</strong> or <strong>address book</strong> was modified" : "<strong>Контактну</strong> або <strong>адресну книгу</strong> було змінено",
"System address book disabled" : "Системну адресну книгу вимкнено",
"Accounts" : "Облікові записи",
"System address book which holds all accounts" : "Системна адресна книга, в якій містяться всі облікові записи",
"File is not updatable: %1$s" : "Файл не оновлюється: %1$s",
@@ -202,8 +201,6 @@
"Could not rename part file to final file, canceled by hook" : "Не вдалося перейменувати файл частини на остаточний файл, скасовано підхопленням",
"Could not rename part file to final file" : "Не вдалося перейменувати файл частини на остаточний файл",
"Failed to check file size: %1$s" : "Не вдалося перевірити розмір файлу: %1$s",
"Could not open file: %1$s (%2$d), file does seem to exist" : "Не вдалося відкрити файл: %1$s (%2$d), хоча схоже, що файл наявний",
"Could not open file: %1$s (%2$d), file doesn't seem to exist" : "Не вдалося відкрити файл: %1$s (%2$d), схоже, що файл відсутній",
"Encryption not ready: %1$s" : "Шифрування не готове: %1$s",
"Failed to open file: %1$s" : "Не вдалося відкрити файл: %1$s",
"Failed to unlink: %1$s" : "Не вдалося від’єднати: %1$s",
@@ -228,10 +225,6 @@
"DAV system address book" : "Системна адресна книга DAV",
"No outstanding DAV system address book sync." : "Немає незавершеної синхронізації системної адресної книги DAV.",
"The DAV system address book sync has not run yet as your instance has more than 1000 users or because an error occurred. Please run it manually by calling \"occ dav:sync-system-addressbook\"." : "Синхронізація системної адресної книги DAV ще не запускалася, оскільки, або ваша система вже має понад 1000 користувачів, або сталася помилка. Будь ласка, запустіть синхронізацію вручну за допомогою команди \"occ dav:sync-system-addressbook\".",
"DAV system address book size" : "Розмір системної адресної книги DAV ",
"The system address book is disabled" : "Системну адресну книгу вимкнено",
"The system address book is enabled, but contains more than the configured limit of %d contacts" : "Системну адресну книгу увімкнено, проте вона містить більше, ніж визначено максимальну кількість у %d контактів.",
"The system address book is enabled and contains less than the configured limit of %d contacts" : "Системну адресну книгу увімкнено, проте вона містить менше, ніж визначено максимальну кількість у %d контактів.",
"WebDAV endpoint" : "Точка доступу WebDAV",
"Could not check that your web server is properly set up to allow file synchronization over WebDAV. Please check manually." : "Неможливо перевірити, чи на вашому вебсервері правильно налаштовано доступ для синхронізації файлів через протокол WebDAV. Перевірте це вручну.",
"Your web server is not yet properly set up to allow file synchronization, because the WebDAV interface seems to be broken." : "Ваш вебсервер не налаштований як треба для синхронізації файлів, схоже інтерфейс WebDAV поламаний.",
+4 -2
View File
@@ -43,9 +43,9 @@ class CalendarProvider implements ICalendarProvider {
});
}
$additionalProperties = $this->getAdditionalPropertiesForCalendars($calendarInfos);
$iCalendars = [];
$additionalProperties = $this->getAdditionalPropertiesForCalendars($calendarInfos);
foreach ($calendarInfos as $calendarInfo) {
$user = str_replace('principals/users/', '', $calendarInfo['principaluri']);
$path = 'calendars/' . $user . '/' . $calendarInfo['uri'];
@@ -60,7 +60,9 @@ class CalendarProvider implements ICalendarProvider {
);
}
$additionalFederatedProps = $this->getAdditionalPropertiesForCalendars($federatedCalendarInfos);
$additionalFederatedProps = $this->getAdditionalPropertiesForCalendars(
$federatedCalendarInfos,
);
foreach ($federatedCalendarInfos as $calendarInfo) {
$user = str_replace('principals/users/', '', $calendarInfo['principaluri']);
$path = 'calendars/' . $user . '/' . $calendarInfo['uri'];
@@ -104,10 +104,9 @@ class CalendarFederationProvider implements ICloudFederationProvider {
);
}
// convert access to permissions
// TODO: implement read-write sharing
$permissions = match ($access) {
DavSharingBackend::ACCESS_READ => Constants::PERMISSION_READ,
DavSharingBackend::ACCESS_READ_WRITE => Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE,
default => throw new ProviderCouldNotAddShareException(
"Unsupported access value: $access",
'',
@@ -123,27 +122,20 @@ class CalendarFederationProvider implements ICloudFederationProvider {
$sharedWithPrincipal = 'principals/users/' . $share->getShareWith();
// Delete existing incoming federated share first
$calendar = $this->federatedCalendarMapper->findByUri($sharedWithPrincipal, $calendarUri);
$this->federatedCalendarMapper->deleteByUri($sharedWithPrincipal, $calendarUri);
if ($calendar === null) {
$calendar = new FederatedCalendarEntity();
$calendar->setPrincipaluri($sharedWithPrincipal);
$calendar->setUri($calendarUri);
$calendar->setRemoteUrl($calendarUrl);
$calendar->setDisplayName($displayName);
$calendar->setColor($color);
$calendar->setToken($share->getShareSecret());
$calendar->setSharedBy($share->getSharedBy());
$calendar->setSharedByDisplayName($share->getSharedByDisplayName());
$calendar->setPermissions($permissions);
$calendar->setComponents($components);
$calendar = $this->federatedCalendarMapper->insert($calendar);
} else {
$calendar->setToken($share->getShareSecret());
$calendar->setPermissions($permissions);
$calendar->setComponents($components);
$this->federatedCalendarMapper->update($calendar);
}
$calendar = new FederatedCalendarEntity();
$calendar->setPrincipaluri($sharedWithPrincipal);
$calendar->setUri($calendarUri);
$calendar->setRemoteUrl($calendarUrl);
$calendar->setDisplayName($displayName);
$calendar->setColor($color);
$calendar->setToken($share->getShareSecret());
$calendar->setSharedBy($share->getSharedBy());
$calendar->setSharedByDisplayName($share->getSharedByDisplayName());
$calendar->setPermissions($permissions);
$calendar->setComponents($components);
$calendar = $this->federatedCalendarMapper->insert($calendar);
$this->jobList->add(FederatedCalendarSyncJob::class, [
FederatedCalendarSyncJob::ARGUMENT_ID => $calendar->getId(),
@@ -10,289 +10,29 @@ declare(strict_types=1);
namespace OCA\DAV\CalDAV\Federation;
use OCA\DAV\CalDAV\CalDavBackend;
use OCP\Constants;
use Sabre\CalDAV\ICalendar;
use Sabre\CalDAV\Plugin;
use Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet;
use Sabre\DAV\Exception\MethodNotAllowed;
use Sabre\DAV\Exception\NotFound;
use Sabre\DAV\IMultiGet;
use Sabre\DAV\INode;
use Sabre\DAV\IProperties;
use Sabre\DAV\PropPatch;
class FederatedCalendar implements ICalendar, IProperties, IMultiGet {
private const CALENDAR_TYPE = CalDavBackend::CALENDAR_TYPE_FEDERATED;
private const DAV_PROPERTY_CALENDAR_LABEL = '{DAV:}displayname';
private const DAV_PROPERTY_CALENDAR_COLOR = '{http://apple.com/ns/ical/}calendar-color';
private string $principalUri;
private string $calendarUri;
private ?array $calendarACL = null;
private FederatedCalendarEntity $federationInfo;
use OCA\DAV\CalDAV\Calendar;
use OCP\IConfig;
use OCP\IL10N;
use Psr\Log\LoggerInterface;
use Sabre\CalDAV\Backend;
class FederatedCalendar extends Calendar {
public function __construct(
private readonly FederatedCalendarMapper $federatedCalendarMapper,
private readonly FederatedCalendarSyncService $federatedCalendarService,
private readonly CalDavBackend $caldavBackend,
Backend\BackendInterface $caldavBackend,
$calendarInfo,
IL10N $l10n,
IConfig $config,
LoggerInterface $logger,
private readonly FederatedCalendarMapper $federatedCalendarMapper,
) {
$this->principalUri = $calendarInfo['principaluri'];
$this->calendarUri = $calendarInfo['uri'];
$this->federationInfo = $federatedCalendarMapper->findByUri($this->principalUri, $this->calendarUri);
parent::__construct($caldavBackend, $calendarInfo, $l10n, $config, $logger);
}
public function getResourceId(): int {
return $this->federationInfo->getId();
}
public function getName(): string {
return $this->federationInfo->getUri();
}
/**
* @param string $name Name of the file
*/
public function setName($name): void {
throw new MethodNotAllowed('Renaming federated calendars is not allowed');
}
protected function getCalendarType(): int {
return self::CALENDAR_TYPE;
}
public function getPrincipalURI(): string {
return $this->federationInfo->getPrincipaluri();
}
public function getOwner(): ?string {
return $this->federationInfo->getSharedByPrincipal();
}
public function getGroup(): ?string {
return null;
}
/**
* @return array<array-key, mixed>
*/
public function getACL(): array {
if ($this->calendarACL !== null) {
return $this->calendarACL;
}
$permissions = $this->federationInfo->getPermissions();
// default permission
$acl = [
// read object permission
[
'privilege' => '{DAV:}read',
'principal' => $this->principalUri,
'protected' => true,
],
// read acl permission
[
'privilege' => '{DAV:}read-acl',
'principal' => $this->principalUri,
'protected' => true,
],
// write properties permission (calendar name, color)
[
'privilege' => '{DAV:}write-properties',
'principal' => $this->principalUri,
'protected' => true,
],
];
// create permission
if ($permissions & Constants::PERMISSION_CREATE) {
$acl[] = [
'privilege' => '{DAV:}bind',
'principal' => $this->principalUri,
'protected' => true,
];
}
// update permission
if ($permissions & Constants::PERMISSION_UPDATE) {
$acl[] = [
'privilege' => '{DAV:}write-content',
'principal' => $this->principalUri,
'protected' => true,
];
}
// delete permission
if ($permissions & Constants::PERMISSION_DELETE) {
$acl[] = [
'privilege' => '{DAV:}unbind',
'principal' => $this->principalUri,
'protected' => true,
];
}
// cache the calculated ACL for later use
$this->calendarACL = $acl;
return $acl;
}
public function setACL(array $acl): void {
throw new MethodNotAllowed('Changing ACLs on federated calendars is not allowed');
}
public function getSupportedPrivilegeSet(): ?array {
return null;
}
/**
* @return array<string, mixed> properties array, with property name as key
*/
public function getProperties($properties): array {
return [
self::DAV_PROPERTY_CALENDAR_LABEL => $this->federationInfo->getDisplayName(),
self::DAV_PROPERTY_CALENDAR_COLOR => $this->federationInfo->getColor(),
'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet(explode(',', $this->federationInfo->getComponents())),
];
}
public function propPatch(PropPatch $propPatch): void {
$mutations = $propPatch->getMutations();
if (count($mutations) > 0) {
// evaluate if name was changed
if (isset($mutations[self::DAV_PROPERTY_CALENDAR_LABEL])) {
$this->federationInfo->setDisplayName($mutations[self::DAV_PROPERTY_CALENDAR_LABEL]);
$propPatch->setResultCode(self::DAV_PROPERTY_CALENDAR_LABEL, 200);
}
// evaluate if color was changed
if (isset($mutations[self::DAV_PROPERTY_CALENDAR_COLOR])) {
$this->federationInfo->setColor($mutations[self::DAV_PROPERTY_CALENDAR_COLOR]);
$propPatch->setResultCode(self::DAV_PROPERTY_CALENDAR_COLOR, 200);
}
$this->federatedCalendarMapper->update($this->federationInfo);
}
}
public function getChildACL(): array {
return $this->getACL();
}
public function getLastModified(): ?int {
return $this->federationInfo->getLastSync();
}
public function delete(): void {
public function delete() {
$this->federatedCalendarMapper->deleteById($this->getResourceId());
}
/**
* @param string $name Name of the file
*/
public function createDirectory($name): void {
throw new MethodNotAllowed('Creating nested collection is not allowed');
protected function getCalendarType(): int {
return CalDavBackend::CALENDAR_TYPE_FEDERATED;
}
public function calendarQuery(array $filters): array {
$uris = $this->caldavBackend->calendarQuery($this->federationInfo->getId(), $filters, $this->getCalendarType());
return $uris;
}
/**
* @param string $name Name of the file
*/
public function getChild($name): INode {
$obj = $this->caldavBackend->getCalendarObject($this->federationInfo->getId(), $name, $this->getCalendarType());
if ($obj === null) {
throw new NotFound('Calendar object not found');
}
return new FederatedCalendarObject($this, $obj);
}
/**
* @return array<INode>
*/
public function getChildren(): array {
$objs = $this->caldavBackend->getCalendarObjects($this->federationInfo->getId(), $this->getCalendarType());
$children = [];
foreach ($objs as $obj) {
$children[] = new FederatedCalendarObject($this, $obj);
}
return $children;
}
/**
* @param array<string> $paths Names of the files
*
* @return array<INode>
*/
public function getMultipleChildren(array $paths): array {
$objs = $this->caldavBackend->getMultipleCalendarObjects($this->federationInfo->getId(), $paths, $this->getCalendarType());
$children = [];
foreach ($objs as $obj) {
$children[] = new FederatedCalendarObject($this, $obj);
}
return $children;
}
/**
* @param string $name Name of the file
*/
public function childExists($name): bool {
$obj = $this->caldavBackend->getCalendarObject($this->federationInfo->getId(), $name, $this->getCalendarType());
return $obj !== null;
}
/**
* @param string $name Name of the file
* @param resource|string $data Initial payload
*/
public function createFile($name, $data = null): string {
if (is_resource($data)) {
$data = stream_get_contents($data);
}
// Create on remote server first
$etag = $this->federatedCalendarService->createCalendarObject($this->federationInfo, $name, $data);
if (empty($etag)) {
throw new \Exception('Failed to create calendar object on remote server');
}
// Then store locally
return $this->caldavBackend->createCalendarObject($this->federationInfo->getId(), $name, $data, $this->getCalendarType());
}
/**
* @param string $name Name of the file
* @param resource|string $data Initial payload
*/
public function updateFile($name, $data): string {
if (is_resource($data)) {
$data = stream_get_contents($data);
}
// Update remote calendar first
$etag = $this->federatedCalendarService->updateCalendarObject($this->federationInfo, $name, $data);
if (empty($etag)) {
throw new \Exception('Failed to update calendar object on remote server');
}
// Then update locally
return $this->caldavBackend->updateCalendarObject($this->federationInfo->getId(), $name, $data, $this->getCalendarType());
}
public function deleteFile(string $name): void {
// Delete from remote server first
$this->federatedCalendarService->deleteCalendarObject($this->federationInfo, $name);
// Then delete locally
$this->caldavBackend->deleteCalendarObject($this->federationInfo->getId(), $name, $this->getCalendarType());
}
}
@@ -94,8 +94,8 @@ class FederatedCalendarEntity extends Entity {
'{' . \Sabre\CalDAV\Plugin::NS_CALENDARSERVER . '}getctag' => $this->getSyncTokenForSabre(),
'{' . \Sabre\CalDAV\Plugin::NS_CALDAV . '}supported-calendar-component-set' => $this->getSupportedCalendarComponentSet(),
'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->getSharedByPrincipal(),
'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => ($this->getPermissions() & \OCP\Constants::PERMISSION_UPDATE) === 0 ? 1 : 0,
'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}permissions' => $this->getPermissions(),
// TODO: implement read-write sharing
'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => 1
];
}
}
@@ -9,23 +9,34 @@ declare(strict_types=1);
namespace OCA\DAV\CalDAV\Federation;
use OCA\DAV\AppInfo\Application;
use OCA\DAV\CalDAV\CalDavBackend;
use OCP\IConfig;
use OCP\IL10N;
use OCP\L10N\IFactory as IL10NFactory;
use Psr\Log\LoggerInterface;
class FederatedCalendarFactory {
private readonly IL10N $l10n;
public function __construct(
private readonly FederatedCalendarMapper $federatedCalendarMapper,
private readonly FederatedCalendarSyncService $federatedCalendarService,
private readonly CalDavBackend $caldavBackend,
private readonly IConfig $config,
private readonly LoggerInterface $logger,
private readonly FederatedCalendarMapper $federatedCalendarMapper,
IL10NFactory $l10nFactory,
) {
$this->l10n = $l10nFactory->get(Application::APP_ID);
}
public function createFederatedCalendar(array $calendarInfo): FederatedCalendar {
return new FederatedCalendar(
$this->federatedCalendarMapper,
$this->federatedCalendarService,
$this->caldavBackend,
$calendarInfo,
$this->l10n,
$this->config,
$this->logger,
$this->federatedCalendarMapper,
);
}
}
@@ -51,7 +51,8 @@ class FederatedCalendarImpl implements ICalendar, ICalendarIsShared, ICalendarIs
}
public function getPermissions(): int {
return $this->calendarInfo['{http://owncloud.org/ns}permissions'] ?? Constants::PERMISSION_READ;
// TODO: implement read-write sharing
return Constants::PERMISSION_READ;
}
public function isDeleted(): bool {
@@ -63,8 +64,7 @@ class FederatedCalendarImpl implements ICalendar, ICalendarIsShared, ICalendarIs
}
public function isWritable(): bool {
$permissions = $this->getPermissions();
return ($permissions & Constants::PERMISSION_UPDATE) !== 0;
return false;
}
public function isEnabled(): bool {
@@ -1,107 +0,0 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\DAV\CalDAV\Federation;
use Sabre\CalDAV\ICalendarObject;
use Sabre\DAV\Exception\MethodNotAllowed;
use Sabre\DAVACL\IACL;
class FederatedCalendarObject implements ICalendarObject, IACL {
public function __construct(
protected FederatedCalendar $calendarObject,
protected $objectData,
) {
}
public function getName(): string {
return $this->objectData['uri'];
}
/**
* @param string $name Name of the file
*/
public function setName($name) {
throw new \Exception('Not implemented');
}
public function get(): string {
return $this->objectData['calendardata'];
}
/**
* @param resource|string $data contents of the file
*/
public function put($data): string {
$etag = $this->calendarObject->updateFile($this->objectData['uri'], $data);
$this->objectData['calendardata'] = $data;
$this->objectData['etag'] = $etag;
return $etag;
}
public function delete(): void {
$this->calendarObject->deleteFile($this->objectData['uri']);
}
public function getContentType(): ?string {
$mime = 'text/calendar; charset=utf-8';
if (isset($this->objectData['component']) && $this->objectData['component']) {
$mime .= '; component=' . $this->objectData['component'];
}
return $mime;
}
public function getETag(): string {
if (isset($this->objectData['etag'])) {
return $this->objectData['etag'];
} else {
return '"' . md5($this->get()) . '"';
}
}
public function getLastModified(): int {
return $this->objectData['lastmodified'];
}
public function getSize(): int {
if (isset($this->objectData['size'])) {
return $this->objectData['size'];
} else {
return strlen($this->get());
}
}
public function getOwner(): ?string {
return $this->calendarObject->getPrincipalURI();
}
public function getGroup(): ?string {
return null;
}
/**
* @return array<array-key, mixed>
*/
public function getACL(): array {
return $this->calendarObject->getACL();
}
public function setACL(array $acl): void {
throw new MethodNotAllowed('Changing ACLs on federated events is not allowed');
}
public function getSupportedPrivilegeSet(): ?array {
return null;
}
}
@@ -9,52 +9,20 @@ declare(strict_types=1);
namespace OCA\DAV\CalDAV\Federation;
use OCA\DAV\CalDAV\CalDavBackend;
use OCA\DAV\Service\ASyncService;
use OCP\AppFramework\Db\TTransactional;
use OCP\AppFramework\Http;
use OCA\DAV\CalDAV\SyncService as CalDavSyncService;
use OCP\Federation\ICloudIdManager;
use OCP\Http\Client\IClientService;
use OCP\IConfig;
use OCP\IDBConnection;
use Psr\Http\Client\ClientExceptionInterface;
use Psr\Log\LoggerInterface;
class FederatedCalendarSyncService extends ASyncService {
use TTransactional;
class FederatedCalendarSyncService {
private const SYNC_TOKEN_PREFIX = 'http://sabre.io/ns/sync/';
public function __construct(
IClientService $clientService,
IConfig $config,
private readonly FederatedCalendarMapper $federatedCalendarMapper,
private readonly LoggerInterface $logger,
private readonly CalDavBackend $backend,
private readonly IDBConnection $dbConnection,
private readonly CalDavSyncService $syncService,
private readonly ICloudIdManager $cloudIdManager,
) {
parent::__construct($clientService, $config);
}
/**
* Extract and encode credentials from a federated calendar entity.
*
* @return array{username: string, remoteUrl: string, token: string}
*/
private function getCredentials(FederatedCalendarEntity $calendar): array {
[,, $sharedWith] = explode('/', $calendar->getPrincipaluri());
$calDavUser = $this->cloudIdManager->getCloudId($sharedWith, null)->getId();
// Need to encode the cloud id as it might contain a colon which is not allowed in basic
// auth according to RFC 7617
$calDavUser = base64_encode($calDavUser);
return [
'username' => $calDavUser,
'remoteUrl' => $calendar->getRemoteUrl(),
'token' => $calendar->getToken(),
];
}
/**
@@ -63,77 +31,29 @@ class FederatedCalendarSyncService extends ASyncService {
* @throws ClientExceptionInterface If syncing the calendar fails.
*/
public function syncOne(FederatedCalendarEntity $calendar): int {
$credentials = $this->getCredentials($calendar);
[,, $sharedWith] = explode('/', $calendar->getPrincipaluri());
$calDavUser = $this->cloudIdManager->getCloudId($sharedWith, null)->getId();
$remoteUrl = $calendar->getRemoteUrl();
$syncToken = $calendar->getSyncTokenForSabre();
try {
$response = $this->requestSyncReport(
$credentials['remoteUrl'],
$credentials['username'],
$credentials['token'],
$syncToken,
);
} catch (ClientExceptionInterface $ex) {
if ($ex->getCode() === Http::STATUS_UNAUTHORIZED) {
// Remote server revoked access to the calendar => remove it
$this->federatedCalendarMapper->delete($calendar);
$this->logger->warning("Authorization failed, remove federated calendar: {$credentials['remoteUrl']}", [
'app' => 'dav',
]);
return 0;
}
$this->logger->error('Client exception:', ['app' => 'dav', 'exception' => $ex]);
throw $ex;
}
// Need to encode the cloud id as it might contain a colon which is not allowed in basic
// auth according to RFC 7617
$calDavUser = base64_encode($calDavUser);
// Process changes from remote
$downloadedEvents = 0;
foreach ($response['response'] as $resource => $status) {
$objectUri = basename($resource);
if (isset($status[200])) {
// Object created or updated
$absoluteUrl = $this->prepareUri($credentials['remoteUrl'], $resource);
$calendarData = $this->download($absoluteUrl, $credentials['username'], $credentials['token']);
$this->atomic(function () use ($calendar, $objectUri, $calendarData): void {
$existingObject = $this->backend->getCalendarObject(
$calendar->getId(),
$objectUri,
CalDavBackend::CALENDAR_TYPE_FEDERATED
);
if (!$existingObject) {
$this->backend->createCalendarObject(
$calendar->getId(),
$objectUri,
$calendarData,
CalDavBackend::CALENDAR_TYPE_FEDERATED
);
} else {
$this->backend->updateCalendarObject(
$calendar->getId(),
$objectUri,
$calendarData,
CalDavBackend::CALENDAR_TYPE_FEDERATED
);
}
}, $this->dbConnection);
$downloadedEvents++;
} else {
// Object deleted
$this->backend->deleteCalendarObject(
$calendar->getId(),
$objectUri,
CalDavBackend::CALENDAR_TYPE_FEDERATED,
true
);
}
}
$syncResponse = $this->syncService->syncRemoteCalendar(
$remoteUrl,
$calDavUser,
$calendar->getToken(),
$syncToken,
$calendar,
);
$newSyncToken = $response['token'];
$newSyncToken = $syncResponse->getSyncToken();
// Check sync token format and extract the actual sync token integer
$matches = [];
if (!preg_match('/^http:\/\/sabre\.io\/ns\/sync\/([0-9]+)$/', $newSyncToken, $matches)) {
$this->logger->error("Failed to sync federated calendar at {$credentials['remoteUrl']}: New sync token has unexpected format: $newSyncToken", [
$this->logger->error("Failed to sync federated calendar at $remoteUrl: New sync token has unexpected format: $newSyncToken", [
'calendar' => $calendar->toCalendarInfo(),
'newSyncToken' => $newSyncToken,
]);
@@ -147,58 +67,10 @@ class FederatedCalendarSyncService extends ASyncService {
$newSyncToken,
);
} else {
$this->logger->debug("Sync Token for {$credentials['remoteUrl']} unchanged from previous sync");
$this->logger->debug("Sync Token for $remoteUrl unchanged from previous sync");
$this->federatedCalendarMapper->updateSyncTime($calendar->getId());
}
return $downloadedEvents;
}
/**
* Create a calendar object on the remote server.
*
* @throws ClientExceptionInterface If the remote request fails.
*/
public function createCalendarObject(FederatedCalendarEntity $calendar, string $name, string $data): string {
$credentials = $this->getCredentials($calendar);
$objectUrl = $this->prepareUri($credentials['remoteUrl'], $name);
return $this->requestPut(
$objectUrl,
$credentials['username'],
$credentials['token'],
$data,
'text/calendar; charset=utf-8'
);
}
/**
* Update a calendar object on the remote server.
*
* @throws ClientExceptionInterface If the remote request fails.
*/
public function updateCalendarObject(FederatedCalendarEntity $calendar, string $name, string $data): string {
$credentials = $this->getCredentials($calendar);
$objectUrl = $this->prepareUri($credentials['remoteUrl'], $name);
return $this->requestPut(
$objectUrl,
$credentials['username'],
$credentials['token'],
$data,
'text/calendar; charset=utf-8'
);
}
/**
* Delete a calendar object on the remote server.
*
* @throws ClientExceptionInterface If the remote request fails.
*/
public function deleteCalendarObject(FederatedCalendarEntity $calendar, string $name): void {
$credentials = $this->getCredentials($calendar);
$objectUrl = $this->prepareUri($credentials['remoteUrl'], $name);
$this->requestDelete($objectUrl, $credentials['username'], $credentials['token']);
return $syncResponse->getDownloadedEvents();
}
}
+1 -9
View File
@@ -12,7 +12,6 @@ use OCA\DAV\CalDAV\Calendar;
use OCA\DAV\CalDAV\CalendarHome;
use OCA\DAV\CalDAV\CalendarObject;
use OCA\DAV\CalDAV\DefaultCalendarValidator;
use OCA\DAV\CalDAV\Federation\FederatedCalendar;
use OCA\DAV\CalDAV\TipBroker;
use OCP\IConfig;
use Psr\Log\LoggerInterface;
@@ -174,15 +173,8 @@ class Plugin extends \Sabre\CalDAV\Schedule\Plugin {
return;
}
/** @var Calendar&ICalendar $calendarNode */
/** @var Calendar $calendarNode */
$calendarNode = $this->server->tree->getNodeForPath($calendarPath);
// abort if calendar is federated
if ($calendarNode instanceof FederatedCalendar) {
$this->logger->debug('Not processing scheduling for federated calendar at path: ' . $calendarPath);
return;
}
// extract addresses for owner
$addresses = $this->getAddressesForPrincipal($calendarNode->getOwner());
// determine if request is from a sharee
@@ -8,6 +8,7 @@ declare(strict_types=1);
*/
namespace OCA\DAV\CardDAV\Notification;
use InvalidArgumentException;
use OCA\DAV\AppInfo\Application;
use OCP\IL10N;
use OCP\L10N\IFactory;
@@ -41,7 +42,7 @@ class Notifier implements INotifier {
*/
public function prepare(INotification $notification, string $languageCode): INotification {
if ($notification->getApp() !== Application::APP_ID) {
throw new UnknownNotificationException();
throw new InvalidArgumentException();
}
$l = $this->l10nFactory->get(Application::APP_ID, $languageCode);
+2 -3
View File
@@ -482,10 +482,9 @@ class File extends Node implements IFile {
// comparing current file size with the one in DB
// if different, fix DB and refresh cache.
$fsSize = $this->fileView->filesize($this->getPath());
if ($this->getSize() !== $fsSize) {
if ($this->getSize() !== $this->fileView->filesize($this->getPath())) {
$logger = Server::get(LoggerInterface::class);
$logger->warning('fixing cached size of file id=' . $this->getId() . ', cached size was ' . $this->getSize() . ', but the filesystem reported a size of ' . $fsSize);
$logger->warning('fixing cached size of file id=' . $this->getId());
$this->getFileInfo()->getStorage()->getUpdater()->update($this->getFileInfo()->getInternalPath());
$this->refreshInfo();
-66
View File
@@ -191,70 +191,4 @@ abstract class ASyncService {
rtrim($responseUri, '/'),
);
}
/**
* Push data to the remote server via HTTP PUT.
* Used for creating or updating CalDAV/CardDAV objects.
*
* @param string $url The absolute URL to PUT to
* @param string $username The username for authentication
* @param string $token The authentication token/password
* @param string $data The data to upload
* @param string $contentType The Content-Type header (e.g., 'text/calendar' or 'text/vcard')
*
* @return string The ETag returned by the server
*/
protected function requestPut(
string $url,
string $username,
string $token,
string $data,
string $contentType = 'text/calendar; charset=utf-8',
): string {
$client = $this->getClient();
$options = [
'auth' => [$username, $token],
'body' => $data,
'headers' => [
'Content-Type' => $contentType,
],
'verify' => !$this->config->getSystemValue(
'sharing.federation.allowSelfSignedCertificates',
false,
),
];
$response = $client->put($url, $options);
// Extract and return the ETag from the response
$etag = $response->getHeader('ETag');
return $etag;
}
/**
* Delete a resource from the remote server via HTTP DELETE.
* Used for deleting CalDAV/CardDAV objects.
*
* @param string $url The absolute URL to DELETE
* @param string $username The username for authentication
* @param string $token The authentication token/password
*/
protected function requestDelete(
string $url,
string $username,
string $token,
): void {
$client = $this->getClient();
$options = [
'auth' => [$username, $token],
'verify' => !$this->config->getSystemValue(
'sharing.federation.allowSelfSignedCertificates',
false,
),
];
$client->delete($url, $options);
}
}
@@ -92,12 +92,11 @@ class CalendarFederationProviderTest extends TestCase {
->willReturn(true);
$this->federatedCalendarMapper->expects(self::once())
->method('findByUri')
->method('deleteByUri')
->with(
'principals/users/sharee1',
'ae4b8ab904076fff2b955ea21b1a0d92',
)
->willReturn(null);
);
$this->federatedCalendarMapper->expects(self::once())
->method('insert')
@@ -124,68 +123,6 @@ class CalendarFederationProviderTest extends TestCase {
$this->assertEquals(10, $this->calendarFederationProvider->shareReceived($share));
}
public function testShareReceivedWithExistingCalendar(): void {
$share = $this->createMock(ICloudFederationShare::class);
$share->method('getShareType')
->willReturn('user');
$share->method('getProtocol')
->willReturn([
'version' => 'v1',
'url' => 'https://nextcloud.remote/remote.php/dav/remote-calendars/abcdef123/cal1_shared_by_user1',
'displayName' => 'Calendar 1',
'color' => '#ff0000',
'access' => 3,
'components' => 'VEVENT,VTODO',
]);
$share->method('getShareWith')
->willReturn('sharee1');
$share->method('getShareSecret')
->willReturn('new-token');
$share->method('getSharedBy')
->willReturn('user1@nextcloud.remote');
$share->method('getSharedByDisplayName')
->willReturn('User 1');
$this->calendarFederationConfig->expects(self::once())
->method('isFederationEnabled')
->willReturn(true);
$existingCalendar = new FederatedCalendarEntity();
$existingCalendar->setId(10);
$existingCalendar->setPrincipaluri('principals/users/sharee1');
$existingCalendar->setUri('ae4b8ab904076fff2b955ea21b1a0d92');
$existingCalendar->setRemoteUrl('https://nextcloud.remote/remote.php/dav/remote-calendars/abcdef123/cal1_shared_by_user1');
$existingCalendar->setToken('old-token');
$existingCalendar->setPermissions(1);
$existingCalendar->setComponents('VEVENT');
$this->federatedCalendarMapper->expects(self::once())
->method('findByUri')
->with(
'principals/users/sharee1',
'ae4b8ab904076fff2b955ea21b1a0d92',
)
->willReturn($existingCalendar);
$this->federatedCalendarMapper->expects(self::never())
->method('insert');
$this->federatedCalendarMapper->expects(self::once())
->method('update')
->willReturnCallback(function (FederatedCalendarEntity $calendar) {
$this->assertEquals('new-token', $calendar->getToken());
$this->assertEquals(1, $calendar->getPermissions());
$this->assertEquals('VEVENT,VTODO', $calendar->getComponents());
return $calendar;
});
$this->jobList->expects(self::once())
->method('add')
->with(FederatedCalendarSyncJob::class, ['id' => 10]);
$this->assertEquals(10, $this->calendarFederationProvider->shareReceived($share));
}
public function testShareReceivedWithInvalidProtocolVersion(): void {
$share = $this->createMock(ICloudFederationShare::class);
$share->method('getShareType')
@@ -333,65 +270,6 @@ class CalendarFederationProviderTest extends TestCase {
$this->calendarFederationProvider->shareReceived($share);
}
public function testShareReceivedWithReadWriteAccess(): void {
$share = $this->createMock(ICloudFederationShare::class);
$share->method('getShareType')
->willReturn('user');
$share->method('getProtocol')
->willReturn([
'version' => 'v1',
'url' => 'https://nextcloud.remote/remote.php/dav/remote-calendars/abcdef123/cal1_shared_by_user1',
'displayName' => 'Calendar 1',
'color' => '#ff0000',
'access' => 2, // Backend::ACCESS_READ_WRITE
'components' => 'VEVENT,VTODO',
]);
$share->method('getShareWith')
->willReturn('sharee1');
$share->method('getShareSecret')
->willReturn('token');
$share->method('getSharedBy')
->willReturn('user1@nextcloud.remote');
$share->method('getSharedByDisplayName')
->willReturn('User 1');
$this->calendarFederationConfig->expects(self::once())
->method('isFederationEnabled')
->willReturn(true);
$this->federatedCalendarMapper->expects(self::once())
->method('findByUri')
->with(
'principals/users/sharee1',
'ae4b8ab904076fff2b955ea21b1a0d92',
)
->willReturn(null);
$this->federatedCalendarMapper->expects(self::once())
->method('insert')
->willReturnCallback(function (FederatedCalendarEntity $calendar) {
$this->assertEquals('principals/users/sharee1', $calendar->getPrincipaluri());
$this->assertEquals('ae4b8ab904076fff2b955ea21b1a0d92', $calendar->getUri());
$this->assertEquals('https://nextcloud.remote/remote.php/dav/remote-calendars/abcdef123/cal1_shared_by_user1', $calendar->getRemoteUrl());
$this->assertEquals('Calendar 1', $calendar->getDisplayName());
$this->assertEquals('#ff0000', $calendar->getColor());
$this->assertEquals('token', $calendar->getToken());
$this->assertEquals('user1@nextcloud.remote', $calendar->getSharedBy());
$this->assertEquals('User 1', $calendar->getSharedByDisplayName());
$this->assertEquals(15, $calendar->getPermissions()); // READ | CREATE | UPDATE | DELETE
$this->assertEquals('VEVENT,VTODO', $calendar->getComponents());
$calendar->setId(10);
return $calendar;
});
$this->jobList->expects(self::once())
->method('add')
->with(FederatedCalendarSyncJob::class, ['id' => 10]);
$this->assertEquals(10, $this->calendarFederationProvider->shareReceived($share));
}
public function testShareReceivedWithUnsupportedAccess(): void {
$share = $this->createMock(ICloudFederationShare::class);
$share->method('getShareType')
@@ -402,7 +280,7 @@ class CalendarFederationProviderTest extends TestCase {
'url' => 'https://nextcloud.remote/remote.php/dav/remote-calendars/abcdef123/cal1_shared_by_user1',
'displayName' => 'Calendar 1',
'color' => '#ff0000',
'access' => 999, // Invalid access value
'access' => 2, // Backend::ACCESS_READ_WRITE
'components' => 'VEVENT,VTODO',
]);
$share->method('getShareWith')
@@ -9,17 +9,13 @@ declare(strict_types=1);
namespace OCA\DAV\Tests\unit\CalDAV\Federation;
use OCA\DAV\CalDAV\CalDavBackend;
use OCA\DAV\CalDAV\Federation\FederatedCalendarEntity;
use OCA\DAV\CalDAV\Federation\FederatedCalendarMapper;
use OCA\DAV\CalDAV\Federation\FederatedCalendarSyncService;
use OCA\DAV\CalDAV\SyncService as CalDavSyncService;
use OCA\DAV\CalDAV\SyncServiceResult;
use OCP\Federation\ICloudId;
use OCP\Federation\ICloudIdManager;
use OCP\Http\Client\IClient;
use OCP\Http\Client\IClientService;
use OCP\Http\Client\IResponse;
use OCP\IConfig;
use OCP\IDBConnection;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Log\LoggerInterface;
@@ -30,30 +26,21 @@ class FederatedCalendarSyncServiceTest extends TestCase {
private FederatedCalendarMapper&MockObject $federatedCalendarMapper;
private LoggerInterface&MockObject $logger;
private CalDavBackend&MockObject $backend;
private IDBConnection&MockObject $dbConnection;
private CalDavSyncService&MockObject $calDavSyncService;
private ICloudIdManager&MockObject $cloudIdManager;
private IClientService&MockObject $clientService;
private IConfig&MockObject $config;
protected function setUp(): void {
parent::setUp();
$this->federatedCalendarMapper = $this->createMock(FederatedCalendarMapper::class);
$this->logger = $this->createMock(LoggerInterface::class);
$this->backend = $this->createMock(CalDavBackend::class);
$this->dbConnection = $this->createMock(IDBConnection::class);
$this->calDavSyncService = $this->createMock(CalDavSyncService::class);
$this->cloudIdManager = $this->createMock(ICloudIdManager::class);
$this->clientService = $this->createMock(IClientService::class);
$this->config = $this->createMock(IConfig::class);
$this->federatedCalendarSyncService = new FederatedCalendarSyncService(
$this->clientService,
$this->config,
$this->federatedCalendarMapper,
$this->logger,
$this->backend,
$this->dbConnection,
$this->calDavSyncService,
$this->cloudIdManager,
);
}
@@ -74,24 +61,16 @@ class FederatedCalendarSyncServiceTest extends TestCase {
->with('user1')
->willReturn($cloudId);
// Mock HTTP client for sync report
$client = $this->createMock(IClient::class);
$response = $this->createMock(IResponse::class);
$response->method('getBody')
->willReturn('<?xml version="1.0"?><d:multistatus xmlns:d="DAV:"><d:sync-token>http://sabre.io/ns/sync/101</d:sync-token></d:multistatus>');
$client->expects(self::once())
->method('request')
->with('REPORT', 'https://remote.tld/remote.php/dav/remote-calendars/abcdef123/cal1_shared_by_user2', self::anything())
->willReturn($response);
$this->clientService->method('newClient')
->willReturn($client);
$this->config->method('getSystemValueInt')
->willReturn(30);
$this->config->method('getSystemValue')
->willReturn(false);
$this->calDavSyncService->expects(self::once())
->method('syncRemoteCalendar')
->with(
'https://remote.tld/remote.php/dav/remote-calendars/abcdef123/cal1_shared_by_user2',
'dXNlcjFAbmV4dGNsb3VkLnRlc3Rpbmc=',
'token',
'http://sabre.io/ns/sync/100',
$calendar,
)
->willReturn(new SyncServiceResult('http://sabre.io/ns/sync/101', 10));
$this->federatedCalendarMapper->expects(self::once())
->method('updateSyncTokenAndTime')
@@ -99,7 +78,7 @@ class FederatedCalendarSyncServiceTest extends TestCase {
$this->federatedCalendarMapper->expects(self::never())
->method('updateSyncTime');
$this->assertEquals(0, $this->federatedCalendarSyncService->syncOne($calendar));
$this->assertEquals(10, $this->federatedCalendarSyncService->syncOne($calendar));
}
public function testSyncOneUnchanged(): void {
@@ -118,24 +97,16 @@ class FederatedCalendarSyncServiceTest extends TestCase {
->with('user1')
->willReturn($cloudId);
// Mock HTTP client for sync report
$client = $this->createMock(IClient::class);
$response = $this->createMock(IResponse::class);
$response->method('getBody')
->willReturn('<?xml version="1.0"?><d:multistatus xmlns:d="DAV:"><d:sync-token>http://sabre.io/ns/sync/100</d:sync-token></d:multistatus>');
$client->expects(self::once())
->method('request')
->with('REPORT', 'https://remote.tld/remote.php/dav/remote-calendars/abcdef123/cal1_shared_by_user2', self::anything())
->willReturn($response);
$this->clientService->method('newClient')
->willReturn($client);
$this->config->method('getSystemValueInt')
->willReturn(30);
$this->config->method('getSystemValue')
->willReturn(false);
$this->calDavSyncService->expects(self::once())
->method('syncRemoteCalendar')
->with(
'https://remote.tld/remote.php/dav/remote-calendars/abcdef123/cal1_shared_by_user2',
'dXNlcjFAbmV4dGNsb3VkLnRlc3Rpbmc=',
'token',
'http://sabre.io/ns/sync/100',
$calendar,
)
->willReturn(new SyncServiceResult('http://sabre.io/ns/sync/100', 0));
$this->federatedCalendarMapper->expects(self::never())
->method('updateSyncTokenAndTime');
@@ -172,24 +143,16 @@ class FederatedCalendarSyncServiceTest extends TestCase {
->with('user1')
->willReturn($cloudId);
// Mock HTTP client for sync report with unexpected token format
$client = $this->createMock(IClient::class);
$response = $this->createMock(IResponse::class);
$response->method('getBody')
->willReturn('<?xml version="1.0"?><d:multistatus xmlns:d="DAV:"><d:sync-token>' . $syncToken . '</d:sync-token></d:multistatus>');
$client->expects(self::once())
->method('request')
->with('REPORT', 'https://remote.tld/remote.php/dav/remote-calendars/abcdef123/cal1_shared_by_user2', self::anything())
->willReturn($response);
$this->clientService->method('newClient')
->willReturn($client);
$this->config->method('getSystemValueInt')
->willReturn(30);
$this->config->method('getSystemValue')
->willReturn(false);
$this->calDavSyncService->expects(self::once())
->method('syncRemoteCalendar')
->with(
'https://remote.tld/remote.php/dav/remote-calendars/abcdef123/cal1_shared_by_user2',
'dXNlcjFAbmV4dGNsb3VkLnRlc3Rpbmc=',
'token',
'http://sabre.io/ns/sync/100',
$calendar,
)
->willReturn(new SyncServiceResult($syncToken, 10));
$this->federatedCalendarMapper->expects(self::never())
->method('updateSyncTokenAndTime');
@@ -1,404 +0,0 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\DAV\Tests\unit\CalDAV\Federation;
use OCA\DAV\CalDAV\CalDavBackend;
use OCA\DAV\CalDAV\Federation\FederatedCalendar;
use OCA\DAV\CalDAV\Federation\FederatedCalendarEntity;
use OCA\DAV\CalDAV\Federation\FederatedCalendarMapper;
use OCA\DAV\CalDAV\Federation\FederatedCalendarObject;
use OCA\DAV\CalDAV\Federation\FederatedCalendarSyncService;
use OCP\Constants;
use PHPUnit\Framework\MockObject\MockObject;
use Sabre\DAV\Exception\MethodNotAllowed;
use Sabre\DAV\Exception\NotFound;
use Sabre\DAV\PropPatch;
use Test\TestCase;
class FederatedCalendarTest extends TestCase {
private FederatedCalendar $federatedCalendar;
private FederatedCalendarMapper&MockObject $federatedCalendarMapper;
private FederatedCalendarSyncService&MockObject $federatedCalendarService;
private CalDavBackend&MockObject $caldavBackend;
private FederatedCalendarEntity $federationInfo;
protected function setUp(): void {
parent::setUp();
$this->federatedCalendarMapper = $this->createMock(FederatedCalendarMapper::class);
$this->federatedCalendarService = $this->createMock(FederatedCalendarSyncService::class);
$this->caldavBackend = $this->createMock(CalDavBackend::class);
$this->federationInfo = new FederatedCalendarEntity();
$this->federationInfo->setId(10);
$this->federationInfo->setPrincipaluri('principals/users/user1');
$this->federationInfo->setUri('calendar-uri');
$this->federationInfo->setDisplayName('Federated Calendar');
$this->federationInfo->setColor('#ff0000');
$this->federationInfo->setSharedBy('user2@nextcloud.remote');
$this->federationInfo->setSharedByDisplayName('User 2');
$this->federationInfo->setPermissions(Constants::PERMISSION_READ);
$this->federationInfo->setLastSync(1234567890);
$this->federatedCalendarMapper->method('findByUri')
->with('principals/users/user1', 'calendar-uri')
->willReturn($this->federationInfo);
$calendarInfo = [
'principaluri' => 'principals/users/user1',
'id' => 10,
'uri' => 'calendar-uri',
];
$this->federatedCalendar = new FederatedCalendar(
$this->federatedCalendarMapper,
$this->federatedCalendarService,
$this->caldavBackend,
$calendarInfo,
);
}
public function testGetResourceId(): void {
$this->assertEquals(10, $this->federatedCalendar->getResourceId());
}
public function testGetName(): void {
$this->assertEquals('calendar-uri', $this->federatedCalendar->getName());
}
public function testSetName(): void {
$this->expectException(MethodNotAllowed::class);
$this->expectExceptionMessage('Renaming federated calendars is not allowed');
$this->federatedCalendar->setName('new-name');
}
public function testGetPrincipalURI(): void {
$this->assertEquals('principals/users/user1', $this->federatedCalendar->getPrincipalURI());
}
public function testGetOwner(): void {
$expected = 'principals/remote-users/' . base64_encode('user2@nextcloud.remote');
$this->assertEquals($expected, $this->federatedCalendar->getOwner());
}
public function testGetGroup(): void {
$this->assertNull($this->federatedCalendar->getGroup());
}
public function testGetACLWithReadOnlyPermissions(): void {
$this->federationInfo->setPermissions(Constants::PERMISSION_READ);
$acl = $this->federatedCalendar->getACL();
$this->assertCount(3, $acl);
// Check basic read permissions
$this->assertEquals('{DAV:}read', $acl[0]['privilege']);
$this->assertTrue($acl[0]['protected']);
$this->assertEquals('{DAV:}read-acl', $acl[1]['privilege']);
$this->assertTrue($acl[1]['protected']);
$this->assertEquals('{DAV:}write-properties', $acl[2]['privilege']);
$this->assertTrue($acl[2]['protected']);
}
public function testGetACLWithCreatePermission(): void {
$this->federationInfo->setPermissions(Constants::PERMISSION_READ | Constants::PERMISSION_CREATE);
$acl = $this->federatedCalendar->getACL();
$this->assertCount(4, $acl);
// Check that create permission is added
$privileges = array_column($acl, 'privilege');
$this->assertContains('{DAV:}bind', $privileges);
}
public function testGetACLWithUpdatePermission(): void {
$this->federationInfo->setPermissions(Constants::PERMISSION_READ | Constants::PERMISSION_UPDATE);
$acl = $this->federatedCalendar->getACL();
$this->assertCount(4, $acl);
// Check that update permission is added (write-content, not write-properties which is already in base ACL)
$privileges = array_column($acl, 'privilege');
$this->assertContains('{DAV:}write-content', $privileges);
}
public function testGetACLWithDeletePermission(): void {
$this->federationInfo->setPermissions(Constants::PERMISSION_READ | Constants::PERMISSION_DELETE);
$acl = $this->federatedCalendar->getACL();
$this->assertCount(4, $acl);
// Check that delete permission is added
$privileges = array_column($acl, 'privilege');
$this->assertContains('{DAV:}unbind', $privileges);
}
public function testGetACLWithAllPermissions(): void {
$this->federationInfo->setPermissions(
Constants::PERMISSION_READ
| Constants::PERMISSION_CREATE
| Constants::PERMISSION_UPDATE
| Constants::PERMISSION_DELETE
);
$acl = $this->federatedCalendar->getACL();
$this->assertCount(6, $acl);
$privileges = array_column($acl, 'privilege');
$this->assertContains('{DAV:}read', $privileges);
$this->assertContains('{DAV:}bind', $privileges);
$this->assertContains('{DAV:}write-content', $privileges);
$this->assertContains('{DAV:}write-properties', $privileges);
$this->assertContains('{DAV:}unbind', $privileges);
}
public function testSetACL(): void {
$this->expectException(MethodNotAllowed::class);
$this->expectExceptionMessage('Changing ACLs on federated calendars is not allowed');
$this->federatedCalendar->setACL([]);
}
public function testGetSupportedPrivilegeSet(): void {
$this->assertNull($this->federatedCalendar->getSupportedPrivilegeSet());
}
public function testGetProperties(): void {
$properties = $this->federatedCalendar->getProperties([
'{DAV:}displayname',
'{http://apple.com/ns/ical/}calendar-color',
]);
$this->assertEquals('Federated Calendar', $properties['{DAV:}displayname']);
$this->assertEquals('#ff0000', $properties['{http://apple.com/ns/ical/}calendar-color']);
}
public function testPropPatchWithDisplayName(): void {
$propPatch = $this->createMock(PropPatch::class);
$propPatch->method('getMutations')
->willReturn([
'{DAV:}displayname' => 'New Calendar Name',
]);
$this->federatedCalendarMapper->expects(self::once())
->method('update')
->willReturnCallback(function (FederatedCalendarEntity $entity) {
$this->assertEquals('New Calendar Name', $entity->getDisplayName());
return $entity;
});
$propPatch->expects(self::once())
->method('setResultCode')
->with('{DAV:}displayname', 200);
$this->federatedCalendar->propPatch($propPatch);
}
public function testPropPatchWithColor(): void {
$propPatch = $this->createMock(PropPatch::class);
$propPatch->method('getMutations')
->willReturn([
'{http://apple.com/ns/ical/}calendar-color' => '#00ff00',
]);
$this->federatedCalendarMapper->expects(self::once())
->method('update')
->willReturnCallback(function (FederatedCalendarEntity $entity) {
$this->assertEquals('#00ff00', $entity->getColor());
return $entity;
});
$propPatch->expects(self::once())
->method('setResultCode')
->with('{http://apple.com/ns/ical/}calendar-color', 200);
$this->federatedCalendar->propPatch($propPatch);
}
public function testPropPatchWithNoMutations(): void {
$propPatch = $this->createMock(PropPatch::class);
$propPatch->method('getMutations')
->willReturn([]);
$this->federatedCalendarMapper->expects(self::never())
->method('update');
$propPatch->expects(self::never())
->method('handle');
$this->federatedCalendar->propPatch($propPatch);
}
public function testGetChildACL(): void {
$this->assertEquals($this->federatedCalendar->getACL(), $this->federatedCalendar->getChildACL());
}
public function testGetLastModified(): void {
$this->assertEquals(1234567890, $this->federatedCalendar->getLastModified());
}
public function testDelete(): void {
$this->federatedCalendarMapper->expects(self::once())
->method('deleteById')
->with(10);
$this->federatedCalendar->delete();
}
public function testCreateDirectory(): void {
$this->expectException(MethodNotAllowed::class);
$this->expectExceptionMessage('Creating nested collection is not allowed');
$this->federatedCalendar->createDirectory('test');
}
public function testCalendarQuery(): void {
$filters = ['comp-filter' => ['name' => 'VEVENT']];
$expectedUris = ['event1.ics', 'event2.ics'];
$this->caldavBackend->expects(self::once())
->method('calendarQuery')
->with(10, $filters, 2) // 2 is CALENDAR_TYPE_FEDERATED
->willReturn($expectedUris);
$result = $this->federatedCalendar->calendarQuery($filters);
$this->assertEquals($expectedUris, $result);
}
public function testGetChild(): void {
$objectData = [
'id' => 1,
'uri' => 'event1.ics',
'calendardata' => 'BEGIN:VCALENDAR...',
];
$this->caldavBackend->expects(self::once())
->method('getCalendarObject')
->with(10, 'event1.ics', 2) // 2 is CALENDAR_TYPE_FEDERATED
->willReturn($objectData);
$child = $this->federatedCalendar->getChild('event1.ics');
$this->assertInstanceOf(FederatedCalendarObject::class, $child);
}
public function testGetChildNotFound(): void {
$this->caldavBackend->expects(self::once())
->method('getCalendarObject')
->with(10, 'nonexistent.ics', 2)
->willReturn(null);
$this->expectException(NotFound::class);
$this->federatedCalendar->getChild('nonexistent.ics');
}
public function testGetChildren(): void {
$objects = [
['id' => 1, 'uri' => 'event1.ics', 'calendardata' => 'BEGIN:VCALENDAR...'],
['id' => 2, 'uri' => 'event2.ics', 'calendardata' => 'BEGIN:VCALENDAR...'],
];
$this->caldavBackend->expects(self::once())
->method('getCalendarObjects')
->with(10, 2) // 2 is CALENDAR_TYPE_FEDERATED
->willReturn($objects);
$children = $this->federatedCalendar->getChildren();
$this->assertCount(2, $children);
$this->assertInstanceOf(FederatedCalendarObject::class, $children[0]);
$this->assertInstanceOf(FederatedCalendarObject::class, $children[1]);
}
public function testGetMultipleChildren(): void {
$paths = ['event1.ics', 'event2.ics'];
$objects = [
['id' => 1, 'uri' => 'event1.ics', 'calendardata' => 'BEGIN:VCALENDAR...'],
['id' => 2, 'uri' => 'event2.ics', 'calendardata' => 'BEGIN:VCALENDAR...'],
];
$this->caldavBackend->expects(self::once())
->method('getMultipleCalendarObjects')
->with(10, $paths, 2) // 2 is CALENDAR_TYPE_FEDERATED
->willReturn($objects);
$children = $this->federatedCalendar->getMultipleChildren($paths);
$this->assertCount(2, $children);
$this->assertInstanceOf(FederatedCalendarObject::class, $children[0]);
$this->assertInstanceOf(FederatedCalendarObject::class, $children[1]);
}
public function testChildExists(): void {
$this->caldavBackend->expects(self::once())
->method('getCalendarObject')
->with(10, 'event1.ics', 2)
->willReturn(['id' => 1, 'uri' => 'event1.ics']);
$result = $this->federatedCalendar->childExists('event1.ics');
$this->assertTrue($result);
}
public function testChildNotExists(): void {
$this->caldavBackend->expects(self::once())
->method('getCalendarObject')
->with(10, 'nonexistent.ics', 2)
->willReturn(null);
$result = $this->federatedCalendar->childExists('nonexistent.ics');
$this->assertFalse($result);
}
public function testCreateFile(): void {
$calendarData = 'BEGIN:VCALENDAR...END:VCALENDAR';
$remoteEtag = '"remote-etag-123"';
$localEtag = '"local-etag-456"';
$this->federatedCalendarService->expects(self::once())
->method('createCalendarObject')
->with($this->federationInfo, 'event1.ics', $calendarData)
->willReturn($remoteEtag);
$this->caldavBackend->expects(self::once())
->method('createCalendarObject')
->with(10, 'event1.ics', $calendarData, 2)
->willReturn($localEtag);
$result = $this->federatedCalendar->createFile('event1.ics', $calendarData);
$this->assertEquals($localEtag, $result);
}
public function testUpdateFile(): void {
$calendarData = 'BEGIN:VCALENDAR...UPDATED...END:VCALENDAR';
$remoteEtag = '"remote-etag-updated"';
$localEtag = '"local-etag-updated"';
$this->federatedCalendarService->expects(self::once())
->method('updateCalendarObject')
->with($this->federationInfo, 'event1.ics', $calendarData)
->willReturn($remoteEtag);
$this->caldavBackend->expects(self::once())
->method('updateCalendarObject')
->with(10, 'event1.ics', $calendarData, 2)
->willReturn($localEtag);
$result = $this->federatedCalendar->updateFile('event1.ics', $calendarData);
$this->assertEquals($localEtag, $result);
}
public function testDeleteFile(): void {
$this->federatedCalendarService->expects(self::once())
->method('deleteCalendarObject')
->with($this->federationInfo, 'event1.ics');
$this->caldavBackend->expects(self::once())
->method('deleteCalendarObject')
->with(10, 'event1.ics', 2);
$this->federatedCalendar->deleteFile('event1.ics');
}
}
+1 -1
View File
@@ -545,7 +545,7 @@ class Crypt {
$options,
$iv);
if ($plainContent !== false) {
if ($plainContent) {
return $plainContent;
} else {
throw new DecryptionFailedException('Encryption library: Decryption (symmetric) of content failed: ' . openssl_error_string());
+2 -2
View File
@@ -42,8 +42,8 @@ OC.L10N.register(
"The lookup server is only available for global scale." : "Сервер пошуку доступний тільки для глобального масштабу.",
"Search global and public address book for people" : "Шукати користувачів у глобальній та публічній адресних книгах",
"Allow people to publish their data to a global and public address book" : "Дозволити користувачам розміщувати власні дані у глобальній публічній адресній книзі",
"Trusted federation" : "Довірені об'єднані хмари",
"Automatically accept shares from trusted federated accounts and groups by default" : "Стандартно автоматично приймати пропозиції спільного доступу від надійних облікових записів та груп об'єднаних хмар",
"Trusted federation" : "Довірена федерація",
"Automatically accept shares from trusted federated accounts and groups by default" : "Типово автоматично приймати пропозиції спільного доступу від надійних облікових записів та груп об'єднаних хмар",
"Share with me through my #Nextcloud Federated Cloud ID, see {url}" : "Поділітися зі мною через мій #Nextcloud Federated Cloud ID, див. {url}",
"Share with me through my #Nextcloud Federated Cloud ID" : "Поділітися зі мною через мій #Nextcloud Federated Cloud ID",
"Share with me via Nextcloud" : "Поділіться зі мною у Nextcloud",
+2 -2
View File
@@ -40,8 +40,8 @@
"The lookup server is only available for global scale." : "Сервер пошуку доступний тільки для глобального масштабу.",
"Search global and public address book for people" : "Шукати користувачів у глобальній та публічній адресних книгах",
"Allow people to publish their data to a global and public address book" : "Дозволити користувачам розміщувати власні дані у глобальній публічній адресній книзі",
"Trusted federation" : "Довірені об'єднані хмари",
"Automatically accept shares from trusted federated accounts and groups by default" : "Стандартно автоматично приймати пропозиції спільного доступу від надійних облікових записів та груп об'єднаних хмар",
"Trusted federation" : "Довірена федерація",
"Automatically accept shares from trusted federated accounts and groups by default" : "Типово автоматично приймати пропозиції спільного доступу від надійних облікових записів та груп об'єднаних хмар",
"Share with me through my #Nextcloud Federated Cloud ID, see {url}" : "Поділітися зі мною через мій #Nextcloud Federated Cloud ID, див. {url}",
"Share with me through my #Nextcloud Federated Cloud ID" : "Поділітися зі мною через мій #Nextcloud Federated Cloud ID",
"Share with me via Nextcloud" : "Поділіться зі мною у Nextcloud",
-11
View File
@@ -11,18 +11,7 @@ OC.L10N.register(
"Federation" : "Об'єднання",
"Federation allows you to connect with other trusted servers to exchange the account directory." : "Об'єднання хмар дозволяє з'єднуватися з іншими довіреними серверами й обмінюватися обліковими даними користувачів.",
"Federation allows you to connect with other trusted servers to exchange the account directory. For example this will be used to auto-complete external accounts for federated sharing." : "Об'єднання хмар дозволяє з'єднуватися з іншими довіреними серверами й обмінюватися обліковими даними користувачів. Так, це може бути корисно для автоматичної підстановки зовнішніх користувачів під час надання у спільний доступ ресурсів об'єднаних хмар.",
"Could not add trusted server. Please try again later." : "Не вдалося додати довірений сервер. Спробуйте ще раз пізніше.",
"Add trusted server" : "Додати довірений сервер",
"Server url" : " Посилання на сервер",
"Add" : "Додати",
"Server ok" : "Сервер ОК",
"User list was exchanged at least once successfully with the remote server." : "Принаймні один раз відбувся обмін списком користувачів з віддаленим сервером.",
"Server pending" : "Очікування сервера",
"Waiting for shared secret or initial user list exchange." : "Очікування парольної фрази спільного доступу або початкового обміну списком користувачів",
"Server access revoked" : "Відкликано доступ для сервера",
"Server failure" : "Помилка на стороні сервера",
"Connection to the remote server failed or the remote server is misconfigured." : "Не вдалося встановити з'єднання з віддаленим сервером, або віддалений сервер має помилки з налаштуванням",
"Failed to delete trusted server. Please try again later." : "Не вдалося вилучити довірений сервер. Спробуйте ще раз пізніше.",
"Delete" : "Видалити",
"Federation allows you to connect with other trusted servers to exchange the account directory. For example this will be used to auto-complete external accounts for federated sharing. It is not necessary to add a server as trusted server in order to create a federated share." : "Об'єднання хмар дозволяє з'єднуватися з іншими довіреними серверами й обмінюватися обліковими даними користувачів. Так, це може бути корисно для автоматичної підстановки зовнішніх користувачів під час надання у спільний доступ ресурсів об'єднаних хмар. Необов'язково додавати сервер яко довірений для створення спільного ресурсу між об'єднаними хмарами.",
"Each server must validate the other. This process may require a few cron cycles." : "Кожен сервер має підтвердити один одного. Цей процес може вимагати кількох циклів виконання cron.",
-11
View File
@@ -9,18 +9,7 @@
"Federation" : "Об'єднання",
"Federation allows you to connect with other trusted servers to exchange the account directory." : "Об'єднання хмар дозволяє з'єднуватися з іншими довіреними серверами й обмінюватися обліковими даними користувачів.",
"Federation allows you to connect with other trusted servers to exchange the account directory. For example this will be used to auto-complete external accounts for federated sharing." : "Об'єднання хмар дозволяє з'єднуватися з іншими довіреними серверами й обмінюватися обліковими даними користувачів. Так, це може бути корисно для автоматичної підстановки зовнішніх користувачів під час надання у спільний доступ ресурсів об'єднаних хмар.",
"Could not add trusted server. Please try again later." : "Не вдалося додати довірений сервер. Спробуйте ще раз пізніше.",
"Add trusted server" : "Додати довірений сервер",
"Server url" : " Посилання на сервер",
"Add" : "Додати",
"Server ok" : "Сервер ОК",
"User list was exchanged at least once successfully with the remote server." : "Принаймні один раз відбувся обмін списком користувачів з віддаленим сервером.",
"Server pending" : "Очікування сервера",
"Waiting for shared secret or initial user list exchange." : "Очікування парольної фрази спільного доступу або початкового обміну списком користувачів",
"Server access revoked" : "Відкликано доступ для сервера",
"Server failure" : "Помилка на стороні сервера",
"Connection to the remote server failed or the remote server is misconfigured." : "Не вдалося встановити з'єднання з віддаленим сервером, або віддалений сервер має помилки з налаштуванням",
"Failed to delete trusted server. Please try again later." : "Не вдалося вилучити довірений сервер. Спробуйте ще раз пізніше.",
"Delete" : "Видалити",
"Federation allows you to connect with other trusted servers to exchange the account directory. For example this will be used to auto-complete external accounts for federated sharing. It is not necessary to add a server as trusted server in order to create a federated share." : "Об'єднання хмар дозволяє з'єднуватися з іншими довіреними серверами й обмінюватися обліковими даними користувачів. Так, це може бути корисно для автоматичної підстановки зовнішніх користувачів під час надання у спільний доступ ресурсів об'єднаних хмар. Необов'язково додавати сервер яко довірений для створення спільного ресурсу між об'єднаними хмарами.",
"Each server must validate the other. This process may require a few cron cycles." : "Кожен сервер має підтвердити один одного. Цей процес може вимагати кількох циклів виконання cron.",
+1 -1
View File
@@ -79,7 +79,7 @@ OC.L10N.register(
"Go to the \"{dir}\" directory" : "Vaia ao directorio «{dir}».",
"Current directory path" : "Ruta do directorio actual",
"Share" : "Compartir",
"Reload content" : "Volver cargar o contido",
"Reload content" : "Recargar o contido",
"Your have used your space quota and cannot upload files anymore" : "Vde. usou a súa cota de espazo e xa non pode enviar ningún ficheiro más",
"You do not have permission to upload or create files here." : "Non ten permiso para enviar ou crear ficheiros aquí.",
"Drag and drop files here to upload" : "Arrastre e solte os ficheiros aquí para envialos",
+1 -1
View File
@@ -77,7 +77,7 @@
"Go to the \"{dir}\" directory" : "Vaia ao directorio «{dir}».",
"Current directory path" : "Ruta do directorio actual",
"Share" : "Compartir",
"Reload content" : "Volver cargar o contido",
"Reload content" : "Recargar o contido",
"Your have used your space quota and cannot upload files anymore" : "Vde. usou a súa cota de espazo e xa non pode enviar ningún ficheiro más",
"You do not have permission to upload or create files here." : "Non ten permiso para enviar ou crear ficheiros aquí.",
"Drag and drop files here to upload" : "Arrastre e solte os ficheiros aquí para envialos",
-10
View File
@@ -79,7 +79,6 @@ OC.L10N.register(
"Go to the \"{dir}\" directory" : "\"{dir}\" klasörüne git",
"Current directory path" : "Geçerli klasör yolu",
"Share" : "Paylaş",
"Reload content" : "İçeriği yeniden yükle",
"Your have used your space quota and cannot upload files anymore" : "Depolama alanınızın tümünü kullandığınız için başka dosya yüklemezsiniz",
"You do not have permission to upload or create files here." : "Buraya dosya yükleme ya da ekleme izniniz yok.",
"Drag and drop files here to upload" : "Yüklemek istediğiniz dosyaları sürükleyip buraya bırakın",
@@ -110,7 +109,6 @@ OC.L10N.register(
"Last 30 days" : "Önceki 30 gün",
"This year ({year})" : "Bu yıl ({year})",
"Last year ({year})" : "Önceki yıl ({year})",
"Custom range" : "Özel aralık",
"Custom date range" : "Özel tarih aralığı",
"Search everywhere" : "Her yerde ara",
"Documents" : "Belgeler",
@@ -122,7 +120,6 @@ OC.L10N.register(
"Images" : "Görseller",
"Videos" : "Görüntüler",
"Filters" : "Süzgeçler",
"Back to filters" : "Süzgeçlere dön",
"Appearance" : "Görünüm",
"Show hidden files" : "Gizli dosyaları görüntüle",
"Show file type column" : "Dosya türü sütunu görüntülensin",
@@ -234,9 +231,6 @@ OC.L10N.register(
"Removing the file extension \"{old}\" may render the file unreadable." : "\"{old}\" dosya uzantısının kaldırılması dosyayı okunamaz yapabilir.",
"Adding the file extension \"{new}\" may render the file unreadable." : "\"{new}\" dosya uzantısının eklenmesi dosyayı okunamaz yapabilir.",
"Do not show this dialog again." : "Bu ileti bir daha görüntülenmesin.",
"Rename file to hidden" : "Gizlemek için dosyayı yeniden adlandırın",
"Prefixing a filename with a dot may render the file hidden." : "Dosya adının başına nokta koymak onu görünümlerde gizler.",
"Are you sure you want to rename the file to \"{filename}\"?" : "Dosyanın adını \"{filename}\" olarak değiştirmek istediğinize emin misiniz?",
"Cancel" : "İptal",
"Rename" : "Yeniden adlandır",
"Select file or folder to link to" : "Bağlantı verilecek dosya ya da klasörü seçin",
@@ -251,7 +245,6 @@ OC.L10N.register(
"Error during upload: {message}" : "Yükleme sırasında sorun çıktı: {message}",
"Error during upload, status code {status}" : "Yüklenirken sorun çıktı, durum kodu {status}",
"Unknown error during upload" : "Yükleme sırasında bilinmeyen bir sorun çıktı",
"File list is reloading" : "Dosya listesi yeniden yükleniyor",
"Loading current folder" : "Geçerli klasör yükleniyor",
"Retry" : "Yeniden dene",
"No files in here" : "Burada herhangi bir dosya yok",
@@ -319,9 +312,7 @@ OC.L10N.register(
"The files are locked" : "Dosyalar kilitli",
"The file does not exist anymore" : "Dosya artık yok",
"Moving \"{source}\" to \"{destination}\" …" : "\"{source}\", \"{destination}\" üzerine taşınıyor…",
"Moving {count} files to \"{destination}\" …" : "{count} dosya \"{destination}\" konumuna taşınıyor…",
"Copying \"{source}\" to \"{destination}\" …" : "\"{source}\", \"{destination}\" üzerine kopyalanıyor…",
"Copying {count} files to \"{destination}\" …" : "{count} dosya \"{destination}\" konumuna kopyalanıyor…",
"Choose destination" : "Hedefi seçin",
"Copy to {target}" : "{target} içine kopyala",
"Move to {target}" : "{target} içine taşı",
@@ -346,7 +337,6 @@ OC.L10N.register(
"Templates" : "Kalıplar",
"New template folder" : "Yeni kalıp klasörü",
"In folder" : "Klasörde",
"Pick folder to search in" : "Aranacak klasörü seçin",
"Search in all files" : "Tüm dosyalarda ara",
"Search in folder: {folder}" : "Şu klasörde ara: {folder}",
"One of the dropped files could not be processed" : "Bırakılan dosyalardan biri işlenemedi",
-10
View File
@@ -77,7 +77,6 @@
"Go to the \"{dir}\" directory" : "\"{dir}\" klasörüne git",
"Current directory path" : "Geçerli klasör yolu",
"Share" : "Paylaş",
"Reload content" : "İçeriği yeniden yükle",
"Your have used your space quota and cannot upload files anymore" : "Depolama alanınızın tümünü kullandığınız için başka dosya yüklemezsiniz",
"You do not have permission to upload or create files here." : "Buraya dosya yükleme ya da ekleme izniniz yok.",
"Drag and drop files here to upload" : "Yüklemek istediğiniz dosyaları sürükleyip buraya bırakın",
@@ -108,7 +107,6 @@
"Last 30 days" : "Önceki 30 gün",
"This year ({year})" : "Bu yıl ({year})",
"Last year ({year})" : "Önceki yıl ({year})",
"Custom range" : "Özel aralık",
"Custom date range" : "Özel tarih aralığı",
"Search everywhere" : "Her yerde ara",
"Documents" : "Belgeler",
@@ -120,7 +118,6 @@
"Images" : "Görseller",
"Videos" : "Görüntüler",
"Filters" : "Süzgeçler",
"Back to filters" : "Süzgeçlere dön",
"Appearance" : "Görünüm",
"Show hidden files" : "Gizli dosyaları görüntüle",
"Show file type column" : "Dosya türü sütunu görüntülensin",
@@ -232,9 +229,6 @@
"Removing the file extension \"{old}\" may render the file unreadable." : "\"{old}\" dosya uzantısının kaldırılması dosyayı okunamaz yapabilir.",
"Adding the file extension \"{new}\" may render the file unreadable." : "\"{new}\" dosya uzantısının eklenmesi dosyayı okunamaz yapabilir.",
"Do not show this dialog again." : "Bu ileti bir daha görüntülenmesin.",
"Rename file to hidden" : "Gizlemek için dosyayı yeniden adlandırın",
"Prefixing a filename with a dot may render the file hidden." : "Dosya adının başına nokta koymak onu görünümlerde gizler.",
"Are you sure you want to rename the file to \"{filename}\"?" : "Dosyanın adını \"{filename}\" olarak değiştirmek istediğinize emin misiniz?",
"Cancel" : "İptal",
"Rename" : "Yeniden adlandır",
"Select file or folder to link to" : "Bağlantı verilecek dosya ya da klasörü seçin",
@@ -249,7 +243,6 @@
"Error during upload: {message}" : "Yükleme sırasında sorun çıktı: {message}",
"Error during upload, status code {status}" : "Yüklenirken sorun çıktı, durum kodu {status}",
"Unknown error during upload" : "Yükleme sırasında bilinmeyen bir sorun çıktı",
"File list is reloading" : "Dosya listesi yeniden yükleniyor",
"Loading current folder" : "Geçerli klasör yükleniyor",
"Retry" : "Yeniden dene",
"No files in here" : "Burada herhangi bir dosya yok",
@@ -317,9 +310,7 @@
"The files are locked" : "Dosyalar kilitli",
"The file does not exist anymore" : "Dosya artık yok",
"Moving \"{source}\" to \"{destination}\" …" : "\"{source}\", \"{destination}\" üzerine taşınıyor…",
"Moving {count} files to \"{destination}\" …" : "{count} dosya \"{destination}\" konumuna taşınıyor…",
"Copying \"{source}\" to \"{destination}\" …" : "\"{source}\", \"{destination}\" üzerine kopyalanıyor…",
"Copying {count} files to \"{destination}\" …" : "{count} dosya \"{destination}\" konumuna kopyalanıyor…",
"Choose destination" : "Hedefi seçin",
"Copy to {target}" : "{target} içine kopyala",
"Move to {target}" : "{target} içine taşı",
@@ -344,7 +335,6 @@
"Templates" : "Kalıplar",
"New template folder" : "Yeni kalıp klasörü",
"In folder" : "Klasörde",
"Pick folder to search in" : "Aranacak klasörü seçin",
"Search in all files" : "Tüm dosyalarda ara",
"Search in folder: {folder}" : "Şu klasörde ara: {folder}",
"One of the dropped files could not be processed" : "Bırakılan dosyalardan biri işlenemedi",
+1 -1
View File
@@ -387,7 +387,7 @@ OC.L10N.register(
"Files that are not shared will show up here." : "Тут показуватимуться файли, які не є у спільному доступі.",
"Recent" : "Останні",
"List of recently modified files and folders." : "Список нещодавно змінених файлів та каталогів.",
"No recently modified files" : "Відсутні файли, які було нещодавно змінено",
"No recently modified files" : "Відсутні файли із нещодавними змінами",
"Files and folders you recently modified will show up here." : "Тут показуватимуться файли та каталоги, які було нещодавно змінено.",
"Search" : "Пошук",
"Search results within your files." : "Шукати результати серед ваших файлів.",
+1 -1
View File
@@ -385,7 +385,7 @@
"Files that are not shared will show up here." : "Тут показуватимуться файли, які не є у спільному доступі.",
"Recent" : "Останні",
"List of recently modified files and folders." : "Список нещодавно змінених файлів та каталогів.",
"No recently modified files" : "Відсутні файли, які було нещодавно змінено",
"No recently modified files" : "Відсутні файли із нещодавними змінами",
"Files and folders you recently modified will show up here." : "Тут показуватимуться файли та каталоги, які було нещодавно змінено.",
"Search" : "Пошук",
"Search results within your files." : "Шукати результати серед ваших файлів.",
@@ -35,7 +35,7 @@ describe('View in folder action conditions tests', () => {
contents: [],
})).toMatch(/<svg.+<\/svg>/)
expect(action.default).toBeUndefined()
expect(action.order).toBe(10)
expect(action.order).toBe(80)
expect(action.enabled).toBeDefined()
})
})
+3 -3
View File
@@ -5,7 +5,7 @@
import type { IFileAction } from '@nextcloud/files'
import FolderEyeSvg from '@mdi/svg/svg/folder-eye-outline.svg?raw'
import FolderMoveSvg from '@mdi/svg/svg/folder-move-outline.svg?raw'
import { FileType, Permission } from '@nextcloud/files'
import { t } from '@nextcloud/l10n'
import { isPublicShare } from '@nextcloud/sharing/public'
@@ -15,7 +15,7 @@ export const action: IFileAction = {
displayName() {
return t('files', 'View in folder')
},
iconSvgInline: () => FolderEyeSvg,
iconSvgInline: () => FolderMoveSvg,
enabled({ nodes, view }) {
// Not enabled for public shares
@@ -63,5 +63,5 @@ export const action: IFileAction = {
return null
},
order: 10,
order: 80,
}
+1 -1
View File
@@ -95,7 +95,7 @@ class Backends extends Base {
*/
private function formatConfiguration(array $parameters): array {
$configuration = array_filter($parameters, function (DefinitionParameter $parameter) {
return !$parameter->isFlagSet(DefinitionParameter::FLAG_HIDDEN);
return $parameter->isFlagSet(DefinitionParameter::FLAG_HIDDEN);
});
return array_map(function (DefinitionParameter $parameter) {
return $parameter->getTypeName();
@@ -28,7 +28,7 @@ trait DependencyTrait {
*
* @return MissingDependency[] Unsatisfied required dependencies
*/
public function checkRequiredDependencies(): array {
public function checkRequiredDependencies() {
return array_filter(
$this->checkDependencies(),
fn (MissingDependency $dependency) => !$dependency->isOptional()
@@ -32,23 +32,30 @@ class AmazonS3 extends Common {
private LoggerInterface $logger;
public function needsPartFile(): bool {
return false;
}
/** @var CappedMemoryCache<array|false> */
private CappedMemoryCache $objectCache;
/** @var CappedMemoryCache<bool> */
private CappedMemoryCache $directoryCache;
/** @var CappedMemoryCache<array> */
private CappedMemoryCache $filesCache;
private IMimeTypeDetector $mimeDetector;
private ICache $memCache;
private ?bool $versioningEnabled = null;
private ICache $memCache;
public function __construct(array $parameters) {
parent::__construct($parameters);
$this->parseParams($parameters);
// @todo: using `key` here may be problematic with different authentication methods and/or key rotation...
$this->id = 'amazon::external::' . md5($this->params['hostname'] . ':' . $this->params['bucket'] . ':' . $this->params['key']);
$this->initCaches();
$this->objectCache = new CappedMemoryCache();
$this->directoryCache = new CappedMemoryCache();
$this->filesCache = new CappedMemoryCache();
$this->mimeDetector = Server::get(IMimeTypeDetector::class);
/** @var ICacheFactory $cacheFactory */
$cacheFactory = Server::get(ICacheFactory::class);
@@ -59,7 +66,7 @@ class AmazonS3 extends Common {
private function normalizePath(string $path): string {
$path = trim($path, '/');
if ($path === '') {
if (!$path) {
$path = '.';
}
@@ -77,16 +84,10 @@ class AmazonS3 extends Common {
return $path;
}
private function initCaches(): void {
$this->objectCache = new CappedMemoryCache(2048);
$this->directoryCache = new CappedMemoryCache(8192);
$this->filesCache = new CappedMemoryCache(4096);
}
private function clearCaches(): void {
$this->objectCache->clear();
$this->directoryCache->clear();
$this->filesCache->clear();
private function clearCache(): void {
$this->objectCache = new CappedMemoryCache();
$this->directoryCache = new CappedMemoryCache();
$this->filesCache = new CappedMemoryCache();
}
private function invalidateCache(string $key): void {
@@ -245,7 +246,7 @@ class AmazonS3 extends Common {
}
protected function clearBucket(): bool {
$this->clearCaches();
$this->clearCache();
return $this->batchDelete();
}
@@ -269,8 +270,8 @@ class AmazonS3 extends Common {
$connection->deleteObjects([
'Bucket' => $this->bucket,
'Delete' => [
'Quiet' => true,
'Objects' => array_map(fn (array $object) => [
'ETag' => $object['ETag'],
'Key' => $object['Key'],
], $objects['Contents'])
]
@@ -320,9 +321,51 @@ class AmazonS3 extends Common {
return $stat;
}
/**
* Return content length for object
*
* When the information is already present (e.g. opendir has been called before)
* this value is return. Otherwise a headObject is emitted.
*/
private function getContentLength(string $path): int {
if (isset($this->filesCache[$path])) {
return (int)$this->filesCache[$path]['ContentLength'];
}
$result = $this->headObject($path);
if (isset($result['ContentLength'])) {
return (int)$result['ContentLength'];
}
return 0;
}
/**
* Return last modified for object
*
* When the information is already present (e.g. opendir has been called before)
* this value is return. Otherwise a headObject is emitted.
*/
private function getLastModified(string $path): string {
if (isset($this->filesCache[$path])) {
return $this->filesCache[$path]['LastModified'];
}
$result = $this->headObject($path);
if (isset($result['LastModified'])) {
return $result['LastModified'];
}
return 'now';
}
public function is_dir(string $path): bool {
$path = $this->normalizePath($path);
if (isset($this->filesCache[$path])) {
return false;
}
try {
return $this->doesDirectoryExist($path);
} catch (S3Exception $e) {
@@ -345,7 +388,7 @@ class AmazonS3 extends Common {
if (isset($this->directoryCache[$path]) && $this->directoryCache[$path]) {
return 'dir';
}
if ($this->headObject($path)) {
if (isset($this->filesCache[$path]) || $this->headObject($path)) {
return 'file';
}
if ($this->doesDirectoryExist($path)) {
@@ -699,11 +742,6 @@ class AmazonS3 extends Common {
}
}
public function needsPartFile(): bool {
// handled natively by the S3 backend/client integration
return false;
}
public function writeStream(string $path, $stream, ?int $size = null): int {
if ($size === null) {
$size = 0;
@@ -178,7 +178,7 @@ class BackendService {
* @return Backend[]
*/
public function getAvailableBackends() {
return array_filter($this->getBackends(), fn (Backend $backend) => $backend->checkRequiredDependencies() === []);
return array_filter($this->getBackends(), fn (Backend $backend) => !$backend->checkRequiredDependencies());
}
/**
+1 -1
View File
@@ -181,7 +181,7 @@ OC.L10N.register(
"Choose a default folder for accepted shares" : "Escolla un cartafol predeterminado para as comparticións aceptadas",
"Invalid path selected" : "Seleccionou unha ruta incorrecta.",
"Unknown error" : "Produciuse un erro descoñecido",
"Set default folder for accepted shares" : "Definir o cartafol predeterminado para as comparticións aceptadas",
"Set default folder for accepted shares" : "Definir o cartafol predeterminado para as s aceptadas",
"Reset" : "Restabelecer",
"Reset folder to system default" : "Restabelecer o cartafol ao predeterminado do sistema",
"Share expiration: {date}" : "Caducidade da compartición: {date}",
+1 -1
View File
@@ -179,7 +179,7 @@
"Choose a default folder for accepted shares" : "Escolla un cartafol predeterminado para as comparticións aceptadas",
"Invalid path selected" : "Seleccionou unha ruta incorrecta.",
"Unknown error" : "Produciuse un erro descoñecido",
"Set default folder for accepted shares" : "Definir o cartafol predeterminado para as comparticións aceptadas",
"Set default folder for accepted shares" : "Definir o cartafol predeterminado para as s aceptadas",
"Reset" : "Restabelecer",
"Reset folder to system default" : "Restabelecer o cartafol ao predeterminado do sistema",
"Share expiration: {date}" : "Caducidade da compartición: {date}",
+1 -1
View File
@@ -233,7 +233,7 @@ OC.L10N.register(
"Create a new share link" : "Креирајте нов линк за споделување",
"Quick share options, the current selected is \"{selectedOption}\"" : "Опции за брзо споделување за , тековната избрана е \"{selectedOption}\"",
"View only" : "Само за гледање",
"Can edit" : "Може да уредува",
"Can edit" : "Може да се уредува",
"Custom permissions" : "Прилагодени дозволи",
"Resharing is not allowed" : "Повторно споделување не е дозволено",
"Name or email …" : "Име или е-пошта …",
+1 -1
View File
@@ -231,7 +231,7 @@
"Create a new share link" : "Креирајте нов линк за споделување",
"Quick share options, the current selected is \"{selectedOption}\"" : "Опции за брзо споделување за , тековната избрана е \"{selectedOption}\"",
"View only" : "Само за гледање",
"Can edit" : "Може да уредува",
"Can edit" : "Може да се уредува",
"Custom permissions" : "Прилагодени дозволи",
"Resharing is not allowed" : "Повторно споделување не е дозволено",
"Name or email …" : "Име или е-пошта …",
+1 -1
View File
@@ -282,7 +282,7 @@ OC.L10N.register(
"Advanced settings" : "Розширені",
"Share label" : "Мітка спільного ресурсу",
"Share link token" : "Токен спільного ресурсу",
"Set the public share link token to something easy to remember or generate a new token. It is not recommended to use a guessable token for shares which contain sensitive information." : "Встановіть токен для публічного посилання на спільний ресурс з простою для запам'ятовування назвою або створіть новий токен. Не рекомендується використовувати токени, які можна легко вгадати, для спільних ресурсів, що містять чутливі дані.",
"Set the public share link token to something easy to remember or generate a new token. It is not recommended to use a guessable token for shares which contain sensitive information." : "Встановити публічне посилання на спільний ресурс у просту для запам'ятовування назву або створити новий токен. Не рекомендується використовувати токени, які можна легко вгадати для спільних ресурсів, які містять чутливі дані.",
"Generating…" : "Створення...",
"Generate new token" : "Створити новий токен",
"Set password" : "Встановити пароль",
+1 -1
View File
@@ -280,7 +280,7 @@
"Advanced settings" : "Розширені",
"Share label" : "Мітка спільного ресурсу",
"Share link token" : "Токен спільного ресурсу",
"Set the public share link token to something easy to remember or generate a new token. It is not recommended to use a guessable token for shares which contain sensitive information." : "Встановіть токен для публічного посилання на спільний ресурс з простою для запам'ятовування назвою або створіть новий токен. Не рекомендується використовувати токени, які можна легко вгадати, для спільних ресурсів, що містять чутливі дані.",
"Set the public share link token to something easy to remember or generate a new token. It is not recommended to use a guessable token for shares which contain sensitive information." : "Встановити публічне посилання на спільний ресурс у просту для запам'ятовування назву або створити новий токен. Не рекомендується використовувати токени, які можна легко вгадати для спільних ресурсів, які містять чутливі дані.",
"Generating…" : "Створення...",
"Generate new token" : "Створити новий токен",
"Set password" : "Встановити пароль",
@@ -236,7 +236,7 @@ class ShareAPIController extends OCSController {
$expiration = $share->getExpirationDate();
if ($expiration !== null) {
$expiration->setTimezone($this->dateTimeZone->getTimeZone());
$result['expiration'] = $expiration->format('Y-m-d H:i:s');
$result['expiration'] = $expiration->format('Y-m-d 00:00:00');
}
if ($share->getShareType() === IShare::TYPE_USER) {
+4 -4
View File
@@ -1052,7 +1052,7 @@ class ApiTest extends TestCase {
$share1 = $this->shareManager->getShareById($share1->getFullId());
// date should be changed
$dateWithinRange->setTime(23, 59, 59);
$dateWithinRange->setTime(0, 0, 0);
$dateWithinRange->setTimezone(new \DateTimeZone(date_default_timezone_get()));
$this->assertEquals($dateWithinRange, $share1->getExpirationDate());
@@ -1263,7 +1263,7 @@ class ApiTest extends TestCase {
public static function datesProvider() {
$date = new \DateTime();
$date->setTime(23, 59, 59);
$date->setTime(0, 0);
$date->add(new \DateInterval('P5D'));
$date->setTimezone(new \DateTimeZone(date_default_timezone_get()));
@@ -1325,14 +1325,14 @@ class ApiTest extends TestCase {
$data = $result->getData();
$this->assertTrue(is_string($data['token']));
$this->assertEquals($date->format('Y-m-d 23:59:59'), $data['expiration']);
$this->assertEquals($date->format('Y-m-d 00:00:00'), $data['expiration']);
// check for correct link
$url = Server::get(IURLGenerator::class)->getAbsoluteURL('/index.php/s/' . $data['token']);
$this->assertEquals($url, $data['url']);
$share = $this->shareManager->getShareById('ocinternal:' . $data['id']);
$date->setTime(23, 59, 59);
$date->setTime(0, 0, 0);
$this->assertEquals($date, $share->getExpirationDate());
$this->shareManager->deleteShare($share);
@@ -18,7 +18,6 @@ use OCP\Files\Mount\IMountManager;
use OCP\IAppConfig;
use OCP\IConfig;
use OCP\IDateTimeZone;
use OCP\IDBConnection;
use OCP\IGroupManager;
use OCP\IUserManager;
use OCP\IUserSession;
@@ -93,7 +92,6 @@ class CapabilitiesTest extends \Test\TestCase {
$this->createMock(ShareDisableChecker::class),
$this->createMock(IDateTimeZone::class),
$appConfig,
$this->createMock(IDBConnection::class),
);
$cap = new Capabilities($config, $appConfig, $shareManager, $appManager);
@@ -773,7 +773,7 @@ class ShareAPIControllerTest extends TestCase {
$data['Folder shared with group'] = [$share, $expected, true];
// File shared by link with Expire
$expire = \DateTime::createFromFormat('Y-m-d H:i:s', '2000-01-02 23:59:59');
$expire = \DateTime::createFromFormat('Y-m-d h:i:s', '2000-01-02 01:02:03');
$share = [
'101',
IShare::TYPE_LINK,
@@ -807,7 +807,7 @@ class ShareAPIControllerTest extends TestCase {
'file_target' => 'target',
'file_parent' => 3,
'token' => 'token',
'expiration' => '2000-01-02 23:59:59',
'expiration' => '2000-01-02 00:00:00',
'permissions' => 4,
'attributes' => null,
'stime' => 5,
@@ -4504,7 +4504,7 @@ class ShareAPIControllerTest extends TestCase {
'permissions' => 1,
'stime' => 946684862,
'parent' => null,
'expiration' => '2001-02-03 04:05:06',
'expiration' => '2001-02-03 00:00:00',
'token' => null,
'uid_file_owner' => 'owner',
'displayname_file_owner' => 'owner',
@@ -4554,7 +4554,7 @@ class ShareAPIControllerTest extends TestCase {
'permissions' => 1,
'stime' => 946684862,
'parent' => null,
'expiration' => '2001-02-03 04:05:06',
'expiration' => '2001-02-03 00:00:00',
'token' => null,
'uid_file_owner' => 'owner',
'displayname_file_owner' => 'owner',
@@ -99,11 +99,12 @@ class SharesReminderJobTest extends \Test\TestCase {
$someMail = 'test@test.com';
$noExpirationDate = null;
$today = new \DateTime();
// Expiration dates are set to end of day (23:59:59) by the Share Manager
$today->setTime(23, 59, 59);
$nearFuture = clone $today;
// For expiration dates, the time is always automatically set to zero by ShareAPIController
$today->setTime(0, 0);
$nearFuture = new \DateTime();
$nearFuture->setTimestamp($today->getTimestamp() + 86400 * 1);
$farFuture = new \DateTime();
$farFuture->setTimestamp($today->getTimestamp() + 86400 * 1);
$farFuture->setTimestamp($today->getTimestamp() + 86400 * 2);
$permissionRead = Constants::PERMISSION_READ;
$permissionCreate = $permissionRead | Constants::PERMISSION_CREATE;
$permissionUpdate = $permissionRead | Constants::PERMISSION_UPDATE;
+1
View File
@@ -193,6 +193,7 @@ abstract class TestCase extends \Test\TestCase {
Server::get(IUserSession::class)->setUser(null);
Filesystem::tearDown();
Server::get(IUserSession::class)->login($user, $password);
Filesystem::initMountPoints($user);
\OC::$server->getUserFolder($user);
\OC_Util::setupFS($user);
-16
View File
@@ -1,16 +0,0 @@
OC.L10N.register(
"profile",
{
"Profile" : "Профіль",
"Searching …" : "Пошук …",
"Not found" : "Не знойдзена",
"Insert" : "Уставіць",
"You have not added any info yet" : "Вы пакуль не дадалі ніякай інфармацыі",
"{user} has not added any info yet" : "{user} пакуль не дадаў(-ла) ніякай інфармацыі",
"Error opening the user status modal, try hard refreshing the page" : "Памылка пры адкрыцці статусу карыстальніка, паспрабуйце абнавіць старонку",
"Edit Profile" : "Рэдагаваць профіль",
"Profile not found" : "Профіль не знойдзены",
"The profile does not exist." : "Профіль не існуе.",
"Back to %s" : "Назад да %s"
},
"nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);");
-14
View File
@@ -1,14 +0,0 @@
{ "translations": {
"Profile" : "Профіль",
"Searching …" : "Пошук …",
"Not found" : "Не знойдзена",
"Insert" : "Уставіць",
"You have not added any info yet" : "Вы пакуль не дадалі ніякай інфармацыі",
"{user} has not added any info yet" : "{user} пакуль не дадаў(-ла) ніякай інфармацыі",
"Error opening the user status modal, try hard refreshing the page" : "Памылка пры адкрыцці статусу карыстальніка, паспрабуйце абнавіць старонку",
"Edit Profile" : "Рэдагаваць профіль",
"Profile not found" : "Профіль не знойдзены",
"The profile does not exist." : "Профіль не існуе.",
"Back to %s" : "Назад да %s"
},"pluralForm" :"nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);"
}
-16
View File
@@ -1,16 +0,0 @@
OC.L10N.register(
"profile",
{
"Searching …" : "Cercant …",
"Not found" : "No s'ha trobat",
"Insert" : "Insereix",
"You have not added any info yet" : "Encara no heu afegit cap informació",
"{user} has not added any info yet" : "{user} encara no ha afegit cap informació",
"Error opening the user status modal, try hard refreshing the page" : "S'ha produït un error en obrir el quadre de diàleg modal d'estat de l'usuari, proveu d'actualitzar la pàgina",
"Edit Profile" : "Edita el perfil",
"The headline and about sections will show up here" : "La capçalera i les seccions d'informació es mostraran aquí",
"Profile not found" : "No s'ha trobat el perfil",
"The profile does not exist." : "El perfil no existeix.",
"Back to %s" : "Torna a %s"
},
"nplurals=2; plural=(n != 1);");
-14
View File
@@ -1,14 +0,0 @@
{ "translations": {
"Searching …" : "Cercant …",
"Not found" : "No s'ha trobat",
"Insert" : "Insereix",
"You have not added any info yet" : "Encara no heu afegit cap informació",
"{user} has not added any info yet" : "{user} encara no ha afegit cap informació",
"Error opening the user status modal, try hard refreshing the page" : "S'ha produït un error en obrir el quadre de diàleg modal d'estat de l'usuari, proveu d'actualitzar la pàgina",
"Edit Profile" : "Edita el perfil",
"The headline and about sections will show up here" : "La capçalera i les seccions d'informació es mostraran aquí",
"Profile not found" : "No s'ha trobat el perfil",
"The profile does not exist." : "El perfil no existeix.",
"Back to %s" : "Torna a %s"
},"pluralForm" :"nplurals=2; plural=(n != 1);"
}
-16
View File
@@ -1,16 +0,0 @@
OC.L10N.register(
"profile",
{
"Searching …" : "Leita …",
"Not found" : "Fannst ekki",
"Insert" : "Setja inn",
"You have not added any info yet" : "Þú hefur ekki bætt við neinum upplýsingum ennþá",
"{user} has not added any info yet" : "{user} hefur ekki bætt við neinum upplýsingum ennþá",
"Error opening the user status modal, try hard refreshing the page" : "Villa við að opna stöðuglugga notandans, prófaðu að þvinga endurlestur síðunnar",
"Edit Profile" : "Breyta sniði",
"The headline and about sections will show up here" : "Fyrirsögnin og hlutar um hugbúnaðinn munu birtast hér",
"Profile not found" : "Sniðið finnst ekki",
"The profile does not exist." : "Sniðið er ekki til.",
"Back to %s" : "Til baka í %s"
},
"nplurals=2; plural=(n % 10 != 1 || n % 100 == 11);");
-14
View File
@@ -1,14 +0,0 @@
{ "translations": {
"Searching …" : "Leita …",
"Not found" : "Fannst ekki",
"Insert" : "Setja inn",
"You have not added any info yet" : "Þú hefur ekki bætt við neinum upplýsingum ennþá",
"{user} has not added any info yet" : "{user} hefur ekki bætt við neinum upplýsingum ennþá",
"Error opening the user status modal, try hard refreshing the page" : "Villa við að opna stöðuglugga notandans, prófaðu að þvinga endurlestur síðunnar",
"Edit Profile" : "Breyta sniði",
"The headline and about sections will show up here" : "Fyrirsögnin og hlutar um hugbúnaðinn munu birtast hér",
"Profile not found" : "Sniðið finnst ekki",
"The profile does not exist." : "Sniðið er ekki til.",
"Back to %s" : "Til baka í %s"
},"pluralForm" :"nplurals=2; plural=(n % 10 != 1 || n % 100 == 11);"
}
-15
View File
@@ -1,15 +0,0 @@
OC.L10N.register(
"profile",
{
"Profile" : "Профил",
"Searching …" : "Пребарување …",
"Not found" : "Не е пронајдено",
"You have not added any info yet" : "Сè уште немате додадено никакви информации",
"{user} has not added any info yet" : "{user} нема додадено никакви информации",
"Edit Profile" : "Уреди профил",
"The headline and about sections will show up here" : "Насловот и за секциите ќе се појават овде",
"Profile not found" : "Профилот не е пронајден",
"The profile does not exist." : "Профилот на постои",
"Back to %s" : "Врати се на %s"
},
"nplurals=2; plural=(n % 10 == 1 && n % 100 != 11) ? 0 : 1;");
-13
View File
@@ -1,13 +0,0 @@
{ "translations": {
"Profile" : "Профил",
"Searching …" : "Пребарување …",
"Not found" : "Не е пронајдено",
"You have not added any info yet" : "Сè уште немате додадено никакви информации",
"{user} has not added any info yet" : "{user} нема додадено никакви информации",
"Edit Profile" : "Уреди профил",
"The headline and about sections will show up here" : "Насловот и за секциите ќе се појават овде",
"Profile not found" : "Профилот не е пронајден",
"The profile does not exist." : "Профилот на постои",
"Back to %s" : "Врати се на %s"
},"pluralForm" :"nplurals=2; plural=(n % 10 == 1 && n % 100 != 11) ? 0 : 1;"
}
-17
View File
@@ -1,17 +0,0 @@
OC.L10N.register(
"profile",
{
"Profile picker" : "Selector de perfil",
"Profile" : "Perfil",
"Searching …" : "Recèrca…",
"Not found" : "Non trobat",
"Search for a user profile" : "Cercar un perfil utilizaire",
"Search for a user profile. Start typing" : "Cercar un perfil utilizaire. Començatz de picar",
"Insert selected user profile link" : "Inserir lo ligam del perfil utilizaire seleccionat",
"Insert" : "Inserir",
"Edit Profile" : "Modificar perfil",
"Profile not found" : "Perfil pas trobat",
"The profile does not exist." : "Lo perfil existís pas.",
"Back to %s" : "Tornar a %s"
},
"nplurals=2; plural=(n > 1);");
-15
View File
@@ -1,15 +0,0 @@
{ "translations": {
"Profile picker" : "Selector de perfil",
"Profile" : "Perfil",
"Searching …" : "Recèrca…",
"Not found" : "Non trobat",
"Search for a user profile" : "Cercar un perfil utilizaire",
"Search for a user profile. Start typing" : "Cercar un perfil utilizaire. Començatz de picar",
"Insert selected user profile link" : "Inserir lo ligam del perfil utilizaire seleccionat",
"Insert" : "Inserir",
"Edit Profile" : "Modificar perfil",
"Profile not found" : "Perfil pas trobat",
"The profile does not exist." : "Lo perfil existís pas.",
"Back to %s" : "Tornar a %s"
},"pluralForm" :"nplurals=2; plural=(n > 1);"
}
-14
View File
@@ -1,14 +0,0 @@
OC.L10N.register(
"profile",
{
"Not found" : "Nu a fost găsit",
"You have not added any info yet" : "Nu ați adăugat nicio informație",
"{user} has not added any info yet" : "{user} nu a adăugat nicio informație",
"Error opening the user status modal, try hard refreshing the page" : "Eroare la deschiderea status utilizator, încercați refresh",
"Edit Profile" : "Editare profil",
"The headline and about sections will show up here" : "Secțiunile titlu și despre vor fi afișate aici",
"Profile not found" : "Profil inexistent",
"The profile does not exist." : "Profilul nu există",
"Back to %s" : "Înapoi la %s"
},
"nplurals=3; plural=(n==1?0:(((n%100>19)||((n%100==0)&&(n!=0)))?2:1));");
-12
View File
@@ -1,12 +0,0 @@
{ "translations": {
"Not found" : "Nu a fost găsit",
"You have not added any info yet" : "Nu ați adăugat nicio informație",
"{user} has not added any info yet" : "{user} nu a adăugat nicio informație",
"Error opening the user status modal, try hard refreshing the page" : "Eroare la deschiderea status utilizator, încercați refresh",
"Edit Profile" : "Editare profil",
"The headline and about sections will show up here" : "Secțiunile titlu și despre vor fi afișate aici",
"Profile not found" : "Profil inexistent",
"The profile does not exist." : "Profilul nu există",
"Back to %s" : "Înapoi la %s"
},"pluralForm" :"nplurals=3; plural=(n==1?0:(((n%100>19)||((n%100==0)&&(n!=0)))?2:1));"
}
+12
View File
@@ -0,0 +1,12 @@
OC.L10N.register(
"profile",
{
"Not found" : "ไม่พบ",
"You have not added any info yet" : "คุณยังไม่ได้เพิ่มข้อมูลใด ๆ",
"{user} has not added any info yet" : "{user} ยังไม่ได้เพิ่มข้อมูลใด ๆ",
"Edit Profile" : "แก้ไขโปรไฟล์",
"Profile not found" : "ไม่พบโปรไฟล์",
"The profile does not exist." : "โปรไฟล์นี้ไม่มีอยู่",
"Back to %s" : "กลับสู่ %s"
},
"nplurals=1; plural=0;");
+10
View File
@@ -0,0 +1,10 @@
{ "translations": {
"Not found" : "ไม่พบ",
"You have not added any info yet" : "คุณยังไม่ได้เพิ่มข้อมูลใด ๆ",
"{user} has not added any info yet" : "{user} ยังไม่ได้เพิ่มข้อมูลใด ๆ",
"Edit Profile" : "แก้ไขโปรไฟล์",
"Profile not found" : "ไม่พบโปรไฟล์",
"The profile does not exist." : "โปรไฟล์นี้ไม่มีอยู่",
"Back to %s" : "กลับสู่ %s"
},"pluralForm" :"nplurals=1; plural=0;"
}
-4
View File
@@ -1,15 +1,11 @@
OC.L10N.register(
"profile",
{
"Profile picker" : "Profil seçici",
"Profile" : "Profil",
"This application provides the profile" : "Bu uygulama profili sağlar",
"Provides a customisable user profile interface." : "Özelleştirilebilir bir kullanıcı profili arayüzü sağlar.",
"Searching …" : "Aranıyor…",
"Not found" : "Bulunamadı",
"Search for a user profile" : "Kullanıcı profili ara",
"Search for a user profile. Start typing" : "Aranacak kullanıcı profilini yazmaya başlayın",
"Insert selected user profile link" : "Seçilmiş kullanıcı profili bağlantısını ekle",
"Insert" : "Ekle",
"You have not added any info yet" : "Henüz herhangi bir bilgi eklememişsiniz",
"{user} has not added any info yet" : "{user} henüz herhangi bir bilgi eklememiş",
-4
View File
@@ -1,13 +1,9 @@
{ "translations": {
"Profile picker" : "Profil seçici",
"Profile" : "Profil",
"This application provides the profile" : "Bu uygulama profili sağlar",
"Provides a customisable user profile interface." : "Özelleştirilebilir bir kullanıcı profili arayüzü sağlar.",
"Searching …" : "Aranıyor…",
"Not found" : "Bulunamadı",
"Search for a user profile" : "Kullanıcı profili ara",
"Search for a user profile. Start typing" : "Aranacak kullanıcı profilini yazmaya başlayın",
"Insert selected user profile link" : "Seçilmiş kullanıcı profili bağlantısını ekle",
"Insert" : "Ekle",
"You have not added any info yet" : "Henüz herhangi bir bilgi eklememişsiniz",
"{user} has not added any info yet" : "{user} henüz herhangi bir bilgi eklememiş",
-3
View File
@@ -593,11 +593,8 @@ OC.L10N.register(
"cron.php is registered at a webcron service to call cron.php every 5 minutes over HTTP. Use case: Very small instance (15 accounts depending on the usage)." : "cron.php ist bei einem Webcron-Dienst registriert, um cron.php alle 5 Minuten über HTTP aufzurufen. Anwendungsfall: Sehr kleine Instanz (15 Konten, je nach Nutzung).",
"Cron (Recommended)" : "Cron (Empfohlen)",
"Unable to update profile default setting" : "Standardeinstellung des Profils kann nicht aktualisiert werden",
"Unable to update profile picker setting" : "Einstellungen der Profilauswahl konnten nicht aktualisiert werden",
"Profile" : "Profil",
"Enable or disable profile by default for new accounts." : "Profil für neue Konten standardmäßig aktivieren oder deaktivieren.",
"Enable the profile picker" : "Profilauswahl aktivieren",
"Enable or disable the profile picker in the Smart Picker and the profile link previews." : "Aktivieren oder deaktivieren der Profilauswahl im Smart Picker und die Vorschau der Profilverknüpfungen.",
"Password confirmation is required" : "Passwortbestätigung erforderlich",
"Failed to save setting" : "Einstellung konnte nicht gespeichert werden",
"{app}'s declarative setting field: {name}" : "Deklaratives Einstellungsfeld von {app}: {name}",
-3
View File
@@ -591,11 +591,8 @@
"cron.php is registered at a webcron service to call cron.php every 5 minutes over HTTP. Use case: Very small instance (15 accounts depending on the usage)." : "cron.php ist bei einem Webcron-Dienst registriert, um cron.php alle 5 Minuten über HTTP aufzurufen. Anwendungsfall: Sehr kleine Instanz (15 Konten, je nach Nutzung).",
"Cron (Recommended)" : "Cron (Empfohlen)",
"Unable to update profile default setting" : "Standardeinstellung des Profils kann nicht aktualisiert werden",
"Unable to update profile picker setting" : "Einstellungen der Profilauswahl konnten nicht aktualisiert werden",
"Profile" : "Profil",
"Enable or disable profile by default for new accounts." : "Profil für neue Konten standardmäßig aktivieren oder deaktivieren.",
"Enable the profile picker" : "Profilauswahl aktivieren",
"Enable or disable the profile picker in the Smart Picker and the profile link previews." : "Aktivieren oder deaktivieren der Profilauswahl im Smart Picker und die Vorschau der Profilverknüpfungen.",
"Password confirmation is required" : "Passwortbestätigung erforderlich",
"Failed to save setting" : "Einstellung konnte nicht gespeichert werden",
"{app}'s declarative setting field: {name}" : "Deklaratives Einstellungsfeld von {app}: {name}",
-3
View File
@@ -593,11 +593,8 @@ OC.L10N.register(
"cron.php is registered at a webcron service to call cron.php every 5 minutes over HTTP. Use case: Very small instance (15 accounts depending on the usage)." : "cron.php ist bei einem Webcron-Dienst registriert, um cron.php alle 5 Minuten über HTTP aufzurufen. Anwendungsfall: Sehr kleine Instanz (15 Konten, je nach Nutzung).",
"Cron (Recommended)" : "Cron (Empfohlen)",
"Unable to update profile default setting" : "Standardeinstellung des Profils kann nicht aktualisiert werden",
"Unable to update profile picker setting" : "Einstellungen der Profilauswahl konnten nicht aktualisiert werden",
"Profile" : "Profil",
"Enable or disable profile by default for new accounts." : "Profil für neue Konten standardmäßig aktivieren oder deaktivieren.",
"Enable the profile picker" : "Profilauswahl aktivieren",
"Enable or disable the profile picker in the Smart Picker and the profile link previews." : "Aktivieren oder deaktivieren der Profilauswahl im Smart Picker und die Vorschau der Profilverknüpfungen.",
"Password confirmation is required" : "Passwortbestätigung erforderlich",
"Failed to save setting" : "Einstellung konnte nicht gespeichert werden",
"{app}'s declarative setting field: {name}" : "Deklaratives Einstellungsfeld von {app}: {name}",
-3
View File
@@ -591,11 +591,8 @@
"cron.php is registered at a webcron service to call cron.php every 5 minutes over HTTP. Use case: Very small instance (15 accounts depending on the usage)." : "cron.php ist bei einem Webcron-Dienst registriert, um cron.php alle 5 Minuten über HTTP aufzurufen. Anwendungsfall: Sehr kleine Instanz (15 Konten, je nach Nutzung).",
"Cron (Recommended)" : "Cron (Empfohlen)",
"Unable to update profile default setting" : "Standardeinstellung des Profils kann nicht aktualisiert werden",
"Unable to update profile picker setting" : "Einstellungen der Profilauswahl konnten nicht aktualisiert werden",
"Profile" : "Profil",
"Enable or disable profile by default for new accounts." : "Profil für neue Konten standardmäßig aktivieren oder deaktivieren.",
"Enable the profile picker" : "Profilauswahl aktivieren",
"Enable or disable the profile picker in the Smart Picker and the profile link previews." : "Aktivieren oder deaktivieren der Profilauswahl im Smart Picker und die Vorschau der Profilverknüpfungen.",
"Password confirmation is required" : "Passwortbestätigung erforderlich",
"Failed to save setting" : "Einstellung konnte nicht gespeichert werden",
"{app}'s declarative setting field: {name}" : "Deklaratives Einstellungsfeld von {app}: {name}",
-3
View File
@@ -593,11 +593,8 @@ OC.L10N.register(
"cron.php is registered at a webcron service to call cron.php every 5 minutes over HTTP. Use case: Very small instance (15 accounts depending on the usage)." : "Tá cron.php cláraithe ag seirbhís webcron chun cron.php a ghlaoch gach 5 nóiméad thar HTTP. Cás úsáide: Sampla an-bheag (15 chuntas ag brath ar an úsáid).",
"Cron (Recommended)" : "Cron (Molta)",
"Unable to update profile default setting" : "Ní féidir socrú réamhshocraithe na próifíle a nuashonrú",
"Unable to update profile picker setting" : "Ní féidir socruithe roghnóra próifíle a nuashonrú",
"Profile" : "Próifíl",
"Enable or disable profile by default for new accounts." : "Cumasaigh nó díchumasaigh próifíl de réir réamhshocraithe le haghaidh cuntas nua.",
"Enable the profile picker" : "Cumasaigh an roghnóir próifíle",
"Enable or disable the profile picker in the Smart Picker and the profile link previews." : "Cumasaigh nó díchumasaigh an roghnóir próifíle sa Roghnóir Cliste agus réamhamhairc na nasc próifíle.",
"Password confirmation is required" : "Tá deimhniú pasfhocail ag teastáil",
"Failed to save setting" : "Theip ar an socrú a shábháil",
"{app}'s declarative setting field: {name}" : "Réimse socruithe dearbhaithe {app}'s: {name}",
-3
View File
@@ -591,11 +591,8 @@
"cron.php is registered at a webcron service to call cron.php every 5 minutes over HTTP. Use case: Very small instance (15 accounts depending on the usage)." : "Tá cron.php cláraithe ag seirbhís webcron chun cron.php a ghlaoch gach 5 nóiméad thar HTTP. Cás úsáide: Sampla an-bheag (15 chuntas ag brath ar an úsáid).",
"Cron (Recommended)" : "Cron (Molta)",
"Unable to update profile default setting" : "Ní féidir socrú réamhshocraithe na próifíle a nuashonrú",
"Unable to update profile picker setting" : "Ní féidir socruithe roghnóra próifíle a nuashonrú",
"Profile" : "Próifíl",
"Enable or disable profile by default for new accounts." : "Cumasaigh nó díchumasaigh próifíl de réir réamhshocraithe le haghaidh cuntas nua.",
"Enable the profile picker" : "Cumasaigh an roghnóir próifíle",
"Enable or disable the profile picker in the Smart Picker and the profile link previews." : "Cumasaigh nó díchumasaigh an roghnóir próifíle sa Roghnóir Cliste agus réamhamhairc na nasc próifíle.",
"Password confirmation is required" : "Tá deimhniú pasfhocail ag teastáil",
"Failed to save setting" : "Theip ar an socrú a shábháil",
"{app}'s declarative setting field: {name}" : "Réimse socruithe dearbhaithe {app}'s: {name}",
+1 -4
View File
@@ -399,7 +399,7 @@ OC.L10N.register(
"Enforce expiration date for remote shares" : "Aplicar a data de caducidade dos recursos compartidos remotos",
"Default expiration time of remote shares in days" : "Tempo de caducidade predeterminado dos recursos compartidos remotos en días",
"Expire remote shares after x days" : "As comparticións remotas caducan após x días",
"Set default expiration date for shares via link or mail" : "Definir a data de caducidade predeterminada para compartir mediante ligazón ou correo electrónico",
"Set default expiration date for shares via link or mail" : "Definir a data de caducidade predeterminada para as comparticións con outros servidores",
"Enforce expiration date for link or mail shares" : "Aplicar a data de caducidade para as de ligazóns ou correos para compartir",
"Default expiration time of shares in days" : "Tempo de caducidade predeterminado dos recursos compartidos en días",
"Privacy settings for sharing" : "Axustes da privacidade para compartir",
@@ -593,11 +593,8 @@ OC.L10N.register(
"cron.php is registered at a webcron service to call cron.php every 5 minutes over HTTP. Use case: Very small instance (15 accounts depending on the usage)." : "cron.php está rexistrado nun servizo webcron para chamar a cron.php cada 5 minutos a través de HTTP. Caso de uso: instancia moi pequena (de 1 a 5 contas segundo o uso).",
"Cron (Recommended)" : "Cron (Recomendado)",
"Unable to update profile default setting" : "Non é posíbel actualizar a configuración predeterminada do perfil",
"Unable to update profile picker setting" : "Non é posíbel actualizar a configuración do selector de perfil",
"Profile" : "Perfil",
"Enable or disable profile by default for new accounts." : "Activar ou desactivar o perfil predeterminado para as novas contas.",
"Enable the profile picker" : "Activar o selector de perfil",
"Enable or disable the profile picker in the Smart Picker and the profile link previews." : "Activar ou desactivar o selector de perfís no Selector intelixente e nas vistas previas da ligazón ao perfil.",
"Password confirmation is required" : "Requírese a confirmación do contrasinal",
"Failed to save setting" : "Produciuse un fallo ao gardar o axuste",
"{app}'s declarative setting field: {name}" : "Campo de axuste declarativo de {app}: {name}",
+1 -4
View File
@@ -397,7 +397,7 @@
"Enforce expiration date for remote shares" : "Aplicar a data de caducidade dos recursos compartidos remotos",
"Default expiration time of remote shares in days" : "Tempo de caducidade predeterminado dos recursos compartidos remotos en días",
"Expire remote shares after x days" : "As comparticións remotas caducan após x días",
"Set default expiration date for shares via link or mail" : "Definir a data de caducidade predeterminada para compartir mediante ligazón ou correo electrónico",
"Set default expiration date for shares via link or mail" : "Definir a data de caducidade predeterminada para as comparticións con outros servidores",
"Enforce expiration date for link or mail shares" : "Aplicar a data de caducidade para as de ligazóns ou correos para compartir",
"Default expiration time of shares in days" : "Tempo de caducidade predeterminado dos recursos compartidos en días",
"Privacy settings for sharing" : "Axustes da privacidade para compartir",
@@ -591,11 +591,8 @@
"cron.php is registered at a webcron service to call cron.php every 5 minutes over HTTP. Use case: Very small instance (15 accounts depending on the usage)." : "cron.php está rexistrado nun servizo webcron para chamar a cron.php cada 5 minutos a través de HTTP. Caso de uso: instancia moi pequena (de 1 a 5 contas segundo o uso).",
"Cron (Recommended)" : "Cron (Recomendado)",
"Unable to update profile default setting" : "Non é posíbel actualizar a configuración predeterminada do perfil",
"Unable to update profile picker setting" : "Non é posíbel actualizar a configuración do selector de perfil",
"Profile" : "Perfil",
"Enable or disable profile by default for new accounts." : "Activar ou desactivar o perfil predeterminado para as novas contas.",
"Enable the profile picker" : "Activar o selector de perfil",
"Enable or disable the profile picker in the Smart Picker and the profile link previews." : "Activar ou desactivar o selector de perfís no Selector intelixente e nas vistas previas da ligazón ao perfil.",
"Password confirmation is required" : "Requírese a confirmación do contrasinal",
"Failed to save setting" : "Produciuse un fallo ao gardar o axuste",
"{app}'s declarative setting field: {name}" : "Campo de axuste declarativo de {app}: {name}",
-3
View File
@@ -593,11 +593,8 @@ OC.L10N.register(
"cron.php is registered at a webcron service to call cron.php every 5 minutes over HTTP. Use case: Very small instance (15 accounts depending on the usage)." : "cron.phpは、HTTP越しに5分おきにcron.phpを読み出すWebcronサービスにより登録されています。使用例: 極小なインスタンス(利用率に依存する1〜5アカウント程度)の場合。",
"Cron (Recommended)" : "Cron (推奨)",
"Unable to update profile default setting" : "プロフィールのデフォルト設定を更新できませんでした",
"Unable to update profile picker setting" : "プロフィールピッカーの設定を更新できません",
"Profile" : "プロフィール",
"Enable or disable profile by default for new accounts." : "新しいアカウントの場合、デフォルトでプロフィールを有効または無効にします。",
"Enable the profile picker" : "プロフィールピッカーを有効にする",
"Enable or disable the profile picker in the Smart Picker and the profile link previews." : "スマートピッカーにおけるプロフィールピッカーとプロフィールリンクプレビューの有効化または無効化",
"Password confirmation is required" : "パスワードの確認が必要です",
"Failed to save setting" : "設定の保存に失敗しました",
"{app}'s declarative setting field: {name}" : "{app}の宣言的設定フィールド: {name}",
-3
View File
@@ -591,11 +591,8 @@
"cron.php is registered at a webcron service to call cron.php every 5 minutes over HTTP. Use case: Very small instance (15 accounts depending on the usage)." : "cron.phpは、HTTP越しに5分おきにcron.phpを読み出すWebcronサービスにより登録されています。使用例: 極小なインスタンス(利用率に依存する1〜5アカウント程度)の場合。",
"Cron (Recommended)" : "Cron (推奨)",
"Unable to update profile default setting" : "プロフィールのデフォルト設定を更新できませんでした",
"Unable to update profile picker setting" : "プロフィールピッカーの設定を更新できません",
"Profile" : "プロフィール",
"Enable or disable profile by default for new accounts." : "新しいアカウントの場合、デフォルトでプロフィールを有効または無効にします。",
"Enable the profile picker" : "プロフィールピッカーを有効にする",
"Enable or disable the profile picker in the Smart Picker and the profile link previews." : "スマートピッカーにおけるプロフィールピッカーとプロフィールリンクプレビューの有効化または無効化",
"Password confirmation is required" : "パスワードの確認が必要です",
"Failed to save setting" : "設定の保存に失敗しました",
"{app}'s declarative setting field: {name}" : "{app}の宣言的設定フィールド: {name}",
-3
View File
@@ -593,11 +593,8 @@ OC.L10N.register(
"cron.php is registered at a webcron service to call cron.php every 5 minutes over HTTP. Use case: Very small instance (15 accounts depending on the usage)." : "cron.php é registrado em um serviço webcron para chamar cron.php a cada 5 minutos por HTTP. Caso de uso: Instância muito pequena (15 contas dependendo do uso).",
"Cron (Recommended)" : "Cron (Recomendado)",
"Unable to update profile default setting" : "Não foi possível atualizar a configuração padrão do perfil",
"Unable to update profile picker setting" : "Não é possível atualizar a configuração do seletor de perfis",
"Profile" : "Perfil",
"Enable or disable profile by default for new accounts." : "Ativar ou desativar o perfil por padrão para novas contas.",
"Enable the profile picker" : "Ativar o seletor de perfis",
"Enable or disable the profile picker in the Smart Picker and the profile link previews." : "Ative ou desative o seletor de perfis no seletor inteligente e as pré-visualizações dos links dos perfis.",
"Password confirmation is required" : "A confirmação da senha é necessária",
"Failed to save setting" : "Falha ao salvar a configuração",
"{app}'s declarative setting field: {name}" : "Campo de configuração declarativa de {app}: {name}",
-3
View File
@@ -591,11 +591,8 @@
"cron.php is registered at a webcron service to call cron.php every 5 minutes over HTTP. Use case: Very small instance (15 accounts depending on the usage)." : "cron.php é registrado em um serviço webcron para chamar cron.php a cada 5 minutos por HTTP. Caso de uso: Instância muito pequena (15 contas dependendo do uso).",
"Cron (Recommended)" : "Cron (Recomendado)",
"Unable to update profile default setting" : "Não foi possível atualizar a configuração padrão do perfil",
"Unable to update profile picker setting" : "Não é possível atualizar a configuração do seletor de perfis",
"Profile" : "Perfil",
"Enable or disable profile by default for new accounts." : "Ativar ou desativar o perfil por padrão para novas contas.",
"Enable the profile picker" : "Ativar o seletor de perfis",
"Enable or disable the profile picker in the Smart Picker and the profile link previews." : "Ative ou desative o seletor de perfis no seletor inteligente e as pré-visualizações dos links dos perfis.",
"Password confirmation is required" : "A confirmação da senha é necessária",
"Failed to save setting" : "Falha ao salvar a configuração",
"{app}'s declarative setting field: {name}" : "Campo de configuração declarativa de {app}: {name}",
+1 -28
View File
@@ -330,10 +330,6 @@ OC.L10N.register(
"Database transaction isolation level" : "Veri tabanı işlemsel yalıtım düzeyi",
"Your database does not run with \"READ COMMITTED\" transaction isolation level. This can cause problems when multiple actions are executed in parallel." : "Veri tabanınız \"READ COMMITTED\" işlem yalıtma düzeyinde çalışmıyor. Bu durum aynı anda birden çok işlem yapıldığında sorun çıkmasına yol açabilir.",
"Was not able to get transaction isolation level: %s" : "İşlemsel yalıtım düzeyi alınamadı: %s",
"Second factor configuration" : "İkinci adım yapılandırması",
"This instance has no second factor provider available." : "Bu kopyada kullanılabilecek bir ikinci adım hizmeti sağlayıcısı yok.",
"Second factor providers are available but two-factor authentication is not enforced." : "Kullanılabilecek ikinci adım hizmeti sağlayıcıları var, ancak iki adımlı doğrulama zorunlu değil.",
"Second factor providers are available and enforced: %s." : "Kullanılabilecek ikinci adım hizmeti sağlayıcıları var ve kullanılması zorunlu: %s.",
".well-known URLs" : ".well-known adresler",
"`check_for_working_wellknown_setup` is set to false in your configuration, so this check was skipped." : "Yapılandırmanızda `check_for_working_wellknown_setup` değerinin false olarak ayarlandığından emin olun. Böylece bu denetim atlanır.",
"Could not check that your web server serves `.well-known` correctly. Please check manually." : "Site sunucunuzun `.well.known` bilgisini doğru şekilde sunup sunmadığı denetlenemedi. Lütfen el ile denetleyin.",
@@ -386,8 +382,6 @@ OC.L10N.register(
"Shares with guessable tokens may be accessed easily" : "Öngörülebilir kodları olan paylaşımlara kolayca erişilebilir",
"Limit sharing based on groups" : "Paylaşımlar gruplara göre sınırlansın",
"Allow sharing for everyone (default)" : "Herkes ile paylaşım yapılabilsin (varsayılan)",
"Exclude some groups" : "Bazı gruplar katılmasın",
"Allow some groups" : "Bazı gruplar katılsın",
"Groups allowed to share" : "Paylaşım yapılabilecek gruplar",
"Groups excluded from sharing" : "Paylaşıma katılmayacak gruplar",
"Not allowed groups will still be able to receive shares, but not to initiate them." : "İzin verilmeyen gruplar paylaşımları almayı sürdürebilir ancak paylaşım yapamaz.",
@@ -442,16 +436,9 @@ OC.L10N.register(
"This app is supported via your current Nextcloud subscription." : "Bu uygulamanın desteği geçerli Nextcloud aboneliğiniz ile sağlanır.",
"Featured apps are developed by and within the community. They offer central functionality and are ready for production use." : "Öne çıkarılmış uygulamalar topluluk tarafından geliştirilmiştir. Temel işlevleri yerine getirirler ve üretim ortamında kullanılabilirler.",
"Community rating: {score}/5" : "Topluluk değerlendirmesi: {score}/5",
"Office suite switching is managed through the Nextcloud All-in-One interface." : "Ofis paketi değişikliği Nextcloud tümü bir arada arayüzünden yapılır.",
"Please use the AIO interface to switch between office suites." : "Ofis paketi değişikliği yapmak için tümü bir arada arayüzünü kullanın.",
"Select your preferred office suite. Please note that installing requires manual server setup." : "Kullanmak istediğiniz ofis paketini seçin. Kurulum için sunucuda el ile işlem yapılması gerektirdiğini unutmayın.",
"installed" : "kurulmuş",
"Learn more" : "Ayrıntılı bilgi alın",
"Disable office suites" : "Ofis paketlerini kullanımdan kaldır",
"Disable all" : "Tümünü kullanımdan kaldır",
"Download and enable all" : "İndir ve tümünü kullanıma al",
"All office suites disabled" : "Tüm ofis paketleri kullanımdan kaldırıldı",
"{name} enabled" : "{name} kullanıma alındı",
"All apps are up-to-date." : "Tüm uygulamalar güncel",
"Icon" : "Simge",
"Name" : "Ad",
@@ -593,11 +580,8 @@ OC.L10N.register(
"cron.php is registered at a webcron service to call cron.php every 5 minutes over HTTP. Use case: Very small instance (15 accounts depending on the usage)." : "cron.php, HTTP üzerinden her 5 dakikada bir cron.php sayfasını çağıran bir internet zamanlanmış görevi hizmetinde kayıtlıdır. Kullanım şekli: Çok küçük kopya (kullanıma bağlı olarak 15 hesap).",
"Cron (Recommended)" : "Cron (önerilen)",
"Unable to update profile default setting" : "Profil varsayılan ayarı güncellenemedi",
"Unable to update profile picker setting" : "Profil seçici ayarı güncellenemedi",
"Profile" : "Profil",
"Enable or disable profile by default for new accounts." : "Yeni hesaplar için profilleri varsayılan olarak kullanıma al ya da kaldır.",
"Enable the profile picker" : "Profil seçiciyi aç",
"Enable or disable the profile picker in the Smart Picker and the profile link previews." : "Akıllı seçicide profil seçiciyi ve profil bağlantısı ön izlemelerini açar ya da kapatır.",
"Password confirmation is required" : "Parola onayının yazılması zorunludur",
"Failed to save setting" : "Ayar kaydedilemedi",
"{app}'s declarative setting field: {name}" : "{app} uygulamasının bildirdiği ayar alanı: {name}",
@@ -815,7 +799,7 @@ OC.L10N.register(
"By member count" : "Üye sayısına göre",
"By name" : "Ada göre",
"Send email" : "E-posta gönder",
"Send welcome email to new accounts" : "Yeni hesaplara karşılama e-postası gönderilsin",
"Send welcome email to new accounts" : "Yeni hesaplara hoş geldiniz e-postası gönderilsin",
"Defaults" : "Varsayılanlar",
"Default quota" : "Varsayılan kota",
"Select default quota" : "Varsayılan kota değerini seçin",
@@ -914,17 +898,6 @@ OC.L10N.register(
"App bundles" : "Uygulama Paketleri",
"Featured apps" : "Öne çıkarılmış uygulamalar",
"Supported apps" : "Desteklenen uygulamalar",
"Best Nextcloud integration" : "En iyi Nextcloud bütünleştirmesi",
"Open source" : "Açık kaynaklı",
"Good performance" : "İyi başarım",
"Best security: documents never leave your server" : "En iyi güvenlik: Belgeler asla sunucunuzdan ayrılmaz",
"Best ODF compatibility" : "En iyi ODF uyumluluğu",
"Best support for legacy files" : "Eski dosyalar için en iyi destek",
"Good Nextcloud integration" : "İyi Nextcloud bütünleştirmesi",
"Open core" : "Açık çekirdekli",
"Best performance" : "En iyi başarım",
"Limited ODF compatibility" : "Sınırlı ODF uyumluluğu",
"Best Microsoft compatibility" : "En iyi Microsoft uyumluluğu",
"Show to everyone" : "Herkese görüntülensin",
"Show to logged in accounts only" : "Yalnızca oturum açmış hesaplara görüntülensin",
"Hide" : "Gizlensin",
+1 -28
View File
@@ -328,10 +328,6 @@
"Database transaction isolation level" : "Veri tabanı işlemsel yalıtım düzeyi",
"Your database does not run with \"READ COMMITTED\" transaction isolation level. This can cause problems when multiple actions are executed in parallel." : "Veri tabanınız \"READ COMMITTED\" işlem yalıtma düzeyinde çalışmıyor. Bu durum aynı anda birden çok işlem yapıldığında sorun çıkmasına yol açabilir.",
"Was not able to get transaction isolation level: %s" : "İşlemsel yalıtım düzeyi alınamadı: %s",
"Second factor configuration" : "İkinci adım yapılandırması",
"This instance has no second factor provider available." : "Bu kopyada kullanılabilecek bir ikinci adım hizmeti sağlayıcısı yok.",
"Second factor providers are available but two-factor authentication is not enforced." : "Kullanılabilecek ikinci adım hizmeti sağlayıcıları var, ancak iki adımlı doğrulama zorunlu değil.",
"Second factor providers are available and enforced: %s." : "Kullanılabilecek ikinci adım hizmeti sağlayıcıları var ve kullanılması zorunlu: %s.",
".well-known URLs" : ".well-known adresler",
"`check_for_working_wellknown_setup` is set to false in your configuration, so this check was skipped." : "Yapılandırmanızda `check_for_working_wellknown_setup` değerinin false olarak ayarlandığından emin olun. Böylece bu denetim atlanır.",
"Could not check that your web server serves `.well-known` correctly. Please check manually." : "Site sunucunuzun `.well.known` bilgisini doğru şekilde sunup sunmadığı denetlenemedi. Lütfen el ile denetleyin.",
@@ -384,8 +380,6 @@
"Shares with guessable tokens may be accessed easily" : "Öngörülebilir kodları olan paylaşımlara kolayca erişilebilir",
"Limit sharing based on groups" : "Paylaşımlar gruplara göre sınırlansın",
"Allow sharing for everyone (default)" : "Herkes ile paylaşım yapılabilsin (varsayılan)",
"Exclude some groups" : "Bazı gruplar katılmasın",
"Allow some groups" : "Bazı gruplar katılsın",
"Groups allowed to share" : "Paylaşım yapılabilecek gruplar",
"Groups excluded from sharing" : "Paylaşıma katılmayacak gruplar",
"Not allowed groups will still be able to receive shares, but not to initiate them." : "İzin verilmeyen gruplar paylaşımları almayı sürdürebilir ancak paylaşım yapamaz.",
@@ -440,16 +434,9 @@
"This app is supported via your current Nextcloud subscription." : "Bu uygulamanın desteği geçerli Nextcloud aboneliğiniz ile sağlanır.",
"Featured apps are developed by and within the community. They offer central functionality and are ready for production use." : "Öne çıkarılmış uygulamalar topluluk tarafından geliştirilmiştir. Temel işlevleri yerine getirirler ve üretim ortamında kullanılabilirler.",
"Community rating: {score}/5" : "Topluluk değerlendirmesi: {score}/5",
"Office suite switching is managed through the Nextcloud All-in-One interface." : "Ofis paketi değişikliği Nextcloud tümü bir arada arayüzünden yapılır.",
"Please use the AIO interface to switch between office suites." : "Ofis paketi değişikliği yapmak için tümü bir arada arayüzünü kullanın.",
"Select your preferred office suite. Please note that installing requires manual server setup." : "Kullanmak istediğiniz ofis paketini seçin. Kurulum için sunucuda el ile işlem yapılması gerektirdiğini unutmayın.",
"installed" : "kurulmuş",
"Learn more" : "Ayrıntılı bilgi alın",
"Disable office suites" : "Ofis paketlerini kullanımdan kaldır",
"Disable all" : "Tümünü kullanımdan kaldır",
"Download and enable all" : "İndir ve tümünü kullanıma al",
"All office suites disabled" : "Tüm ofis paketleri kullanımdan kaldırıldı",
"{name} enabled" : "{name} kullanıma alındı",
"All apps are up-to-date." : "Tüm uygulamalar güncel",
"Icon" : "Simge",
"Name" : "Ad",
@@ -591,11 +578,8 @@
"cron.php is registered at a webcron service to call cron.php every 5 minutes over HTTP. Use case: Very small instance (15 accounts depending on the usage)." : "cron.php, HTTP üzerinden her 5 dakikada bir cron.php sayfasını çağıran bir internet zamanlanmış görevi hizmetinde kayıtlıdır. Kullanım şekli: Çok küçük kopya (kullanıma bağlı olarak 15 hesap).",
"Cron (Recommended)" : "Cron (önerilen)",
"Unable to update profile default setting" : "Profil varsayılan ayarı güncellenemedi",
"Unable to update profile picker setting" : "Profil seçici ayarı güncellenemedi",
"Profile" : "Profil",
"Enable or disable profile by default for new accounts." : "Yeni hesaplar için profilleri varsayılan olarak kullanıma al ya da kaldır.",
"Enable the profile picker" : "Profil seçiciyi aç",
"Enable or disable the profile picker in the Smart Picker and the profile link previews." : "Akıllı seçicide profil seçiciyi ve profil bağlantısı ön izlemelerini açar ya da kapatır.",
"Password confirmation is required" : "Parola onayının yazılması zorunludur",
"Failed to save setting" : "Ayar kaydedilemedi",
"{app}'s declarative setting field: {name}" : "{app} uygulamasının bildirdiği ayar alanı: {name}",
@@ -813,7 +797,7 @@
"By member count" : "Üye sayısına göre",
"By name" : "Ada göre",
"Send email" : "E-posta gönder",
"Send welcome email to new accounts" : "Yeni hesaplara karşılama e-postası gönderilsin",
"Send welcome email to new accounts" : "Yeni hesaplara hoş geldiniz e-postası gönderilsin",
"Defaults" : "Varsayılanlar",
"Default quota" : "Varsayılan kota",
"Select default quota" : "Varsayılan kota değerini seçin",
@@ -912,17 +896,6 @@
"App bundles" : "Uygulama Paketleri",
"Featured apps" : "Öne çıkarılmış uygulamalar",
"Supported apps" : "Desteklenen uygulamalar",
"Best Nextcloud integration" : "En iyi Nextcloud bütünleştirmesi",
"Open source" : "Açık kaynaklı",
"Good performance" : "İyi başarım",
"Best security: documents never leave your server" : "En iyi güvenlik: Belgeler asla sunucunuzdan ayrılmaz",
"Best ODF compatibility" : "En iyi ODF uyumluluğu",
"Best support for legacy files" : "Eski dosyalar için en iyi destek",
"Good Nextcloud integration" : "İyi Nextcloud bütünleştirmesi",
"Open core" : "Açık çekirdekli",
"Best performance" : "En iyi başarım",
"Limited ODF compatibility" : "Sınırlı ODF uyumluluğu",
"Best Microsoft compatibility" : "En iyi Microsoft uyumluluğu",
"Show to everyone" : "Herkese görüntülensin",
"Show to logged in accounts only" : "Yalnızca oturum açmış hesaplara görüntülensin",
"Hide" : "Gizlensin",
+16 -19
View File
@@ -369,18 +369,18 @@ OC.L10N.register(
"Restrict users to only share with users in their groups" : "Дозволити надання у спільний доступ тільки в межах власних груп",
"Ignore the following groups when checking group membership" : "Ігнорувати такі групи під час перевірки участи в групі",
"Allow users to preview files even if download is disabled" : "Дозволити користувачам переглядати файли, навіть якщо завантаження вимкнено",
"Users will still be able to screenshot or record the screen. This does not provide any definitive protection." : "Користувачі все одно можуть робити скріншоти або записувати екран. Це не забезпечить надійним захистом.",
"Users will still be able to screenshot or record the screen. This does not provide any definitive protection." : "Користувачі все одно зможуть робити скріншоти або записувати екран. Це не забезпечує жодного остаточного захисту.",
"Allow users to share via link and emails" : "Дозволити користувачам надання у спільний доступ за допомогою посилань та ел. листів",
"Allow public uploads" : "Дозволити публічне завантаження",
"Allow public shares to be added to other clouds by federation." : "Дозволити додавати публічні ресурси до інших хмар за допомогою функціоналу об'єднаних хмар.",
"Allow public shares to be added to other clouds by federation." : "Дозволити додавати публічні ресурси до інших хмар за допомогою федерації.",
"This will add share permissions to all newly created link shares." : "Це додасть дозволи на спільний доступ до всіх новостворених спільних ресурсів посилань.",
"Always ask for a password" : "Завжди запитувати пароль",
"Enforce password protection" : "Захист паролем обов'язковий",
"Exclude groups from password requirements" : "Виключення щодо вимог пароля для груп",
"Exclude groups from creating link shares" : "Не дозволяти користувачам таких груп створювати посилання спільного доступу",
"Allow users to set custom share link tokens" : "Дозволити користувачам встановлювати власні токени для спільного доступу",
"Shares with custom tokens will continue to be accessible after this setting has been disabled" : "Спільний доступ з токенами користувачів залишатиметься активним після вимкнення цього параметра",
"Shares with guessable tokens may be accessed easily" : "До спільних ресурсів з токенами, які можна спробувати вгадати, інші можуть легко отримати доступ.",
"Allow users to set custom share link tokens" : "Дозволити користвучам встановити власні токени спільних посилань",
"Shares with custom tokens will continue to be accessible after this setting has been disabled" : "Частки з власними токенами залишатимуться доступними після вимкнення цього параметра",
"Shares with guessable tokens may be accessed easily" : "До спільних ресурсів з токенами, які можна вгадати, можна легко отримати доступ.",
"Limit sharing based on groups" : "Обмежити надання у спільний доступ на основі груп",
"Allow sharing for everyone (default)" : "Дозволити надання у спільний доступ для всіх (типово)",
"Exclude some groups" : "Вилучити окремі групи",
@@ -400,16 +400,16 @@ OC.L10N.register(
"Enforce expiration date for link or mail shares" : "Застосовувати термін дії для посилань або спільного доступу до пошти",
"Default expiration time of shares in days" : "Типовий термін дії спільних ресурсів у днях",
"Privacy settings for sharing" : "Налаштування конфіденційності для спільного доступу",
"Allow account name autocompletion in share dialog and allow access to the system address book" : "Дозволити автоматичне заповнення імени користувача та доступ до системної адресної книги",
"Sharing autocompletion restrictions" : "Дозволи автоматичного заповнення для спільного доступу",
"If autocompletion restrictions for both \"same group\" and \"phonebook integration\" are enabled, a match in either is enough to show the user." : "Якщо уімкнено автоматичне заповнення \"в межах власної групи\" та \"доступ за адресною книгою\", достатньо, щоби хоч раз було виконано умови, щоб показати ім'я користувача.",
"Restrict account name autocompletion and system address book access to users within the same groups" : "Дозволити автоматичне заповнення імени користувача, доступ до системної адресної книги користувачам тільки в межах власних груп",
"Restrict account name autocompletion to users based on their phonebook" : "Дозволити автоматичне заповнення імени користувача тільки з власних адресних книг користувачів",
"Allow autocompletion to full match when entering the full name (ignoring restrictions like group membership or missing phonebook match)" : "Дозволити автоматичне заповнення, якщо введено ім'я користувача повністю (при цьому не враховуватимуться такі дозволи, як участь в групах або відсутність у адресній книзі)",
"Full match autocompletion restrictions" : "Дозволи для автоматичного заповнення, якщо дані введено повністю",
"Also allow autocompletion on full match of the user ID" : "Дозволити автоматичне заповнення, якщо введено повний ідентифікатор користувача",
"Also allow autocompletion on full match of the display name" : "Дзволити автоматичне заповнення, якщо введено ім'я користувача",
"Also allow autocompletion on full match of the user email" : "Дозволити автоматичне заповнення, якщо введено ел. адресу користувача",
"Allow account name autocompletion in share dialog and allow access to the system address book" : "Дозволити автозаповнення імени користувача та доступ до системної адресної книги",
"Sharing autocompletion restrictions" : "Обмеження автозавершення для спільного доступу",
"If autocompletion restrictions for both \"same group\" and \"phonebook integration\" are enabled, a match in either is enough to show the user." : "Якщо задіяно автозавершення як для \"тієї саме групи\" та \"інтеграції з адресною книгою\", достатньо одного збігу, щоби показати ім'я користувача.",
"Restrict account name autocompletion and system address book access to users within the same groups" : "Обмежити автозаповнення імени користувача та доступу до системної адресної книги тільки користувачам однієї й тої саме групи",
"Restrict account name autocompletion to users based on their phonebook" : "Обмежити автозавершення імени користувача на основі адресних книг користувачів",
"Allow autocompletion to full match when entering the full name (ignoring restrictions like group membership or missing phonebook match)" : "Дозволити автозавершення для повного збігу при введенні повного імени (не враховуватимуться такі обмеження, як участь в групах або незбіг з адресною книгою)",
"Full match autocompletion restrictions" : "Обмеження повного збігу для автозавершення",
"Also allow autocompletion on full match of the user ID" : "Також дозволити автозавершення при повному збігу ідентифікатора користувача",
"Also allow autocompletion on full match of the display name" : "Також дозволити автозаповнення при повному збігу імени для показу",
"Also allow autocompletion on full match of the user email" : "Також дозволити автозавершення при повному збігу ел. адреси користувача",
"Do not use second user displayname for full match" : "Не використовувати друге ім'я для показу для повного збігу",
"Show disclaimer text on the public link upload page (only shown when the file list is hidden)" : "Показувати текст застереження на сторінці завантаження публічного посилання (відображається, лише якщо список файлів приховано)",
"Disclaimer text" : "Текст відмови від відповідальності",
@@ -590,11 +590,8 @@ OC.L10N.register(
"cron.php is registered at a webcron service to call cron.php every 5 minutes over HTTP. Use case: Very small instance (15 accounts depending on the usage)." : "cron.php зареєстровано на сервісі webcron, щоб викликати cron.php кожні 5 хвилин по HTTP. Варіант використання: Дуже маленький екземпляр (1-5 акаунтів залежно від використання).",
"Cron (Recommended)" : "Cron (рекомендовано)",
"Unable to update profile default setting" : "Не вдалося оновити стандартні налаштування профілю",
"Unable to update profile picker setting" : "Не вдалося оновити налаштування вибору профілю",
"Profile" : "Профіль",
"Enable or disable profile by default for new accounts." : "Увімкнути або вимкнути стандартний профіль для нових акаунтів.",
"Enable the profile picker" : "Увімкнути вибір профілю",
"Enable or disable the profile picker in the Smart Picker and the profile link previews." : "Увімкнути або вимкнути вибір профілю для Асистента з вибору та попереднього перегляду посилання на профіль.",
"Password confirmation is required" : "Необхідне підтвердження паролем",
"Failed to save setting" : "Не вдалося зберегти налаштування",
"{app}'s declarative setting field: {name}" : "Декларативне поле налаштувань {app}: {name}",
@@ -981,7 +978,7 @@ OC.L10N.register(
"Unable to retrieve the group list" : "Неможливо отримати список груп",
"Exclude some groups from sharing" : "Не дозволяти таким групам надавати у спільний доступ",
"Limit sharing to some groups" : "Дозволити надання у спільний доступ тільки для таких груп",
"Also allow autocompletion on full match of the user id" : "Дозволити автоматичне заповнення, якщо введено повністю ідентифікатор користувача",
"Also allow autocompletion on full match of the user id" : "Також дозволити автозавершення при повному збігу ідентифікатора користувача",
"Loading accounts …" : "Завантаження облікових записів ...",
"Set account as admin for …" : "Встановити адміністратором для ...",
"_{userCount} account …_::_{userCount} accounts …_" : ["{userCount} обліковий запис …","{userCount} облікові записи …","{userCount} облікових записів …","{userCount} облікових записів …"],
+16 -19
View File
@@ -367,18 +367,18 @@
"Restrict users to only share with users in their groups" : "Дозволити надання у спільний доступ тільки в межах власних груп",
"Ignore the following groups when checking group membership" : "Ігнорувати такі групи під час перевірки участи в групі",
"Allow users to preview files even if download is disabled" : "Дозволити користувачам переглядати файли, навіть якщо завантаження вимкнено",
"Users will still be able to screenshot or record the screen. This does not provide any definitive protection." : "Користувачі все одно можуть робити скріншоти або записувати екран. Це не забезпечить надійним захистом.",
"Users will still be able to screenshot or record the screen. This does not provide any definitive protection." : "Користувачі все одно зможуть робити скріншоти або записувати екран. Це не забезпечує жодного остаточного захисту.",
"Allow users to share via link and emails" : "Дозволити користувачам надання у спільний доступ за допомогою посилань та ел. листів",
"Allow public uploads" : "Дозволити публічне завантаження",
"Allow public shares to be added to other clouds by federation." : "Дозволити додавати публічні ресурси до інших хмар за допомогою функціоналу об'єднаних хмар.",
"Allow public shares to be added to other clouds by federation." : "Дозволити додавати публічні ресурси до інших хмар за допомогою федерації.",
"This will add share permissions to all newly created link shares." : "Це додасть дозволи на спільний доступ до всіх новостворених спільних ресурсів посилань.",
"Always ask for a password" : "Завжди запитувати пароль",
"Enforce password protection" : "Захист паролем обов'язковий",
"Exclude groups from password requirements" : "Виключення щодо вимог пароля для груп",
"Exclude groups from creating link shares" : "Не дозволяти користувачам таких груп створювати посилання спільного доступу",
"Allow users to set custom share link tokens" : "Дозволити користувачам встановлювати власні токени для спільного доступу",
"Shares with custom tokens will continue to be accessible after this setting has been disabled" : "Спільний доступ з токенами користувачів залишатиметься активним після вимкнення цього параметра",
"Shares with guessable tokens may be accessed easily" : "До спільних ресурсів з токенами, які можна спробувати вгадати, інші можуть легко отримати доступ.",
"Allow users to set custom share link tokens" : "Дозволити користвучам встановити власні токени спільних посилань",
"Shares with custom tokens will continue to be accessible after this setting has been disabled" : "Частки з власними токенами залишатимуться доступними після вимкнення цього параметра",
"Shares with guessable tokens may be accessed easily" : "До спільних ресурсів з токенами, які можна вгадати, можна легко отримати доступ.",
"Limit sharing based on groups" : "Обмежити надання у спільний доступ на основі груп",
"Allow sharing for everyone (default)" : "Дозволити надання у спільний доступ для всіх (типово)",
"Exclude some groups" : "Вилучити окремі групи",
@@ -398,16 +398,16 @@
"Enforce expiration date for link or mail shares" : "Застосовувати термін дії для посилань або спільного доступу до пошти",
"Default expiration time of shares in days" : "Типовий термін дії спільних ресурсів у днях",
"Privacy settings for sharing" : "Налаштування конфіденційності для спільного доступу",
"Allow account name autocompletion in share dialog and allow access to the system address book" : "Дозволити автоматичне заповнення імени користувача та доступ до системної адресної книги",
"Sharing autocompletion restrictions" : "Дозволи автоматичного заповнення для спільного доступу",
"If autocompletion restrictions for both \"same group\" and \"phonebook integration\" are enabled, a match in either is enough to show the user." : "Якщо уімкнено автоматичне заповнення \"в межах власної групи\" та \"доступ за адресною книгою\", достатньо, щоби хоч раз було виконано умови, щоб показати ім'я користувача.",
"Restrict account name autocompletion and system address book access to users within the same groups" : "Дозволити автоматичне заповнення імени користувача, доступ до системної адресної книги користувачам тільки в межах власних груп",
"Restrict account name autocompletion to users based on their phonebook" : "Дозволити автоматичне заповнення імени користувача тільки з власних адресних книг користувачів",
"Allow autocompletion to full match when entering the full name (ignoring restrictions like group membership or missing phonebook match)" : "Дозволити автоматичне заповнення, якщо введено ім'я користувача повністю (при цьому не враховуватимуться такі дозволи, як участь в групах або відсутність у адресній книзі)",
"Full match autocompletion restrictions" : "Дозволи для автоматичного заповнення, якщо дані введено повністю",
"Also allow autocompletion on full match of the user ID" : "Дозволити автоматичне заповнення, якщо введено повний ідентифікатор користувача",
"Also allow autocompletion on full match of the display name" : "Дзволити автоматичне заповнення, якщо введено ім'я користувача",
"Also allow autocompletion on full match of the user email" : "Дозволити автоматичне заповнення, якщо введено ел. адресу користувача",
"Allow account name autocompletion in share dialog and allow access to the system address book" : "Дозволити автозаповнення імени користувача та доступ до системної адресної книги",
"Sharing autocompletion restrictions" : "Обмеження автозавершення для спільного доступу",
"If autocompletion restrictions for both \"same group\" and \"phonebook integration\" are enabled, a match in either is enough to show the user." : "Якщо задіяно автозавершення як для \"тієї саме групи\" та \"інтеграції з адресною книгою\", достатньо одного збігу, щоби показати ім'я користувача.",
"Restrict account name autocompletion and system address book access to users within the same groups" : "Обмежити автозаповнення імени користувача та доступу до системної адресної книги тільки користувачам однієї й тої саме групи",
"Restrict account name autocompletion to users based on their phonebook" : "Обмежити автозавершення імени користувача на основі адресних книг користувачів",
"Allow autocompletion to full match when entering the full name (ignoring restrictions like group membership or missing phonebook match)" : "Дозволити автозавершення для повного збігу при введенні повного імени (не враховуватимуться такі обмеження, як участь в групах або незбіг з адресною книгою)",
"Full match autocompletion restrictions" : "Обмеження повного збігу для автозавершення",
"Also allow autocompletion on full match of the user ID" : "Також дозволити автозавершення при повному збігу ідентифікатора користувача",
"Also allow autocompletion on full match of the display name" : "Також дозволити автозаповнення при повному збігу імени для показу",
"Also allow autocompletion on full match of the user email" : "Також дозволити автозавершення при повному збігу ел. адреси користувача",
"Do not use second user displayname for full match" : "Не використовувати друге ім'я для показу для повного збігу",
"Show disclaimer text on the public link upload page (only shown when the file list is hidden)" : "Показувати текст застереження на сторінці завантаження публічного посилання (відображається, лише якщо список файлів приховано)",
"Disclaimer text" : "Текст відмови від відповідальності",
@@ -588,11 +588,8 @@
"cron.php is registered at a webcron service to call cron.php every 5 minutes over HTTP. Use case: Very small instance (15 accounts depending on the usage)." : "cron.php зареєстровано на сервісі webcron, щоб викликати cron.php кожні 5 хвилин по HTTP. Варіант використання: Дуже маленький екземпляр (1-5 акаунтів залежно від використання).",
"Cron (Recommended)" : "Cron (рекомендовано)",
"Unable to update profile default setting" : "Не вдалося оновити стандартні налаштування профілю",
"Unable to update profile picker setting" : "Не вдалося оновити налаштування вибору профілю",
"Profile" : "Профіль",
"Enable or disable profile by default for new accounts." : "Увімкнути або вимкнути стандартний профіль для нових акаунтів.",
"Enable the profile picker" : "Увімкнути вибір профілю",
"Enable or disable the profile picker in the Smart Picker and the profile link previews." : "Увімкнути або вимкнути вибір профілю для Асистента з вибору та попереднього перегляду посилання на профіль.",
"Password confirmation is required" : "Необхідне підтвердження паролем",
"Failed to save setting" : "Не вдалося зберегти налаштування",
"{app}'s declarative setting field: {name}" : "Декларативне поле налаштувань {app}: {name}",
@@ -979,7 +976,7 @@
"Unable to retrieve the group list" : "Неможливо отримати список груп",
"Exclude some groups from sharing" : "Не дозволяти таким групам надавати у спільний доступ",
"Limit sharing to some groups" : "Дозволити надання у спільний доступ тільки для таких груп",
"Also allow autocompletion on full match of the user id" : "Дозволити автоматичне заповнення, якщо введено повністю ідентифікатор користувача",
"Also allow autocompletion on full match of the user id" : "Також дозволити автозавершення при повному збігу ідентифікатора користувача",
"Loading accounts …" : "Завантаження облікових записів ...",
"Set account as admin for …" : "Встановити адміністратором для ...",
"_{userCount} account …_::_{userCount} accounts …_" : ["{userCount} обліковий запис …","{userCount} облікові записи …","{userCount} облікових записів …","{userCount} облікових записів …"],
-3
View File
@@ -593,11 +593,8 @@ OC.L10N.register(
"cron.php is registered at a webcron service to call cron.php every 5 minutes over HTTP. Use case: Very small instance (15 accounts depending on the usage)." : "cron.php 在 webcron 服務中註冊,每五分鐘透過 HTTP 呼叫一次 cron.php。使用情境:非常小的站台(一到五個帳號,取決於使用量)。",
"Cron (Recommended)" : "Cron(建議)",
"Unable to update profile default setting" : "無法更新個人檔案預設設定",
"Unable to update profile picker setting" : "無法更新個人檔案挑選程式設定",
"Profile" : "個人檔案",
"Enable or disable profile by default for new accounts." : "預設情況下為新帳號啟用或停用個人檔案",
"Enable the profile picker" : "啟用個人檔案挑選程式",
"Enable or disable the profile picker in the Smart Picker and the profile link previews." : "在智慧型挑選程式與個人檔案連結預覽中啟用或停用個人檔案挑選程式。",
"Password confirmation is required" : "需要密碼確認",
"Failed to save setting" : "儲存設定失敗",
"{app}'s declarative setting field: {name}" : "{app} 的聲明性設定欄位:{name}",
-3
View File
@@ -591,11 +591,8 @@
"cron.php is registered at a webcron service to call cron.php every 5 minutes over HTTP. Use case: Very small instance (15 accounts depending on the usage)." : "cron.php 在 webcron 服務中註冊,每五分鐘透過 HTTP 呼叫一次 cron.php。使用情境:非常小的站台(一到五個帳號,取決於使用量)。",
"Cron (Recommended)" : "Cron(建議)",
"Unable to update profile default setting" : "無法更新個人檔案預設設定",
"Unable to update profile picker setting" : "無法更新個人檔案挑選程式設定",
"Profile" : "個人檔案",
"Enable or disable profile by default for new accounts." : "預設情況下為新帳號啟用或停用個人檔案",
"Enable the profile picker" : "啟用個人檔案挑選程式",
"Enable or disable the profile picker in the Smart Picker and the profile link previews." : "在智慧型挑選程式與個人檔案連結預覽中啟用或停用個人檔案挑選程式。",
"Password confirmation is required" : "需要密碼確認",
"Failed to save setting" : "儲存設定失敗",
"{app}'s declarative setting field: {name}" : "{app} 的聲明性設定欄位:{name}",
@@ -16,11 +16,9 @@ use OCP\IConfig;
use OCP\IL10N;
use OCP\IURLGenerator;
use OCP\Share\IManager;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\MockObject\MockObject;
use Test\TestCase;
#[Group(name: 'DB')]
class SharingTest extends TestCase {
private Sharing $admin;
+1 -1
View File
@@ -41,6 +41,6 @@ OC.L10N.register(
"Unable to update share by mail config" : "Не вдається оновити конфігурацію спільного доступу за допомогою пошти",
"Allows people to share a personalized link to a file or folder by putting in an email address." : "Дозволяє користувачам надавати персоналізоване посилання на файл або каталог шляхом додавання адреси ел. пошти.",
"Send password by mail" : "Надіслати пароль поштою",
"Reply to initiator" : "Відповісти ініціатору"
"Reply to initiator" : "Відповідь ініціатору"
},
"nplurals=4; plural=(n % 1 == 0 && n % 10 == 1 && n % 100 != 11 ? 0 : n % 1 == 0 && n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14) ? 1 : n % 1 == 0 && (n % 10 ==0 || (n % 10 >=5 && n % 10 <=9) || (n % 100 >=11 && n % 100 <=14 )) ? 2: 3);");
+1 -1
View File
@@ -39,6 +39,6 @@
"Unable to update share by mail config" : "Не вдається оновити конфігурацію спільного доступу за допомогою пошти",
"Allows people to share a personalized link to a file or folder by putting in an email address." : "Дозволяє користувачам надавати персоналізоване посилання на файл або каталог шляхом додавання адреси ел. пошти.",
"Send password by mail" : "Надіслати пароль поштою",
"Reply to initiator" : "Відповісти ініціатору"
"Reply to initiator" : "Відповідь ініціатору"
},"pluralForm" :"nplurals=4; plural=(n % 1 == 0 && n % 10 == 1 && n % 100 != 11 ? 0 : n % 1 == 0 && n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14) ? 1 : n % 1 == 0 && (n % 10 ==0 || (n % 10 >=5 && n % 10 <=9) || (n % 100 >=11 && n % 100 <=14 )) ? 2: 3);"
}
-1
View File
@@ -109,7 +109,6 @@ OC.L10N.register(
"Reset primary color" : "Birincil rengi sıfırla",
"Reset to default" : "Varsayılanlara dön",
"Non image file selected" : "Seçilen dosya bir görsel değil",
"Failed to upload image" : "Görsel yüklenemedi",
"Preview of the selected image" : "Seçilmiş görselin ön izlemesi",
"Universal access is very important to us. We follow web standards and check to make everything usable also without mouse, and assistive software such as screenreaders. We aim to be compliant with the {linkstart}Web Content Accessibility Guidelines{linkend} 2.1 on AA level, with the high contrast theme even on AAA level." : "Uygulamalarımızı herkesin kullanabilmesini çok önemsiyoruz. internet sitesi standartlarını izleyerek, işlemlerin fare olmadan da yapılabilmesini ve ekran okuyucular gibi yardımcı yazılımların kullanılabilmesini sağlıyoruz. AAA düzeyinde yüksek renk karşıtlığı teması ile AA düzeyinde {linkstart}İnternet Sitesi İçeriği Erişilebilirlik Kuralları{linkend} 2.1 ile uyumlu olmayı amaçladık.",
"If you find any issues, do not hesitate to report them on {issuetracker}our issue tracker{linkend}. And if you want to get involved, come join {designteam}our design team{linkend}!" : "Bir sorunla karşılaşırsanız, bunları {issuetracker}sorun izleyicimiz{linkend} üzerinden bildirmekten çekinmeyin. Katkıda bulunmak istiyorsanız {designteam}tasarım ekibimize{linkend} katılın!",
-1
View File
@@ -107,7 +107,6 @@
"Reset primary color" : "Birincil rengi sıfırla",
"Reset to default" : "Varsayılanlara dön",
"Non image file selected" : "Seçilen dosya bir görsel değil",
"Failed to upload image" : "Görsel yüklenemedi",
"Preview of the selected image" : "Seçilmiş görselin ön izlemesi",
"Universal access is very important to us. We follow web standards and check to make everything usable also without mouse, and assistive software such as screenreaders. We aim to be compliant with the {linkstart}Web Content Accessibility Guidelines{linkend} 2.1 on AA level, with the high contrast theme even on AAA level." : "Uygulamalarımızı herkesin kullanabilmesini çok önemsiyoruz. internet sitesi standartlarını izleyerek, işlemlerin fare olmadan da yapılabilmesini ve ekran okuyucular gibi yardımcı yazılımların kullanılabilmesini sağlıyoruz. AAA düzeyinde yüksek renk karşıtlığı teması ile AA düzeyinde {linkstart}İnternet Sitesi İçeriği Erişilebilirlik Kuralları{linkend} 2.1 ile uyumlu olmayı amaçladık.",
"If you find any issues, do not hesitate to report them on {issuetracker}our issue tracker{linkend}. And if you want to get involved, come join {designteam}our design team{linkend}!" : "Bir sorunla karşılaşırsanız, bunları {issuetracker}sorun izleyicimiz{linkend} üzerinden bildirmekten çekinmeyin. Katkıda bulunmak istiyorsanız {designteam}tasarım ekibimize{linkend} katılın!",
+3 -2
View File
@@ -144,8 +144,9 @@ class IconBuilder {
$y = $tmp->getImageHeight();
$tmp->destroy();
// set resolution for proper scaling
$res = (int)(72 * $size / max($x, $y));
$appIconFile->setResolution($res, $res);
$resX = (int)(72 * $size / $x);
$resY = (int)(72 * $size / $y);
$appIconFile->setResolution($resX, $resY);
$appIconFile->readImageBlob($svg);
} else {
// handle non-SVG images
+4 -2
View File
@@ -297,9 +297,11 @@ class IconBuilderTest extends TestCase {
$x = $tmp->getImageWidth();
$y = $tmp->getImageHeight();
$tmp->destroy();
$res = (int)(72 * $size / max($x, $y));
// set resolution for proper scaling
$resX = (int)(72 * $size / $x);
$resY = (int)(72 * $size / $y);
$appIconFile->setBackgroundColor(new \ImagickPixel('transparent'));
$appIconFile->setResolution($res, $res);
$appIconFile->setResolution($resX, $resY);
$appIconFile->readImageBlob($svgContent);
} else {
$appIconFile->readImage($filePath);
-7
View File
@@ -128,9 +128,7 @@ OC.L10N.register(
"A connection error to LDAP/AD occurred. Please check host, port and credentials." : "Csatlakozási hiba az LDAP/AD-vel. Ellenőrizd a kiszolgálót, a portot és a hitelesítő adatokat.",
"The \"%uid\" placeholder is missing. It will be replaced with the login name when querying LDAP/AD." : "A(z) „%uid” helykitöltő hiányzik. Ez lesz lecserélve a felhasználónévre az LDAP/AD lekérdezésekor.",
"When logging in, {instanceName} will find the user based on the following attributes:" : "Bejelentkezéskor a(z) {instanceName} a következő attribútumok alapján találja meg a felhasználót:",
"Allows login against the LDAP/AD username, which is either 'uid' or 'sAMAccountName' and will be detected." : "Engedélyezi a bejelentkezést az LDAP/AD felhasználónév alapján, amely vagy 'uid' vagy 'sAMAccountName' és észlelve lesz.",
"LDAP/AD Username:" : "LDAP/AD felhasználónév:",
"Allows login against an email attribute. 'mail' and 'mailPrimaryAddress' allowed." : "Engedélyezi a bejelentkezést egy email attribútum alapján. 'mail' és 'mailPrimaryAddress' megengedett.",
"LDAP/AD Email Address:" : "LDAP/AD e-mail-cím:",
"Other Attributes:" : "Más attribútumok:",
"Defines the filter to apply, when login is attempted. `%%uid` replaces the username in the login action. Example: `uid=%%uid`" : "Meghatározza a belépéskor alkalmazandó szűrőt. A `%%uid` lecseréli a felhasználónevet a bejelentkezési műveletnél. Például: `uid=%%uid`",
@@ -141,7 +139,6 @@ OC.L10N.register(
"More than 1,000 directory entries available." : "Több mint 1000 címtárbejegyzés érhető el.",
"_{ldapTestBase} entry available within the provided Base DN_::_{ldapTestBase} entries available within the provided Base DN_" : ["{ldapTestBase} bejegyzés érhető el a megadott alap DN alatt","{ldapTestBase} bejegyzés érhető el a megadott alap DN alatt"],
"When unchecked, this configuration will be skipped." : "Ha nincs kipipálva, ez a beállítás ki lesz hagyva.",
"Configuration active" : "Konfiguráció aktív",
"Copy current configuration into new directory binding" : "Jelenlegi beállítások másolása egy új címtárkötésbe",
"Copy configuration" : "Konfiguráció másolása",
"Delete configuration" : "Konfiguráció törlése",
@@ -149,11 +146,9 @@ OC.L10N.register(
"Host" : "Kiszolgáló",
"Port" : "Port",
"Detect Port" : "Port észlelése",
"The DN of the client user with which the bind shall be done. For anonymous access, leave DN and Password empty." : "A kliens felhasználó DN-je, amellyel az összeköttetést kívánja. Anonim hozzáféréshez hagyja a DN-t és a jelszavat üresen.",
"User DN" : "Alap DN",
"For anonymous access, leave DN and Password empty." : "Az anonim eléréshez hagyja üresen a DN és Jelszó mezőket.",
"Password" : "Jelszó",
"Save credentials" : "Felhasználóadatok mentése",
"Base DN" : "Alap DN",
"One Base DN per line" : "Soronként egy alap DN",
"You can specify Base DN for users and groups in the Advanced tab" : "A felhasználók és csoportok alap DN-jét a Speciális lapon adhatja meg",
@@ -180,14 +175,12 @@ OC.L10N.register(
"Usernames are used to store and assign metadata. In order to precisely identify and recognize users, each LDAP user will have an internal username. This requires a mapping from username to LDAP user. The created username is mapped to the UUID of the LDAP user. Additionally the DN is cached as well to reduce LDAP interaction, but it is not used for identification. If the DN changes, the changes will be found. The internal username is used all over. Clearing the mappings will have leftovers everywhere. Clearing the mappings is not configuration sensitive, it affects all LDAP configurations! Never clear the mappings in a production environment, only in a testing or experimental stage." : "A felhasználónevek a metaadatok kezeléséhez és tárolásához vannak felhasználva. Annak érdekében, hogy teljes mértékben azonosítható legyen egy felhasználó, minden LDAP felhasználó kapni fog egy belső felhasználónevet. Ez egy hozzárendelést igényel az eredeti felhasználónév és az LDAP fiók között. A létrejött felhasználónév hozzárendelődik az LDAP fiók UUID értékéhez. Emellett a DN gyorsítótárazott, hogy csökkentse az LDAP interakciók számát, de nincs használva azonosítás céljából. Ha a DN megváltozik, a rendszer észleli ezeket a változásokat. A belső felhasználónév van mindenhol használva a rendszeren belül. A hozzárendelések törlése adattöredékeket hagy maga után. A hozzárendelések ürítése nem beállításfüggő, minden LDAP beállításra hatással van. Soha ne ürítse éles rendszeren a hozzárendeléseket, csak tesztelési vagy kísérleti szakaszban.",
"Clear Username-LDAP User Mapping" : "FelhasználónévLDAP felhasználó hozzárendelés törlése",
"Clear Groupname-LDAP Group Mapping" : "CsoportLDAP csoport hozzárendelés törlése",
"Please renew your password" : "Kérjük, újítsa meg jelszavát",
"An internal error occurred." : "Belső hiba történt.",
"Please try again or contact your administrator." : "Próbálja meg újra, vagy lépjen kapcsolatba a rendszergazdával.",
"Wrong password." : "Hibás jelszó.",
"Current password" : "Jelenlegi jelszó",
"New password" : "Új jelszó",
"Cancel" : "Mégse",
"Renewing…" : "Megújítás folyamatban...",
"Renew password" : "Jelszó megújítása",
"Confirm action" : "Művelet megerősítése",
"Are you sure you want to permanently delete this LDAP configuration? This cannot be undone." : "Biztos, hogy végleg törli ezt az LDAP konfigurációt? Ez nem vonható vissza.",

Some files were not shown because too many files have changed in this diff Show More