Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 934ad51959 |
@@ -47,7 +47,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
php-versions: [ '8.2', '8.3', '8.4', '8.5' ]
|
||||
php-versions: [ '8.2', '8.3', '8.4' ]
|
||||
|
||||
name: php-lint
|
||||
|
||||
|
||||
@@ -26,10 +26,12 @@ jobs:
|
||||
|
||||
if: ${{ github.repository_owner != 'nextcloud-gmbh' }}
|
||||
|
||||
container: shivammathur/node:latest-i386
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php-versions: ["8.4"]
|
||||
php-versions: ["8.2", "8.3", "8.4"]
|
||||
|
||||
steps:
|
||||
- name: Checkout server
|
||||
@@ -38,22 +40,32 @@ jobs:
|
||||
persist-credentials: false
|
||||
submodules: true
|
||||
|
||||
- name: Set up dependencies
|
||||
uses: docker://ghcr.io/nextcloud/continuous-integration-php8.4-32bit:latest
|
||||
- name: Install tools
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y ffmpeg imagemagick libmagickcore-6.q16-3-extra
|
||||
|
||||
- name: Set up php ${{ matrix.php-versions }}
|
||||
uses: shivammathur/setup-php@bf6b4fbd49ca58e4608c9c89fba0b8d90bd2a39f #v2.35.5
|
||||
with:
|
||||
args: /bin/sh -c "
|
||||
git config --global --add safe.directory /github/workspace &&
|
||||
composer install --no-interaction"
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, imagick, intl, json, libxml, mbstring, openssl, pcntl, posix, redis, session, simplexml, xmlreader, xmlwriter, zip, zlib, sqlite, pdo_sqlite, apcu, ldap
|
||||
coverage: none
|
||||
ini-file: development
|
||||
ini-values: apc.enabled=on, apc.enable_cli=on, disable_functions= # https://github.com/shivammathur/setup-php/discussions/573
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set up dependencies
|
||||
run: composer i
|
||||
|
||||
- name: Set up Nextcloud
|
||||
uses: docker://ghcr.io/nextcloud/continuous-integration-php8.4-32bit:latest
|
||||
with:
|
||||
args: /bin/sh -c "
|
||||
mkdir data &&
|
||||
./occ maintenance:install --verbose --database=sqlite --database-name=nextcloud --database-user=autotest --database-pass=rootpassword --admin-user admin --admin-pass admin &&
|
||||
php -f tests/enable_all.php"
|
||||
env:
|
||||
DB_PORT: 4444
|
||||
run: |
|
||||
mkdir data
|
||||
./occ maintenance:install --verbose --database=sqlite --database-name=nextcloud --database-host=127.0.0.1 --database-port=$DB_PORT --database-user=autotest --database-pass=rootpassword --admin-user admin --admin-pass admin
|
||||
php -f tests/enable_all.php
|
||||
|
||||
- name: PHPUnit
|
||||
uses: docker://ghcr.io/nextcloud/continuous-integration-php8.4-32bit:latest
|
||||
with:
|
||||
args: /bin/sh -c "composer run test -- --exclude-group PRIMARY-azure,PRIMARY-s3,PRIMARY-swift,Memcached,Redis,RoutingWeirdness"
|
||||
run: composer run test -- --exclude-group PRIMARY-azure --exclude-group PRIMARY-s3 --exclude-group PRIMARY-swift --exclude-group Memcached --exclude-group Redis --exclude-group RoutingWeirdness
|
||||
|
||||
@@ -60,15 +60,13 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php-versions: ['8.2']
|
||||
mariadb-versions: ['10.6', '10.11', '11.4', '11.8']
|
||||
mariadb-versions: ['10.3', '10.6', '10.11', '11.4', '11.8']
|
||||
include:
|
||||
- php-versions: '8.3'
|
||||
mariadb-versions: '10.11'
|
||||
coverage: ${{ github.event_name != 'pull_request' }}
|
||||
- php-versions: '8.4'
|
||||
mariadb-versions: '11.8'
|
||||
- php-versions: '8.5'
|
||||
mariadb-versions: '11.8'
|
||||
|
||||
name: MariaDB ${{ matrix.mariadb-versions }} (PHP ${{ matrix.php-versions }}) - database tests
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php-versions: ['8.3', '8.4', '8.5']
|
||||
php-versions: ['8.3', '8.4']
|
||||
include:
|
||||
- php-versions: '8.2'
|
||||
coverage: ${{ github.event_name != 'pull_request' }}
|
||||
|
||||
@@ -67,8 +67,6 @@ jobs:
|
||||
coverage: ${{ github.event_name != 'pull_request' }}
|
||||
- mysql-versions: '8.4'
|
||||
php-versions: '8.4'
|
||||
- mysql-versions: '8.4'
|
||||
php-versions: '8.5'
|
||||
|
||||
name: MySQL ${{ matrix.mysql-versions }} (PHP ${{ matrix.php-versions }}) - database tests
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php-versions: ['8.3', '8.4', '8.5']
|
||||
php-versions: ['8.3', '8.4']
|
||||
include:
|
||||
- php-versions: '8.2'
|
||||
coverage: ${{ github.event_name != 'pull_request' }}
|
||||
|
||||
@@ -69,8 +69,6 @@ jobs:
|
||||
php-versions: '8.3'
|
||||
- oracle-versions: '23'
|
||||
php-versions: '8.4'
|
||||
- oracle-versions: '23'
|
||||
php-versions: '8.5'
|
||||
|
||||
name: Oracle ${{ matrix.oracle-versions }} (PHP ${{ matrix.php-versions }}) - database tests
|
||||
|
||||
|
||||
@@ -68,8 +68,6 @@ jobs:
|
||||
coverage: ${{ github.event_name != 'pull_request' }}
|
||||
- php-versions: '8.4'
|
||||
postgres-versions: '18'
|
||||
- php-versions: '8.5'
|
||||
postgres-versions: '18'
|
||||
|
||||
name: PostgreSQL ${{ matrix.postgres-versions }} (PHP ${{ matrix.php-versions }}) - database tests
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php-versions: ['8.3', '8.4', '8.5']
|
||||
php-versions: ['8.3', '8.4']
|
||||
include:
|
||||
- php-versions: '8.2'
|
||||
coverage: ${{ github.event_name != 'pull_request' }}
|
||||
|
||||
+1
-1
Submodule 3rdparty updated: 086aa0783e...a030dd69ea
@@ -148,6 +148,18 @@ class SharingEventListener extends Action implements IEventListener {
|
||||
'id',
|
||||
]
|
||||
),
|
||||
IShare::TYPE_SCIENCEMESH => $this->log(
|
||||
'The %s "%s" with ID "%s" has been shared to the sciencemesh user "%s" with permissions "%s" (Share ID: %s)',
|
||||
$params,
|
||||
[
|
||||
'itemType',
|
||||
'path',
|
||||
'itemSource',
|
||||
'shareWith',
|
||||
'permissions',
|
||||
'id',
|
||||
]
|
||||
),
|
||||
default => null
|
||||
};
|
||||
}
|
||||
@@ -262,6 +274,17 @@ class SharingEventListener extends Action implements IEventListener {
|
||||
'id',
|
||||
]
|
||||
),
|
||||
IShare::TYPE_SCIENCEMESH => $this->log(
|
||||
'The %s "%s" with ID "%s" has been unshared from the sciencemesh user "%s" (Share ID: %s)',
|
||||
$params,
|
||||
[
|
||||
'itemType',
|
||||
'fileTarget',
|
||||
'itemSource',
|
||||
'shareWith',
|
||||
'id',
|
||||
]
|
||||
),
|
||||
default => null
|
||||
};
|
||||
}
|
||||
|
||||
@@ -106,16 +106,14 @@ class RequestHandlerController extends Controller {
|
||||
#[NoCSRFRequired]
|
||||
#[BruteForceProtection(action: 'receiveFederatedShare')]
|
||||
public function addShare($shareWith, $name, $description, $providerId, $owner, $ownerDisplayName, $sharedBy, $sharedByDisplayName, $protocol, $shareType, $resourceType) {
|
||||
if (!$this->appConfig->getValueBool('core', OCMSignatoryManager::APPCONFIG_SIGN_DISABLED, lazy: true)) {
|
||||
try {
|
||||
// if request is signed and well signed, no exception are thrown
|
||||
// if request is not signed and host is known for not supporting signed request, no exception are thrown
|
||||
$signedRequest = $this->getSignedRequest();
|
||||
$this->confirmSignedOrigin($signedRequest, 'owner', $owner);
|
||||
} catch (IncomingRequestException $e) {
|
||||
$this->logger->warning('incoming request exception', ['exception' => $e]);
|
||||
return new JSONResponse(['message' => $e->getMessage(), 'validationErrors' => []], Http::STATUS_BAD_REQUEST);
|
||||
}
|
||||
try {
|
||||
// if request is signed and well signed, no exception are thrown
|
||||
// if request is not signed and host is known for not supporting signed request, no exception are thrown
|
||||
$signedRequest = $this->getSignedRequest();
|
||||
$this->confirmSignedOrigin($signedRequest, 'owner', $owner);
|
||||
} catch (IncomingRequestException $e) {
|
||||
$this->logger->warning('incoming request exception', ['exception' => $e]);
|
||||
return new JSONResponse(['message' => $e->getMessage(), 'validationErrors' => []], Http::STATUS_BAD_REQUEST);
|
||||
}
|
||||
|
||||
// check if all required parameters are set
|
||||
@@ -356,16 +354,14 @@ class RequestHandlerController extends Controller {
|
||||
);
|
||||
}
|
||||
|
||||
if (!$this->appConfig->getValueBool('core', OCMSignatoryManager::APPCONFIG_SIGN_DISABLED, lazy: true)) {
|
||||
try {
|
||||
// if request is signed and well signed, no exception are thrown
|
||||
// if request is not signed and host is known for not supporting signed request, no exception are thrown
|
||||
$signedRequest = $this->getSignedRequest();
|
||||
$this->confirmNotificationIdentity($signedRequest, $resourceType, $notification);
|
||||
} catch (IncomingRequestException $e) {
|
||||
$this->logger->warning('incoming request exception', ['exception' => $e]);
|
||||
return new JSONResponse(['message' => $e->getMessage(), 'validationErrors' => []], Http::STATUS_BAD_REQUEST);
|
||||
}
|
||||
try {
|
||||
// if request is signed and well signed, no exception are thrown
|
||||
// if request is not signed and host is known for not supporting signed request, no exception are thrown
|
||||
$signedRequest = $this->getSignedRequest();
|
||||
$this->confirmNotificationIdentity($signedRequest, $resourceType, $notification);
|
||||
} catch (IncomingRequestException $e) {
|
||||
$this->logger->warning('incoming request exception', ['exception' => $e]);
|
||||
return new JSONResponse(['message' => $e->getMessage(), 'validationErrors' => []], Http::STATUS_BAD_REQUEST);
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -504,6 +500,7 @@ class RequestHandlerController extends Controller {
|
||||
*
|
||||
* @param IIncomingSignedRequest|null $signedRequest
|
||||
* @param string $resourceType
|
||||
* @param string $sharedSecret
|
||||
*
|
||||
* @throws IncomingRequestException
|
||||
* @throws BadRequestException
|
||||
@@ -527,7 +524,7 @@ class RequestHandlerController extends Controller {
|
||||
return;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
throw new IncomingRequestException($e->getMessage(), previous: $e);
|
||||
throw new IncomingRequestException($e->getMessage());
|
||||
}
|
||||
|
||||
$this->confirmNotificationEntry($signedRequest, $identity);
|
||||
|
||||
+1
-1
@@ -252,7 +252,7 @@ OC.L10N.register(
|
||||
"Completed on %s" : "Erledigt am %s",
|
||||
"Due on %s by %s" : "Fällig am %s von %s",
|
||||
"Due on %s" : "Fällig am %s",
|
||||
"Welcome to Nextcloud Calendar!\n\nThis is a sample event - explore the flexibility of planning with Nextcloud Calendar by making any edits you want!\n\nWith Nextcloud Calendar, you can:\n- Create, edit, and manage events effortlessly.\n- Create multiple calendars and share them with teammates, friends, or family.\n- Check availability and display your busy times to others.\n- Seamlessly integrate with apps and devices via CalDAV.\n- Customize your experience: schedule recurring events, adjust notifications and other settings." : "Willkommen bei Nextcloud Calendar!\n\nDies ist ein Beispielereignis – entdecke die Flexibilität der Planung mit Nextcloud Calendar und nimm beliebige Änderungen vor!\n\nMit Nextcloud Calendar kannst du:\n– Ereignisse mühelos erstellen, bearbeiten und verwalten.\n– Mehrere Kalender erstellen und mit Teamkollegen, Freunden oder der Familie teilen.\n– Verfügbarkeit prüfen und deine Termine anderen anzeigen.\n– Nahtlose Integration mit Apps und Geräten über CalDAV.\n– Individuelle Gestaltung: Plane wiederkehrende Ereignisse, passe Benachrichtigungen und andere Einstellungen an.",
|
||||
"Welcome to Nextcloud Calendar!\n\nThis is a sample event - explore the flexibility of planning with Nextcloud Calendar by making any edits you want!\n\nWith Nextcloud Calendar, you can:\n- Create, edit, and manage events effortlessly.\n- Create multiple calendars and share them with teammates, friends, or family.\n- Check availability and display your busy times to others.\n- Seamlessly integrate with apps and devices via CalDAV.\n- Customize your experience: schedule recurring events, adjust notifications and other settings." : "Willkommen bei Nextcloud Calendar!\n\nDies ist ein Beispielereignis – entdecke die Flexibilität der Planung mit Nextcloud Calendar und nimm beliebige Änderungen vor!\n\nMit Nextcloud Calendar kannst du:\n– Ereignisse mühelos erstellen, bearbeiten und verwalten.\n– Mehrere Kalender erstellen und mit Teamkollegen, Freunden oder der Familie teilen.\n– Verfügbarkeit prüfen und Ihre Termine anderen anzeigen.\n– Nahtlose Integration mit Apps und Geräten über CalDAV.\n– Individuelle Gestaltung: Plane wiederkehrende Ereignisse, passe Benachrichtigungen und andere Einstellungen an.",
|
||||
"Example event - open me!" : "Beispielereignis – öffne mich!",
|
||||
"System Address Book" : "Systemadressbuch",
|
||||
"The system address book contains contact information for all users in your instance." : "Das Systemadressbuch enthält Kontaktinformationen für alle Benutzer in dieser Instanz.",
|
||||
|
||||
@@ -250,7 +250,7 @@
|
||||
"Completed on %s" : "Erledigt am %s",
|
||||
"Due on %s by %s" : "Fällig am %s von %s",
|
||||
"Due on %s" : "Fällig am %s",
|
||||
"Welcome to Nextcloud Calendar!\n\nThis is a sample event - explore the flexibility of planning with Nextcloud Calendar by making any edits you want!\n\nWith Nextcloud Calendar, you can:\n- Create, edit, and manage events effortlessly.\n- Create multiple calendars and share them with teammates, friends, or family.\n- Check availability and display your busy times to others.\n- Seamlessly integrate with apps and devices via CalDAV.\n- Customize your experience: schedule recurring events, adjust notifications and other settings." : "Willkommen bei Nextcloud Calendar!\n\nDies ist ein Beispielereignis – entdecke die Flexibilität der Planung mit Nextcloud Calendar und nimm beliebige Änderungen vor!\n\nMit Nextcloud Calendar kannst du:\n– Ereignisse mühelos erstellen, bearbeiten und verwalten.\n– Mehrere Kalender erstellen und mit Teamkollegen, Freunden oder der Familie teilen.\n– Verfügbarkeit prüfen und deine Termine anderen anzeigen.\n– Nahtlose Integration mit Apps und Geräten über CalDAV.\n– Individuelle Gestaltung: Plane wiederkehrende Ereignisse, passe Benachrichtigungen und andere Einstellungen an.",
|
||||
"Welcome to Nextcloud Calendar!\n\nThis is a sample event - explore the flexibility of planning with Nextcloud Calendar by making any edits you want!\n\nWith Nextcloud Calendar, you can:\n- Create, edit, and manage events effortlessly.\n- Create multiple calendars and share them with teammates, friends, or family.\n- Check availability and display your busy times to others.\n- Seamlessly integrate with apps and devices via CalDAV.\n- Customize your experience: schedule recurring events, adjust notifications and other settings." : "Willkommen bei Nextcloud Calendar!\n\nDies ist ein Beispielereignis – entdecke die Flexibilität der Planung mit Nextcloud Calendar und nimm beliebige Änderungen vor!\n\nMit Nextcloud Calendar kannst du:\n– Ereignisse mühelos erstellen, bearbeiten und verwalten.\n– Mehrere Kalender erstellen und mit Teamkollegen, Freunden oder der Familie teilen.\n– Verfügbarkeit prüfen und Ihre Termine anderen anzeigen.\n– Nahtlose Integration mit Apps und Geräten über CalDAV.\n– Individuelle Gestaltung: Plane wiederkehrende Ereignisse, passe Benachrichtigungen und andere Einstellungen an.",
|
||||
"Example event - open me!" : "Beispielereignis – öffne mich!",
|
||||
"System Address Book" : "Systemadressbuch",
|
||||
"The system address book contains contact information for all users in your instance." : "Das Systemadressbuch enthält Kontaktinformationen für alle Benutzer in dieser Instanz.",
|
||||
|
||||
@@ -73,19 +73,7 @@ OC.L10N.register(
|
||||
"Where: %s" : "قەيەردە: %s",
|
||||
"%1$s via %2$s" : "%1$s ئارقىلىق %2$s",
|
||||
"In the past on %1$s for the entire day" : "پۈتۈن كۈن ئۈچۈن ئۆتمۈشتىكى %1$s دا",
|
||||
"_In %n minute on %1$s for the entire day_::_In %n minutes on %1$s for the entire day_" : ["%1$s دا پۈتۈن كۈندە %n مىنۇتتا","%1$s دا پۈتۈن كۈندە %n مىنۇتتا"],
|
||||
"_In %n hour on %1$s for the entire day_::_In %n hours on %1$s for the entire day_" : ["%1$s دا پۈتۈن كۈندە %n سائەتتە","%1$s دا پۈتۈن كۈندە %n سائەتتە"],
|
||||
"_In %n day on %1$s for the entire day_::_In %n days on %1$s for the entire day_" : ["%1$s دا پۈتۈن كۈندە %n كۈندە","%1$s دا پۈتۈن كۈندە %n كۈندە"],
|
||||
"_In %n week on %1$s for the entire day_::_In %n weeks on %1$s for the entire day_" : ["%1$s دا پۈتۈن كۈندە %n ھەپتىدە","%1$s دا پۈتۈن كۈندە %n ھەپتىدە"],
|
||||
"_In %n month on %1$s for the entire day_::_In %n months on %1$s for the entire day_" : ["%1$s دا پۈتۈن كۈندە %n ئايدا","%1$s دا پۈتۈن كۈندە %n ئايدا"],
|
||||
"_In %n year on %1$s for the entire day_::_In %n years on %1$s for the entire day_" : ["%1$s دا پۈتۈن كۈندە %n يىلدا","%1$s دا پۈتۈن كۈندە %n يىلدا"],
|
||||
"In the past on %1$s between %2$s - %3$s" : "ئۆتمۈشتىكى %1$s دە %2$s - %3$s ئارىسىدا",
|
||||
"_In %n minute on %1$s between %2$s - %3$s_::_In %n minutes on %1$s between %2$s - %3$s_" : ["%1$s دە %2$s - %3$s ئارلىقىدا %n مىنۇتتا","%1$s دە %2$s - %3$s ئارلىقىدا %n مىنۇتتا"],
|
||||
"_In %n hour on %1$s between %2$s - %3$s_::_In %n hours on %1$s between %2$s - %3$s_" : ["%1$s دە %2$s - %3$s ئارلىقىدا %n سائەتتە","%1$s دە %2$s - %3$s ئارلىقىدا %n سائەتتە"],
|
||||
"_In %n day on %1$s between %2$s - %3$s_::_In %n days on %1$s between %2$s - %3$s_" : ["%1$s دە %2$s - %3$s ئارلىقىدا %n كۈندە","%1$s دە %2$s - %3$s ئارلىقىدا %n كۈندە"],
|
||||
"_In %n week on %1$s between %2$s - %3$s_::_In %n weeks on %1$s between %2$s - %3$s_" : ["%1$s دە %2$s - %3$s ئارلىقىدا %n ھەپتىدە","%1$s دە %2$s - %3$s ئارلىقىدا %n ھەپتىدە"],
|
||||
"_In %n month on %1$s between %2$s - %3$s_::_In %n months on %1$s between %2$s - %3$s_" : ["%1$s دە %2$s - %3$s ئارلىقىدا %n ئايدا","%1$s دە %2$s - %3$s ئارلىقىدا %n ئايدا"],
|
||||
"_In %n year on %1$s between %2$s - %3$s_::_In %n years on %1$s between %2$s - %3$s_" : ["%1$s دە %2$s - %3$s ئارلىقىدا %n يىلدا","%1$s دە %2$s - %3$s ئارلىقىدا %n يىلدا"],
|
||||
"Could not generate when statement" : "بايان قىلغاندا ھاسىل قىلالمىدى",
|
||||
"Every Day for the entire day" : "ھەر بىر كۈن پۈتۈن بىر كۈن",
|
||||
"Every Day for the entire day until %1$s" : "ھەر بىر كۈن پۈتۈن كۈن %1$s غىچە",
|
||||
@@ -123,26 +111,8 @@ OC.L10N.register(
|
||||
"On specific dates for the entire day until %1$s" : "%1$s غىچە بەلگىلىك چېسلادا پۈتۈن كۈن",
|
||||
"On specific dates between %1$s - %2$s until %3$s" : "%1$s - %2$s ئارلىقىدىكى %3$s غىچە بولغان بەلگىلىك چېسلادا",
|
||||
"In the past on %1$s" : "ئۆتمۈشتە %1$s دا",
|
||||
"_In %n minute on %1$s_::_In %n minutes on %1$s_" : ["%1$s دا %n مىنۇتتا","%1$s دا %n مىنۇتتا"],
|
||||
"_In %n hour on %1$s_::_In %n hours on %1$s_" : ["%1$s دا %n سائەتتە","%1$s دا %n سائەتتە"],
|
||||
"_In %n day on %1$s_::_In %n days on %1$s_" : ["%1$s دا %n كۈندە","%1$s دا %n كۈندە"],
|
||||
"_In %n week on %1$s_::_In %n weeks on %1$s_" : ["%1$s دا %n ھەپتىدە","%1$s دا %n ھەپتىدە"],
|
||||
"_In %n month on %1$s_::_In %n months on %1$s_" : ["%1$s دا %n ئايدا","%1$s دا %n ئايدا"],
|
||||
"_In %n year on %1$s_::_In %n years on %1$s_" : ["%1$s دا %n يىلدا","%1$s دا %n يىلدا"],
|
||||
"In the past on %1$s then on %2$s" : "ئۆتمۈشتە %1$s دە ئاندىن %2$s دا",
|
||||
"_In %n minute on %1$s then on %2$s_::_In %n minutes on %1$s then on %2$s_" : ["%1$s دا %n مىنۇتتا ئاندىن %2$s دا","%1$s دا %n مىنۇتتا ئاندىن %2$s دا"],
|
||||
"_In %n hour on %1$s then on %2$s_::_In %n hours on %1$s then on %2$s_" : ["%1$s دا %n سائەتتە ئاندىن %2$s دا","%1$s دا %n سائەتتە ئاندىن %2$s دا"],
|
||||
"_In %n day on %1$s then on %2$s_::_In %n days on %1$s then on %2$s_" : ["%1$s دا %n كۈندە ئاندىن %2$s دا","%1$s دا %n كۈندە ئاندىن %2$s دا"],
|
||||
"_In %n week on %1$s then on %2$s_::_In %n weeks on %1$s then on %2$s_" : ["%1$s دا %n ھەپتىدە ئاندىن %2$s دا","%1$s دا %n ھەپتىدە ئاندىن %2$s دا"],
|
||||
"_In %n month on %1$s then on %2$s_::_In %n months on %1$s then on %2$s_" : ["%1$s دا %n ئايدا ئاندىن %2$s دا","%1$s دا %n ئايدا ئاندىن %2$s دا"],
|
||||
"_In %n year on %1$s then on %2$s_::_In %n years on %1$s then on %2$s_" : ["%1$s دا %n يىلدا ئاندىن %2$s دا","%1$s دا %n يىلدا ئاندىن %2$s دا"],
|
||||
"In the past on %1$s then on %2$s and %3$s" : "ئۆتمۈشتە %1$s دە ئاندىن %2$s بىلەن %3$s دە",
|
||||
"_In %n minute on %1$s then on %2$s and %3$s_::_In %n minutes on %1$s then on %2$s and %3$s_" : ["%1$s دا %n مىنۇتتا ئاندىن كىيىن %2$s بىلەن %3$s دا","%1$s دا %n مىنۇتتا ئاندىن كىيىن %2$s بىلەن %3$s دا"],
|
||||
"_In %n hour on %1$s then on %2$s and %3$s_::_In %n hours on %1$s then on %2$s and %3$s_" : ["%1$s دا %n سائەتتە ئاندىن كىيىن %2$s بىلەن %3$s دا","%1$s دا %n سائەتتە ئاندىن كىيىن %2$s بىلەن %3$s دا"],
|
||||
"_In %n day on %1$s then on %2$s and %3$s_::_In %n days on %1$s then on %2$s and %3$s_" : ["%1$s دا %n كۈندە ئاندىن كىيىن %2$s بىلەن %3$s دا","%1$s دا %n كۈندە ئاندىن كىيىن %2$s بىلەن %3$s دا"],
|
||||
"_In %n week on %1$s then on %2$s and %3$s_::_In %n weeks on %1$s then on %2$s and %3$s_" : ["%1$s دا %n ھەپتىدە ئاندىن كىيىن %2$s بىلەن %3$s دا","%1$s دا %n ھەپتىدە ئاندىن كىيىن %2$s بىلەن %3$s دا"],
|
||||
"_In %n month on %1$s then on %2$s and %3$s_::_In %n months on %1$s then on %2$s and %3$s_" : ["%1$s دا %n ئايدا ئاندىن كىيىن %2$s بىلەن %3$s دا","%1$s دا %n ئايدا ئاندىن كىيىن %2$s بىلەن %3$s دا"],
|
||||
"_In %n year on %1$s then on %2$s and %3$s_::_In %n years on %1$s then on %2$s and %3$s_" : ["%1$s دا %n يىلدا ئاندىن كىيىن %2$s بىلەن %3$s دا","%1$s دا %n يىلدا ئاندىن كىيىن %2$s بىلەن %3$s دا"],
|
||||
"Could not generate next recurrence statement" : "كېيىنكى تەكرارلىنىش باياناتىنى ھاسىل قىلالمىدى",
|
||||
"Cancelled: %1$s" : "ئەمەلدىن قالدۇرۇلدى: %1$s",
|
||||
"\"%1$s\" has been canceled" : "\"%1$s\" ئەمەلدىن قالدۇرۇلدى",
|
||||
|
||||
@@ -71,19 +71,7 @@
|
||||
"Where: %s" : "قەيەردە: %s",
|
||||
"%1$s via %2$s" : "%1$s ئارقىلىق %2$s",
|
||||
"In the past on %1$s for the entire day" : "پۈتۈن كۈن ئۈچۈن ئۆتمۈشتىكى %1$s دا",
|
||||
"_In %n minute on %1$s for the entire day_::_In %n minutes on %1$s for the entire day_" : ["%1$s دا پۈتۈن كۈندە %n مىنۇتتا","%1$s دا پۈتۈن كۈندە %n مىنۇتتا"],
|
||||
"_In %n hour on %1$s for the entire day_::_In %n hours on %1$s for the entire day_" : ["%1$s دا پۈتۈن كۈندە %n سائەتتە","%1$s دا پۈتۈن كۈندە %n سائەتتە"],
|
||||
"_In %n day on %1$s for the entire day_::_In %n days on %1$s for the entire day_" : ["%1$s دا پۈتۈن كۈندە %n كۈندە","%1$s دا پۈتۈن كۈندە %n كۈندە"],
|
||||
"_In %n week on %1$s for the entire day_::_In %n weeks on %1$s for the entire day_" : ["%1$s دا پۈتۈن كۈندە %n ھەپتىدە","%1$s دا پۈتۈن كۈندە %n ھەپتىدە"],
|
||||
"_In %n month on %1$s for the entire day_::_In %n months on %1$s for the entire day_" : ["%1$s دا پۈتۈن كۈندە %n ئايدا","%1$s دا پۈتۈن كۈندە %n ئايدا"],
|
||||
"_In %n year on %1$s for the entire day_::_In %n years on %1$s for the entire day_" : ["%1$s دا پۈتۈن كۈندە %n يىلدا","%1$s دا پۈتۈن كۈندە %n يىلدا"],
|
||||
"In the past on %1$s between %2$s - %3$s" : "ئۆتمۈشتىكى %1$s دە %2$s - %3$s ئارىسىدا",
|
||||
"_In %n minute on %1$s between %2$s - %3$s_::_In %n minutes on %1$s between %2$s - %3$s_" : ["%1$s دە %2$s - %3$s ئارلىقىدا %n مىنۇتتا","%1$s دە %2$s - %3$s ئارلىقىدا %n مىنۇتتا"],
|
||||
"_In %n hour on %1$s between %2$s - %3$s_::_In %n hours on %1$s between %2$s - %3$s_" : ["%1$s دە %2$s - %3$s ئارلىقىدا %n سائەتتە","%1$s دە %2$s - %3$s ئارلىقىدا %n سائەتتە"],
|
||||
"_In %n day on %1$s between %2$s - %3$s_::_In %n days on %1$s between %2$s - %3$s_" : ["%1$s دە %2$s - %3$s ئارلىقىدا %n كۈندە","%1$s دە %2$s - %3$s ئارلىقىدا %n كۈندە"],
|
||||
"_In %n week on %1$s between %2$s - %3$s_::_In %n weeks on %1$s between %2$s - %3$s_" : ["%1$s دە %2$s - %3$s ئارلىقىدا %n ھەپتىدە","%1$s دە %2$s - %3$s ئارلىقىدا %n ھەپتىدە"],
|
||||
"_In %n month on %1$s between %2$s - %3$s_::_In %n months on %1$s between %2$s - %3$s_" : ["%1$s دە %2$s - %3$s ئارلىقىدا %n ئايدا","%1$s دە %2$s - %3$s ئارلىقىدا %n ئايدا"],
|
||||
"_In %n year on %1$s between %2$s - %3$s_::_In %n years on %1$s between %2$s - %3$s_" : ["%1$s دە %2$s - %3$s ئارلىقىدا %n يىلدا","%1$s دە %2$s - %3$s ئارلىقىدا %n يىلدا"],
|
||||
"Could not generate when statement" : "بايان قىلغاندا ھاسىل قىلالمىدى",
|
||||
"Every Day for the entire day" : "ھەر بىر كۈن پۈتۈن بىر كۈن",
|
||||
"Every Day for the entire day until %1$s" : "ھەر بىر كۈن پۈتۈن كۈن %1$s غىچە",
|
||||
@@ -121,26 +109,8 @@
|
||||
"On specific dates for the entire day until %1$s" : "%1$s غىچە بەلگىلىك چېسلادا پۈتۈن كۈن",
|
||||
"On specific dates between %1$s - %2$s until %3$s" : "%1$s - %2$s ئارلىقىدىكى %3$s غىچە بولغان بەلگىلىك چېسلادا",
|
||||
"In the past on %1$s" : "ئۆتمۈشتە %1$s دا",
|
||||
"_In %n minute on %1$s_::_In %n minutes on %1$s_" : ["%1$s دا %n مىنۇتتا","%1$s دا %n مىنۇتتا"],
|
||||
"_In %n hour on %1$s_::_In %n hours on %1$s_" : ["%1$s دا %n سائەتتە","%1$s دا %n سائەتتە"],
|
||||
"_In %n day on %1$s_::_In %n days on %1$s_" : ["%1$s دا %n كۈندە","%1$s دا %n كۈندە"],
|
||||
"_In %n week on %1$s_::_In %n weeks on %1$s_" : ["%1$s دا %n ھەپتىدە","%1$s دا %n ھەپتىدە"],
|
||||
"_In %n month on %1$s_::_In %n months on %1$s_" : ["%1$s دا %n ئايدا","%1$s دا %n ئايدا"],
|
||||
"_In %n year on %1$s_::_In %n years on %1$s_" : ["%1$s دا %n يىلدا","%1$s دا %n يىلدا"],
|
||||
"In the past on %1$s then on %2$s" : "ئۆتمۈشتە %1$s دە ئاندىن %2$s دا",
|
||||
"_In %n minute on %1$s then on %2$s_::_In %n minutes on %1$s then on %2$s_" : ["%1$s دا %n مىنۇتتا ئاندىن %2$s دا","%1$s دا %n مىنۇتتا ئاندىن %2$s دا"],
|
||||
"_In %n hour on %1$s then on %2$s_::_In %n hours on %1$s then on %2$s_" : ["%1$s دا %n سائەتتە ئاندىن %2$s دا","%1$s دا %n سائەتتە ئاندىن %2$s دا"],
|
||||
"_In %n day on %1$s then on %2$s_::_In %n days on %1$s then on %2$s_" : ["%1$s دا %n كۈندە ئاندىن %2$s دا","%1$s دا %n كۈندە ئاندىن %2$s دا"],
|
||||
"_In %n week on %1$s then on %2$s_::_In %n weeks on %1$s then on %2$s_" : ["%1$s دا %n ھەپتىدە ئاندىن %2$s دا","%1$s دا %n ھەپتىدە ئاندىن %2$s دا"],
|
||||
"_In %n month on %1$s then on %2$s_::_In %n months on %1$s then on %2$s_" : ["%1$s دا %n ئايدا ئاندىن %2$s دا","%1$s دا %n ئايدا ئاندىن %2$s دا"],
|
||||
"_In %n year on %1$s then on %2$s_::_In %n years on %1$s then on %2$s_" : ["%1$s دا %n يىلدا ئاندىن %2$s دا","%1$s دا %n يىلدا ئاندىن %2$s دا"],
|
||||
"In the past on %1$s then on %2$s and %3$s" : "ئۆتمۈشتە %1$s دە ئاندىن %2$s بىلەن %3$s دە",
|
||||
"_In %n minute on %1$s then on %2$s and %3$s_::_In %n minutes on %1$s then on %2$s and %3$s_" : ["%1$s دا %n مىنۇتتا ئاندىن كىيىن %2$s بىلەن %3$s دا","%1$s دا %n مىنۇتتا ئاندىن كىيىن %2$s بىلەن %3$s دا"],
|
||||
"_In %n hour on %1$s then on %2$s and %3$s_::_In %n hours on %1$s then on %2$s and %3$s_" : ["%1$s دا %n سائەتتە ئاندىن كىيىن %2$s بىلەن %3$s دا","%1$s دا %n سائەتتە ئاندىن كىيىن %2$s بىلەن %3$s دا"],
|
||||
"_In %n day on %1$s then on %2$s and %3$s_::_In %n days on %1$s then on %2$s and %3$s_" : ["%1$s دا %n كۈندە ئاندىن كىيىن %2$s بىلەن %3$s دا","%1$s دا %n كۈندە ئاندىن كىيىن %2$s بىلەن %3$s دا"],
|
||||
"_In %n week on %1$s then on %2$s and %3$s_::_In %n weeks on %1$s then on %2$s and %3$s_" : ["%1$s دا %n ھەپتىدە ئاندىن كىيىن %2$s بىلەن %3$s دا","%1$s دا %n ھەپتىدە ئاندىن كىيىن %2$s بىلەن %3$s دا"],
|
||||
"_In %n month on %1$s then on %2$s and %3$s_::_In %n months on %1$s then on %2$s and %3$s_" : ["%1$s دا %n ئايدا ئاندىن كىيىن %2$s بىلەن %3$s دا","%1$s دا %n ئايدا ئاندىن كىيىن %2$s بىلەن %3$s دا"],
|
||||
"_In %n year on %1$s then on %2$s and %3$s_::_In %n years on %1$s then on %2$s and %3$s_" : ["%1$s دا %n يىلدا ئاندىن كىيىن %2$s بىلەن %3$s دا","%1$s دا %n يىلدا ئاندىن كىيىن %2$s بىلەن %3$s دا"],
|
||||
"Could not generate next recurrence statement" : "كېيىنكى تەكرارلىنىش باياناتىنى ھاسىل قىلالمىدى",
|
||||
"Cancelled: %1$s" : "ئەمەلدىن قالدۇرۇلدى: %1$s",
|
||||
"\"%1$s\" has been canceled" : "\"%1$s\" ئەمەلدىن قالدۇرۇلدى",
|
||||
|
||||
@@ -11,8 +11,7 @@ namespace OCA\DAV\CalDAV\Schedule;
|
||||
use OC\URLGenerator;
|
||||
use OCA\DAV\CalDAV\EventReader;
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\Config\IUserConfig;
|
||||
use OCP\IAppConfig;
|
||||
use OCP\IConfig;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\IL10N;
|
||||
use OCP\IUserManager;
|
||||
@@ -42,13 +41,12 @@ class IMipService {
|
||||
|
||||
public function __construct(
|
||||
private URLGenerator $urlGenerator,
|
||||
private IConfig $config,
|
||||
private IDBConnection $db,
|
||||
private ISecureRandom $random,
|
||||
private L10NFactory $l10nFactory,
|
||||
private ITimeFactory $timeFactory,
|
||||
private readonly IUserManager $userManager,
|
||||
private readonly IUserConfig $userConfig,
|
||||
private readonly IAppConfig $appConfig,
|
||||
) {
|
||||
$language = $this->l10nFactory->findGenericLanguage();
|
||||
$locale = $this->l10nFactory->findLocale($language);
|
||||
@@ -889,8 +887,8 @@ class IMipService {
|
||||
$users = $this->userManager->getByEmail($userAddress);
|
||||
if ($users !== []) {
|
||||
$user = array_shift($users);
|
||||
$language = $this->userConfig->getValueString($user->getUID(), 'core', 'lang', '') ?: null;
|
||||
$locale = $this->userConfig->getValueString($user->getUID(), 'core', 'locale', '') ?: null;
|
||||
$language = $this->config->getUserValue($user->getUID(), 'core', 'lang', null);
|
||||
$locale = $this->config->getUserValue($user->getUID(), 'core', 'locale', null);
|
||||
}
|
||||
// fallback to attendee LANGUAGE parameter if language not set
|
||||
if ($language === null && isset($attendee['LANGUAGE']) && $attendee['LANGUAGE'] instanceof Parameter) {
|
||||
@@ -998,7 +996,7 @@ class IMipService {
|
||||
* The default is 'no', which matches old behavior, and is privacy preserving.
|
||||
*
|
||||
* To enable including attendees in invitation emails:
|
||||
* % php occ config:app:set dav invitation_list_attendees --value yes --type bool
|
||||
* % php occ config:app:set dav invitation_list_attendees --value yes
|
||||
*
|
||||
* @param IEMailTemplate $template
|
||||
* @param IL10N $this->l10n
|
||||
@@ -1006,12 +1004,12 @@ class IMipService {
|
||||
* @author brad2014 on github.com
|
||||
*/
|
||||
public function addAttendees(IEMailTemplate $template, VEvent $vevent) {
|
||||
if (!$this->appConfig->getValueBool('dav', 'invitation_list_attendees')) {
|
||||
if ($this->config->getAppValue('dav', 'invitation_list_attendees', 'no') === 'no') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($vevent->ORGANIZER)) {
|
||||
/** @var Property&Property\ICalendar\CalAddress $organizer */
|
||||
/** @var Property | Property\ICalendar\CalAddress $organizer */
|
||||
$organizer = $vevent->ORGANIZER;
|
||||
$organizerEmail = substr($organizer->getNormalizedValue(), 7);
|
||||
/** @var string|null $organizerName */
|
||||
@@ -1041,14 +1039,8 @@ class IMipService {
|
||||
$attendeesHTML = [];
|
||||
$attendeesText = [];
|
||||
foreach ($attendees as $attendee) {
|
||||
/** @var Property&Property\ICalendar\CalAddress $attendee */
|
||||
$attendeeEmail = substr($attendee->getNormalizedValue(), 7);
|
||||
$attendeeName = null;
|
||||
if (isset($attendee['CN'])) {
|
||||
/** @var Parameter $cn */
|
||||
$cn = $attendee['CN'];
|
||||
$attendeeName = $cn->getValue();
|
||||
}
|
||||
$attendeeName = isset($attendee['CN']) ? $attendee['CN']->getValue() : null;
|
||||
$attendeeHTML = sprintf('<a href="%s">%s</a>',
|
||||
htmlspecialchars($attendee->getNormalizedValue()),
|
||||
htmlspecialchars($attendeeName ?: $attendeeEmail));
|
||||
|
||||
@@ -12,7 +12,6 @@ namespace OCA\DAV\CalDAV;
|
||||
use OCA\DAV\Db\PropertyMapper;
|
||||
use OCP\Calendar\ICalendar;
|
||||
use OCP\Calendar\IManager;
|
||||
use OCP\Config\IUserConfig;
|
||||
use OCP\IConfig;
|
||||
use Sabre\VObject\Component\VCalendar;
|
||||
use Sabre\VObject\Component\VTimeZone;
|
||||
@@ -23,14 +22,13 @@ class TimezoneService {
|
||||
|
||||
public function __construct(
|
||||
private IConfig $config,
|
||||
private IUserConfig $userConfig,
|
||||
private PropertyMapper $propertyMapper,
|
||||
private IManager $calendarManager,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getUserTimezone(string $userId): ?string {
|
||||
$fromConfig = $this->userConfig->getValueString(
|
||||
$fromConfig = $this->config->getUserValue(
|
||||
$userId,
|
||||
'core',
|
||||
'timezone',
|
||||
@@ -53,7 +51,7 @@ class TimezoneService {
|
||||
}
|
||||
|
||||
$principal = 'principals/users/' . $userId;
|
||||
$uri = $this->userConfig->getValueString($userId, 'dav', 'defaultCalendar', CalDavBackend::PERSONAL_CALENDAR_URI);
|
||||
$uri = $this->config->getUserValue($userId, 'dav', 'defaultCalendar', CalDavBackend::PERSONAL_CALENDAR_URI);
|
||||
$calendars = $this->calendarManager->getCalendarsForPrincipal($principal);
|
||||
|
||||
/** @var ?VTimeZone $personalCalendarTimezone */
|
||||
|
||||
@@ -99,6 +99,7 @@ class SharesPlugin extends \Sabre\DAV\ServerPlugin {
|
||||
IShare::TYPE_ROOM,
|
||||
IShare::TYPE_CIRCLE,
|
||||
IShare::TYPE_DECK,
|
||||
IShare::TYPE_SCIENCEMESH,
|
||||
];
|
||||
|
||||
foreach ($requestedShareTypes as $requestedShareType) {
|
||||
|
||||
@@ -14,7 +14,6 @@ use OCP\Files\IRootFolder;
|
||||
use OCP\Files\NotFoundException;
|
||||
use OCP\IUserSession;
|
||||
use Sabre\DAV\Exception\Forbidden;
|
||||
use Sabre\DAV\Exception\NotFound;
|
||||
use Sabre\DAV\ICollection;
|
||||
|
||||
class UploadHome implements ICollection {
|
||||
@@ -73,12 +72,7 @@ class UploadHome implements ICollection {
|
||||
}
|
||||
|
||||
public function childExists($name): bool {
|
||||
try {
|
||||
$this->getChild($name);
|
||||
return true;
|
||||
} catch (NotFound $e) {
|
||||
return false;
|
||||
}
|
||||
return !is_null($this->getChild($name));
|
||||
}
|
||||
|
||||
public function delete() {
|
||||
|
||||
@@ -220,7 +220,8 @@
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"fileId"
|
||||
"fileId",
|
||||
"expirationTime"
|
||||
],
|
||||
"properties": {
|
||||
"fileId": {
|
||||
|
||||
@@ -14,9 +14,9 @@ use OCA\DAV\CalDAV\EventComparisonService;
|
||||
use OCA\DAV\CalDAV\Schedule\IMipPlugin;
|
||||
use OCA\DAV\CalDAV\Schedule\IMipService;
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\Config\IUserConfig;
|
||||
use OCP\Defaults;
|
||||
use OCP\IAppConfig;
|
||||
use OCP\IConfig;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\IUser;
|
||||
@@ -46,7 +46,7 @@ class IMipPluginCharsetTest extends TestCase {
|
||||
// Dependencies
|
||||
private Defaults&MockObject $defaults;
|
||||
private IAppConfig&MockObject $appConfig;
|
||||
private IUserConfig&MockObject $userConfig;
|
||||
private IConfig&MockObject $config;
|
||||
private IDBConnection&MockObject $db;
|
||||
private IFactory $l10nFactory;
|
||||
private IManager&MockObject $mailManager;
|
||||
@@ -77,8 +77,7 @@ class IMipPluginCharsetTest extends TestCase {
|
||||
|
||||
// IMipService
|
||||
$this->urlGenerator = $this->createMock(URLGenerator::class);
|
||||
$this->userConfig = $this->createMock(IUserConfig::class);
|
||||
$this->appConfig = $this->createMock(IAppConfig::class);
|
||||
$this->config = $this->createMock(IConfig::class);
|
||||
$this->db = $this->createMock(IDBConnection::class);
|
||||
$this->random = $this->createMock(ISecureRandom::class);
|
||||
$l10n = $this->createMock(L10N::class);
|
||||
@@ -93,19 +92,19 @@ class IMipPluginCharsetTest extends TestCase {
|
||||
$this->userManager->method('getByEmail')->willReturn([]);
|
||||
$this->imipService = new IMipService(
|
||||
$this->urlGenerator,
|
||||
$this->config,
|
||||
$this->db,
|
||||
$this->random,
|
||||
$this->l10nFactory,
|
||||
$this->timeFactory,
|
||||
$this->userManager,
|
||||
$this->userConfig,
|
||||
$this->appConfig,
|
||||
$this->userManager
|
||||
);
|
||||
|
||||
// EventComparisonService
|
||||
$this->eventComparisonService = new EventComparisonService();
|
||||
|
||||
// IMipPlugin
|
||||
$this->appConfig = $this->createMock(IAppConfig::class);
|
||||
$message = new \OC\Mail\Message(new Email(), false);
|
||||
$this->mailer = $this->createMock(IMailer::class);
|
||||
$this->mailer->method('createMessage')
|
||||
@@ -178,13 +177,8 @@ class IMipPluginCharsetTest extends TestCase {
|
||||
public function testCharsetMailProvider(): void {
|
||||
// Arrange
|
||||
$this->appConfig->method('getValueBool')
|
||||
->willReturnCallback(function ($app, $key, $default) {
|
||||
if ($app === 'core') {
|
||||
$this->assertEquals($key, 'mail_providers_enabled');
|
||||
return true;
|
||||
}
|
||||
return $default;
|
||||
});
|
||||
->with('core', 'mail_providers_enabled', true)
|
||||
->willReturn(true);
|
||||
$mailMessage = new MailProviderMessage();
|
||||
$mailService = $this->createMockForIntersectionOfInterfaces([IService::class, IMessageSend::class]);
|
||||
$mailService->method('initiateMessage')
|
||||
|
||||
@@ -13,8 +13,7 @@ use OC\URLGenerator;
|
||||
use OCA\DAV\CalDAV\EventReader;
|
||||
use OCA\DAV\CalDAV\Schedule\IMipService;
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\Config\IUserConfig;
|
||||
use OCP\IAppConfig;
|
||||
use OCP\IConfig;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\IL10N;
|
||||
use OCP\IUserManager;
|
||||
@@ -27,8 +26,7 @@ use Test\TestCase;
|
||||
|
||||
class IMipServiceTest extends TestCase {
|
||||
private URLGenerator&MockObject $urlGenerator;
|
||||
private IUserConfig&MockObject $userConfig;
|
||||
private IAppConfig&MockObject $appConfig;
|
||||
private IConfig&MockObject $config;
|
||||
private IDBConnection&MockObject $db;
|
||||
private ISecureRandom&MockObject $random;
|
||||
private IFactory&MockObject $l10nFactory;
|
||||
@@ -49,8 +47,7 @@ class IMipServiceTest extends TestCase {
|
||||
parent::setUp();
|
||||
|
||||
$this->urlGenerator = $this->createMock(URLGenerator::class);
|
||||
$this->userConfig = $this->createMock(IUserConfig::class);
|
||||
$this->appConfig = $this->createMock(IAppConfig::class);
|
||||
$this->config = $this->createMock(IConfig::class);
|
||||
$this->db = $this->createMock(IDBConnection::class);
|
||||
$this->random = $this->createMock(ISecureRandom::class);
|
||||
$this->l10nFactory = $this->createMock(IFactory::class);
|
||||
@@ -66,13 +63,12 @@ class IMipServiceTest extends TestCase {
|
||||
->willReturn($this->l10n);
|
||||
$this->service = new IMipService(
|
||||
$this->urlGenerator,
|
||||
$this->config,
|
||||
$this->db,
|
||||
$this->random,
|
||||
$this->l10nFactory,
|
||||
$this->timeFactory,
|
||||
$this->userManager,
|
||||
$this->userConfig,
|
||||
$this->appConfig,
|
||||
$this->userManager
|
||||
);
|
||||
|
||||
// construct calendar with a 1 hour event and same start/end time zones
|
||||
|
||||
@@ -9,14 +9,12 @@ declare(strict_types=1);
|
||||
namespace OCA\DAV\Tests\unit\CalDAV;
|
||||
|
||||
use DateTimeZone;
|
||||
use OCA\DAV\CalDAV\CalDavBackend;
|
||||
use OCA\DAV\CalDAV\CalendarImpl;
|
||||
use OCA\DAV\CalDAV\TimezoneService;
|
||||
use OCA\DAV\Db\Property;
|
||||
use OCA\DAV\Db\PropertyMapper;
|
||||
use OCP\Calendar\ICalendar;
|
||||
use OCP\Calendar\IManager;
|
||||
use OCP\Config\IUserConfig;
|
||||
use OCP\IConfig;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Sabre\VObject\Component\VTimeZone;
|
||||
@@ -24,7 +22,6 @@ use Test\TestCase;
|
||||
|
||||
class TimezoneServiceTest extends TestCase {
|
||||
private IConfig&MockObject $config;
|
||||
private IUserConfig&MockObject $userConfig;
|
||||
private PropertyMapper&MockObject $propertyMapper;
|
||||
private IManager&MockObject $calendarManager;
|
||||
private TimezoneService $service;
|
||||
@@ -33,21 +30,19 @@ class TimezoneServiceTest extends TestCase {
|
||||
parent::setUp();
|
||||
|
||||
$this->config = $this->createMock(IConfig::class);
|
||||
$this->userConfig = $this->createMock(IUserConfig::class);
|
||||
$this->propertyMapper = $this->createMock(PropertyMapper::class);
|
||||
$this->calendarManager = $this->createMock(IManager::class);
|
||||
|
||||
$this->service = new TimezoneService(
|
||||
$this->config,
|
||||
$this->userConfig,
|
||||
$this->propertyMapper,
|
||||
$this->calendarManager,
|
||||
);
|
||||
}
|
||||
|
||||
public function testGetUserTimezoneFromSettings(): void {
|
||||
$this->userConfig->expects(self::once())
|
||||
->method('getValueString')
|
||||
$this->config->expects(self::once())
|
||||
->method('getUserValue')
|
||||
->with('test123', 'core', 'timezone', '')
|
||||
->willReturn('Europe/Warsaw');
|
||||
|
||||
@@ -57,8 +52,8 @@ class TimezoneServiceTest extends TestCase {
|
||||
}
|
||||
|
||||
public function testGetUserTimezoneFromAvailability(): void {
|
||||
$this->userConfig->expects(self::once())
|
||||
->method('getValueString')
|
||||
$this->config->expects(self::once())
|
||||
->method('getUserValue')
|
||||
->with('test123', 'core', 'timezone', '')
|
||||
->willReturn('');
|
||||
$property = new Property();
|
||||
@@ -81,11 +76,11 @@ END:VCALENDAR');
|
||||
}
|
||||
|
||||
public function testGetUserTimezoneFromPersonalCalendar(): void {
|
||||
$this->userConfig->expects(self::exactly(2))
|
||||
->method('getValueString')
|
||||
$this->config->expects(self::exactly(2))
|
||||
->method('getUserValue')
|
||||
->willReturnMap([
|
||||
['test123', 'core', 'timezone', '', false, ''],
|
||||
['test123', 'dav', 'defaultCalendar', CalDavBackend::PERSONAL_CALENDAR_URI, false, 'personal-1'],
|
||||
['test123', 'core', 'timezone', '', ''],
|
||||
['test123', 'dav', 'defaultCalendar', '', 'personal-1'],
|
||||
]);
|
||||
$other = $this->createMock(ICalendar::class);
|
||||
$other->method('getUri')->willReturn('other');
|
||||
@@ -110,11 +105,11 @@ END:VCALENDAR');
|
||||
}
|
||||
|
||||
public function testGetUserTimezoneFromAny(): void {
|
||||
$this->userConfig->expects(self::exactly(2))
|
||||
->method('getValueString')
|
||||
$this->config->expects(self::exactly(2))
|
||||
->method('getUserValue')
|
||||
->willReturnMap([
|
||||
['test123', 'core', 'timezone', '', false, ''],
|
||||
['test123', 'dav', 'defaultCalendar', CalDavBackend::PERSONAL_CALENDAR_URI, false, 'personal-1'],
|
||||
['test123', 'core', 'timezone', '', ''],
|
||||
['test123', 'dav', 'defaultCalendar', '', 'personal-1'],
|
||||
]);
|
||||
$other = $this->createMock(ICalendar::class);
|
||||
$other->method('getUri')->willReturn('other');
|
||||
|
||||
@@ -253,6 +253,7 @@ class SharesPluginTest extends \Test\TestCase {
|
||||
[[IShare::TYPE_REMOTE]],
|
||||
[[IShare::TYPE_ROOM]],
|
||||
[[IShare::TYPE_DECK]],
|
||||
[[IShare::TYPE_SCIENCEMESH]],
|
||||
[[IShare::TYPE_USER, IShare::TYPE_GROUP]],
|
||||
[[IShare::TYPE_USER, IShare::TYPE_GROUP, IShare::TYPE_LINK]],
|
||||
[[IShare::TYPE_USER, IShare::TYPE_LINK]],
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
*/
|
||||
namespace OCA\FederatedFileSharing\Controller;
|
||||
|
||||
use OCA\FederatedFileSharing\AddressHandler;
|
||||
use OCA\FederatedFileSharing\FederatedShareProvider;
|
||||
use OCA\FederatedFileSharing\Notifications;
|
||||
use OCP\App\IAppManager;
|
||||
use OCP\AppFramework\Http;
|
||||
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
|
||||
@@ -27,6 +29,7 @@ use OCP\Federation\ICloudIdManager;
|
||||
use OCP\HintException;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\IRequest;
|
||||
use OCP\IUserManager;
|
||||
use OCP\Log\Audit\CriticalActionPerformedEvent;
|
||||
use OCP\Server;
|
||||
use OCP\Share;
|
||||
@@ -41,6 +44,10 @@ class RequestHandlerController extends OCSController {
|
||||
IRequest $request,
|
||||
private FederatedShareProvider $federatedShareProvider,
|
||||
private IDBConnection $connection,
|
||||
private Share\IManager $shareManager,
|
||||
private Notifications $notifications,
|
||||
private AddressHandler $addressHandler,
|
||||
private IUserManager $userManager,
|
||||
private ICloudIdManager $cloudIdManager,
|
||||
private LoggerInterface $logger,
|
||||
private ICloudFederationFactory $cloudFederationFactory,
|
||||
@@ -59,10 +66,10 @@ class RequestHandlerController extends OCSController {
|
||||
* @param string|null $owner Display name of the receiver
|
||||
* @param string|null $sharedBy Display name of the sender
|
||||
* @param string|null $shareWith ID of the user that receives the share
|
||||
* @param string|null $remoteId ID of the remote
|
||||
* @param int|null $remoteId ID of the remote
|
||||
* @param string|null $sharedByFederatedId Federated ID of the sender
|
||||
* @param string|null $ownerFederatedId Federated ID of the receiver
|
||||
* @return DataResponse<Http::STATUS_OK, list<empty>, array{}>
|
||||
* @return Http\DataResponse<Http::STATUS_OK, list<empty>, array{}>
|
||||
* @throws OCSException
|
||||
*
|
||||
* 200: Share created successfully
|
||||
@@ -76,10 +83,10 @@ class RequestHandlerController extends OCSController {
|
||||
?string $owner = null,
|
||||
?string $sharedBy = null,
|
||||
?string $shareWith = null,
|
||||
?string $remoteId = null,
|
||||
?int $remoteId = null,
|
||||
?string $sharedByFederatedId = null,
|
||||
?string $ownerFederatedId = null,
|
||||
): DataResponse {
|
||||
) {
|
||||
if ($ownerFederatedId === null) {
|
||||
$ownerFederatedId = $this->cloudIdManager->getCloudId($owner, $this->cleanupRemote($remote))->getId();
|
||||
}
|
||||
@@ -125,11 +132,11 @@ class RequestHandlerController extends OCSController {
|
||||
/**
|
||||
* create re-share on behalf of another user
|
||||
*
|
||||
* @param string $id ID of the share
|
||||
* @param int $id ID of the share
|
||||
* @param string|null $token Shared secret between servers
|
||||
* @param string|null $shareWith ID of the user that receives the share
|
||||
* @param int|null $remoteId ID of the remote
|
||||
* @return DataResponse<Http::STATUS_OK, array{token: string, remoteId: string}, array{}>
|
||||
* @return Http\DataResponse<Http::STATUS_OK, array{token: string, remoteId: string}, array{}>
|
||||
* @throws OCSBadRequestException Re-sharing is not possible
|
||||
* @throws OCSException
|
||||
*
|
||||
@@ -137,7 +144,7 @@ class RequestHandlerController extends OCSController {
|
||||
*/
|
||||
#[NoCSRFRequired]
|
||||
#[PublicPage]
|
||||
public function reShare(string $id, ?string $token = null, ?string $shareWith = null, ?int $remoteId = 0): DataResponse {
|
||||
public function reShare(int $id, ?string $token = null, ?string $shareWith = null, ?int $remoteId = 0) {
|
||||
if ($token === null
|
||||
|| $shareWith === null
|
||||
|| $remoteId === null
|
||||
@@ -174,9 +181,9 @@ class RequestHandlerController extends OCSController {
|
||||
/**
|
||||
* accept server-to-server share
|
||||
*
|
||||
* @param string $id ID of the remote share
|
||||
* @param int $id ID of the remote share
|
||||
* @param string|null $token Shared secret between servers
|
||||
* @return DataResponse<Http::STATUS_OK, list<empty>, array{}>
|
||||
* @return Http\DataResponse<Http::STATUS_OK, list<empty>, array{}>
|
||||
* @throws OCSException
|
||||
* @throws ShareNotFound
|
||||
* @throws HintException
|
||||
@@ -185,7 +192,7 @@ class RequestHandlerController extends OCSController {
|
||||
*/
|
||||
#[NoCSRFRequired]
|
||||
#[PublicPage]
|
||||
public function acceptShare(string $id, ?string $token = null): DataResponse {
|
||||
public function acceptShare(int $id, ?string $token = null) {
|
||||
$notification = [
|
||||
'sharedSecret' => $token,
|
||||
'message' => 'Recipient accept the share'
|
||||
@@ -209,16 +216,16 @@ class RequestHandlerController extends OCSController {
|
||||
/**
|
||||
* decline server-to-server share
|
||||
*
|
||||
* @param string $id ID of the remote share
|
||||
* @param int $id ID of the remote share
|
||||
* @param string|null $token Shared secret between servers
|
||||
* @return DataResponse<Http::STATUS_OK, list<empty>, array{}>
|
||||
* @return Http\DataResponse<Http::STATUS_OK, list<empty>, array{}>
|
||||
* @throws OCSException
|
||||
*
|
||||
* 200: Share declined successfully
|
||||
*/
|
||||
#[NoCSRFRequired]
|
||||
#[PublicPage]
|
||||
public function declineShare(string $id, ?string $token = null) {
|
||||
public function declineShare(int $id, ?string $token = null) {
|
||||
$notification = [
|
||||
'sharedSecret' => $token,
|
||||
'message' => 'Recipient declined the share'
|
||||
@@ -242,16 +249,16 @@ class RequestHandlerController extends OCSController {
|
||||
/**
|
||||
* remove server-to-server share if it was unshared by the owner
|
||||
*
|
||||
* @param string $id ID of the share
|
||||
* @param int $id ID of the share
|
||||
* @param string|null $token Shared secret between servers
|
||||
* @return DataResponse<Http::STATUS_OK, list<empty>, array{}>
|
||||
* @return Http\DataResponse<Http::STATUS_OK, list<empty>, array{}>
|
||||
* @throws OCSException
|
||||
*
|
||||
* 200: Share unshared successfully
|
||||
*/
|
||||
#[NoCSRFRequired]
|
||||
#[PublicPage]
|
||||
public function unshare(string $id, ?string $token = null) {
|
||||
public function unshare(int $id, ?string $token = null) {
|
||||
if (!$this->isS2SEnabled()) {
|
||||
throw new OCSException('Server does not support federated cloud sharing', 503);
|
||||
}
|
||||
@@ -268,7 +275,7 @@ class RequestHandlerController extends OCSController {
|
||||
return new DataResponse();
|
||||
}
|
||||
|
||||
private function cleanupRemote(string $remote): string {
|
||||
private function cleanupRemote($remote) {
|
||||
$remote = substr($remote, strpos($remote, '://') + 3);
|
||||
|
||||
return rtrim($remote, '/');
|
||||
@@ -278,16 +285,16 @@ class RequestHandlerController extends OCSController {
|
||||
/**
|
||||
* federated share was revoked, either by the owner or the re-sharer
|
||||
*
|
||||
* @param string $id ID of the share
|
||||
* @param int $id ID of the share
|
||||
* @param string|null $token Shared secret between servers
|
||||
* @return DataResponse<Http::STATUS_OK, list<empty>, array{}>
|
||||
* @return Http\DataResponse<Http::STATUS_OK, list<empty>, array{}>
|
||||
* @throws OCSBadRequestException Revoking the share is not possible
|
||||
*
|
||||
* 200: Share revoked successfully
|
||||
*/
|
||||
#[NoCSRFRequired]
|
||||
#[PublicPage]
|
||||
public function revoke(string $id, ?string $token = null) {
|
||||
public function revoke(int $id, ?string $token = null) {
|
||||
try {
|
||||
$provider = $this->cloudFederationProviderManager->getCloudFederationProvider('file');
|
||||
$notification = ['sharedSecret' => $token];
|
||||
@@ -317,19 +324,19 @@ class RequestHandlerController extends OCSController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Update share information to keep federated re-shares in sync.
|
||||
* update share information to keep federated re-shares in sync
|
||||
*
|
||||
* @param string $id ID of the share
|
||||
* @param int $id ID of the share
|
||||
* @param string|null $token Shared secret between servers
|
||||
* @param int|null $permissions New permissions
|
||||
* @return DataResponse<Http::STATUS_OK, list<empty>, array{}>
|
||||
* @return Http\DataResponse<Http::STATUS_OK, list<empty>, array{}>
|
||||
* @throws OCSBadRequestException Updating permissions is not possible
|
||||
*
|
||||
* 200: Permissions updated successfully
|
||||
*/
|
||||
#[NoCSRFRequired]
|
||||
#[PublicPage]
|
||||
public function updatePermissions(string $id, ?string $token = null, ?int $permissions = null) {
|
||||
public function updatePermissions(int $id, ?string $token = null, ?int $permissions = null) {
|
||||
$ncPermissions = $permissions;
|
||||
|
||||
try {
|
||||
@@ -378,7 +385,7 @@ class RequestHandlerController extends OCSController {
|
||||
* @param string|null $token Shared secret between servers
|
||||
* @param string|null $remote Address of the remote
|
||||
* @param string|null $remote_id ID of the remote
|
||||
* @return DataResponse<Http::STATUS_OK, array{remote: string, owner: string}, array{}>
|
||||
* @return Http\DataResponse<Http::STATUS_OK, array{remote: string, owner: string}, array{}>
|
||||
* @throws OCSBadRequestException Moving share is not possible
|
||||
*
|
||||
* 200: Share moved successfully
|
||||
|
||||
@@ -27,7 +27,6 @@ use OCP\Share\Exceptions\ShareNotFound;
|
||||
use OCP\Share\IShare;
|
||||
use OCP\Share\IShareProvider;
|
||||
use OCP\Share\IShareProviderSupportsAllSharesInFolder;
|
||||
use Override;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
@@ -63,19 +62,24 @@ class FederatedShareProvider implements IShareProvider, IShareProviderSupportsAl
|
||||
) {
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function identifier(): string {
|
||||
/**
|
||||
* Return the identifier of this provider.
|
||||
*
|
||||
* @return string Containing only [a-zA-Z0-9]
|
||||
*/
|
||||
public function identifier() {
|
||||
return 'ocFederatedSharing';
|
||||
}
|
||||
|
||||
/**
|
||||
* Share a path
|
||||
*
|
||||
* @param IShare $share
|
||||
* @return IShare The share object
|
||||
* @throws ShareNotFound
|
||||
* @throws \Exception
|
||||
*/
|
||||
#[Override]
|
||||
public function create(IShare $share): IShare {
|
||||
public function create(IShare $share) {
|
||||
$shareWith = $share->getSharedWith();
|
||||
$itemSource = $share->getNodeId();
|
||||
$itemType = $share->getNodeType();
|
||||
@@ -164,12 +168,14 @@ class FederatedShareProvider implements IShareProvider, IShareProviderSupportsAl
|
||||
}
|
||||
|
||||
/**
|
||||
* Create federated share and inform the recipient.
|
||||
* create federated share and inform the recipient
|
||||
*
|
||||
* @param IShare $share
|
||||
* @return int
|
||||
* @throws ShareNotFound
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function createFederatedShare(IShare $share): string {
|
||||
protected function createFederatedShare(IShare $share) {
|
||||
$token = $this->tokenHandler->generateToken();
|
||||
$shareId = $this->addShareToDB(
|
||||
$share->getNodeId(),
|
||||
@@ -287,8 +293,9 @@ class FederatedShareProvider implements IShareProvider, IShareProviderSupportsAl
|
||||
* @param string $token
|
||||
* @param int $shareType
|
||||
* @param \DateTime $expirationDate
|
||||
* @return int
|
||||
*/
|
||||
private function addShareToDB($itemSource, $itemType, $shareWith, $sharedBy, $uidOwner, $permissions, $token, $shareType, $expirationDate): string {
|
||||
private function addShareToDB($itemSource, $itemType, $shareWith, $sharedBy, $uidOwner, $permissions, $token, $shareType, $expirationDate) {
|
||||
$qb = $this->dbConnection->getQueryBuilder();
|
||||
$qb->insert('share')
|
||||
->setValue('share_type', $qb->createNamedParameter($shareType))
|
||||
@@ -310,13 +317,16 @@ class FederatedShareProvider implements IShareProvider, IShareProviderSupportsAl
|
||||
$qb->setValue('file_target', $qb->createNamedParameter(''));
|
||||
|
||||
$qb->executeStatement();
|
||||
return (string)$qb->getLastInsertId();
|
||||
return $qb->getLastInsertId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a share.
|
||||
* Update a share
|
||||
*
|
||||
* @param IShare $share
|
||||
* @return IShare The share object
|
||||
*/
|
||||
public function update(IShare $share): IShare {
|
||||
public function update(IShare $share) {
|
||||
/*
|
||||
* We allow updating the permissions of federated shares
|
||||
*/
|
||||
@@ -338,12 +348,13 @@ class FederatedShareProvider implements IShareProvider, IShareProviderSupportsAl
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the updated permission to the owner/initiator, if they are not the same.
|
||||
* send the updated permission to the owner/initiator, if they are not the same
|
||||
*
|
||||
* @param IShare $share
|
||||
* @throws ShareNotFound
|
||||
* @throws HintException
|
||||
*/
|
||||
protected function sendPermissionUpdate(IShare $share): void {
|
||||
protected function sendPermissionUpdate(IShare $share) {
|
||||
$remoteId = $this->getRemoteId($share);
|
||||
// if the local user is the owner we send the permission change to the initiator
|
||||
if ($this->userManager->userExists($share->getShareOwner())) {
|
||||
@@ -356,9 +367,12 @@ class FederatedShareProvider implements IShareProvider, IShareProviderSupportsAl
|
||||
|
||||
|
||||
/**
|
||||
* Update successful reShare with the correct token.
|
||||
* update successful reShare with the correct token
|
||||
*
|
||||
* @param int $shareId
|
||||
* @param string $token
|
||||
*/
|
||||
protected function updateSuccessfulReShare(string $shareId, string $token): void {
|
||||
protected function updateSuccessfulReShare($shareId, $token) {
|
||||
$query = $this->dbConnection->getQueryBuilder();
|
||||
$query->update('share')
|
||||
->where($query->expr()->eq('id', $query->createNamedParameter($shareId)))
|
||||
@@ -367,9 +381,12 @@ class FederatedShareProvider implements IShareProvider, IShareProviderSupportsAl
|
||||
}
|
||||
|
||||
/**
|
||||
* Store remote ID in federated reShare table.
|
||||
* store remote ID in federated reShare table
|
||||
*
|
||||
* @param $shareId
|
||||
* @param $remoteId
|
||||
*/
|
||||
public function storeRemoteId(string $shareId, string $remoteId): void {
|
||||
public function storeRemoteId(int $shareId, string $remoteId): void {
|
||||
$query = $this->dbConnection->getQueryBuilder();
|
||||
$query->insert('federated_reshares')
|
||||
->values(
|
||||
@@ -382,8 +399,10 @@ class FederatedShareProvider implements IShareProvider, IShareProviderSupportsAl
|
||||
}
|
||||
|
||||
/**
|
||||
* Get share ID on remote server for federated re-shares.
|
||||
* get share ID on remote server for federated re-shares
|
||||
*
|
||||
* @param IShare $share
|
||||
* @return string
|
||||
* @throws ShareNotFound
|
||||
*/
|
||||
public function getRemoteId(IShare $share): string {
|
||||
@@ -493,9 +512,11 @@ class FederatedShareProvider implements IShareProvider, IShareProviderSupportsAl
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove share from table.
|
||||
* remove share from table
|
||||
*
|
||||
* @param string $shareId
|
||||
*/
|
||||
private function removeShareFromTableById(string $shareId): void {
|
||||
private function removeShareFromTableById($shareId) {
|
||||
$qb = $this->dbConnection->getQueryBuilder();
|
||||
$qb->delete('share')
|
||||
->where($qb->expr()->eq('id', $qb->createNamedParameter($shareId)))
|
||||
@@ -727,8 +748,14 @@ class FederatedShareProvider implements IShareProvider, IShareProviderSupportsAl
|
||||
return $shares;
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function getShareByToken(string $token): IShare {
|
||||
/**
|
||||
* Get a share by token
|
||||
*
|
||||
* @param string $token
|
||||
* @return IShare
|
||||
* @throws ShareNotFound
|
||||
*/
|
||||
public function getShareByToken($token) {
|
||||
$qb = $this->dbConnection->getQueryBuilder();
|
||||
|
||||
$cursor = $qb->select('*')
|
||||
@@ -785,9 +812,9 @@ class FederatedShareProvider implements IShareProvider, IShareProviderSupportsAl
|
||||
* @throws InvalidShare
|
||||
* @throws ShareNotFound
|
||||
*/
|
||||
private function createShareObject($data): IShare {
|
||||
private function createShareObject($data) {
|
||||
$share = new Share($this->rootFolder, $this->userManager);
|
||||
$share->setId((string)$data['id'])
|
||||
$share->setId((int)$data['id'])
|
||||
->setShareType((int)$data['share_type'])
|
||||
->setPermissions((int)$data['permissions'])
|
||||
->setTarget($data['file_target'])
|
||||
@@ -867,7 +894,7 @@ class FederatedShareProvider implements IShareProvider, IShareProviderSupportsAl
|
||||
|
||||
$qb = $this->dbConnection->getQueryBuilder();
|
||||
$qb->delete('share_external')
|
||||
->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_USER)))
|
||||
->where($qb->expr()->eq('share_type', $qb->createNamedParameter(IShare::TYPE_GROUP)))
|
||||
->andWhere($qb->expr()->eq('user', $qb->createNamedParameter($uid)))
|
||||
->executeStatement();
|
||||
}
|
||||
|
||||
@@ -9,18 +9,15 @@ namespace OCA\FederatedFileSharing\OCM;
|
||||
use NCU\Federation\ISignedCloudFederationProvider;
|
||||
use OC\AppFramework\Http;
|
||||
use OC\Files\Filesystem;
|
||||
use OC\Files\SetupManager;
|
||||
use OCA\FederatedFileSharing\AddressHandler;
|
||||
use OCA\FederatedFileSharing\FederatedShareProvider;
|
||||
use OCA\Federation\TrustedServers;
|
||||
use OCA\Files_Sharing\Activity\Providers\RemoteShares;
|
||||
use OCA\Files_Sharing\External\ExternalShare;
|
||||
use OCA\Files_Sharing\External\ExternalShareMapper;
|
||||
use OCA\Files_Sharing\External\Manager;
|
||||
use OCA\GlobalSiteSelector\Service\SlaveService;
|
||||
use OCA\Polls\Db\Share;
|
||||
use OCP\Activity\IManager as IActivityManager;
|
||||
use OCP\App\IAppManager;
|
||||
use OCP\AppFramework\QueryException;
|
||||
use OCP\Constants;
|
||||
use OCP\Federation\Exceptions\ActionNotSupportedException;
|
||||
use OCP\Federation\Exceptions\AuthenticationFailedException;
|
||||
@@ -34,9 +31,9 @@ use OCP\Files\IFilenameValidator;
|
||||
use OCP\Files\NotFoundException;
|
||||
use OCP\HintException;
|
||||
use OCP\IConfig;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\IGroupManager;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\IUser;
|
||||
use OCP\IUserManager;
|
||||
use OCP\Notification\IManager as INotificationManager;
|
||||
use OCP\Server;
|
||||
@@ -44,44 +41,55 @@ use OCP\Share\Exceptions\ShareNotFound;
|
||||
use OCP\Share\IManager;
|
||||
use OCP\Share\IProviderFactory;
|
||||
use OCP\Share\IShare;
|
||||
use OCP\Snowflake\IGenerator;
|
||||
use OCP\Util;
|
||||
use Override;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use SensitiveParameter;
|
||||
|
||||
class CloudFederationProviderFiles implements ISignedCloudFederationProvider {
|
||||
/**
|
||||
* CloudFederationProvider constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly IAppManager $appManager,
|
||||
private readonly FederatedShareProvider $federatedShareProvider,
|
||||
private readonly AddressHandler $addressHandler,
|
||||
private readonly IUserManager $userManager,
|
||||
private readonly IManager $shareManager,
|
||||
private readonly ICloudIdManager $cloudIdManager,
|
||||
private readonly IActivityManager $activityManager,
|
||||
private readonly INotificationManager $notificationManager,
|
||||
private readonly IURLGenerator $urlGenerator,
|
||||
private readonly ICloudFederationFactory $cloudFederationFactory,
|
||||
private readonly ICloudFederationProviderManager $cloudFederationProviderManager,
|
||||
private readonly IGroupManager $groupManager,
|
||||
private readonly IConfig $config,
|
||||
private readonly Manager $externalShareManager,
|
||||
private readonly LoggerInterface $logger,
|
||||
private readonly IFilenameValidator $filenameValidator,
|
||||
private IAppManager $appManager,
|
||||
private FederatedShareProvider $federatedShareProvider,
|
||||
private AddressHandler $addressHandler,
|
||||
private IUserManager $userManager,
|
||||
private IManager $shareManager,
|
||||
private ICloudIdManager $cloudIdManager,
|
||||
private IActivityManager $activityManager,
|
||||
private INotificationManager $notificationManager,
|
||||
private IURLGenerator $urlGenerator,
|
||||
private ICloudFederationFactory $cloudFederationFactory,
|
||||
private ICloudFederationProviderManager $cloudFederationProviderManager,
|
||||
private IDBConnection $connection,
|
||||
private IGroupManager $groupManager,
|
||||
private IConfig $config,
|
||||
private Manager $externalShareManager,
|
||||
private LoggerInterface $logger,
|
||||
private IFilenameValidator $filenameValidator,
|
||||
private readonly IProviderFactory $shareProviderFactory,
|
||||
private readonly SetupManager $setupManager,
|
||||
private readonly IGenerator $snowflakeGenerator,
|
||||
private readonly ExternalShareMapper $externalShareMapper,
|
||||
) {
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function getShareType(): string {
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getShareType() {
|
||||
return 'file';
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function shareReceived(ICloudFederationShare $share): string {
|
||||
/**
|
||||
* share received from another server
|
||||
*
|
||||
* @param ICloudFederationShare $share
|
||||
* @return string provider specific unique ID of the share
|
||||
*
|
||||
* @throws ProviderCouldNotAddShareException
|
||||
* @throws QueryException
|
||||
* @throws HintException
|
||||
* @since 14.0.0
|
||||
*/
|
||||
public function shareReceived(ICloudFederationShare $share) {
|
||||
if (!$this->isS2SEnabled(true)) {
|
||||
throw new ProviderCouldNotAddShareException('Server does not support federated cloud sharing', '', Http::STATUS_SERVICE_UNAVAILABLE);
|
||||
}
|
||||
@@ -91,8 +99,7 @@ class CloudFederationProviderFiles implements ISignedCloudFederationProvider {
|
||||
throw new ProviderCouldNotAddShareException('Unsupported protocol for data exchange.', '', Http::STATUS_NOT_IMPLEMENTED);
|
||||
}
|
||||
|
||||
[, $remote] = $this->addressHandler->splitUserRemote($share->getOwner());
|
||||
|
||||
[$ownerUid, $remote] = $this->addressHandler->splitUserRemote($share->getOwner());
|
||||
// for backward compatibility make sure that the remote url stored in the
|
||||
// database ends with a trailing slash
|
||||
if (!str_ends_with($remote, '/')) {
|
||||
@@ -102,15 +109,17 @@ class CloudFederationProviderFiles implements ISignedCloudFederationProvider {
|
||||
$token = $share->getShareSecret();
|
||||
$name = $share->getResourceName();
|
||||
$owner = $share->getOwnerDisplayName() ?: $share->getOwner();
|
||||
$sharedBy = $share->getSharedByDisplayName();
|
||||
$shareWith = $share->getShareWith();
|
||||
$remoteId = $share->getProviderId();
|
||||
$sharedByFederatedId = $share->getSharedBy();
|
||||
$ownerFederatedId = $share->getOwner();
|
||||
$shareType = $this->mapShareTypeToNextcloud($share->getShareType());
|
||||
|
||||
// if no explicit information about the person who created the share was sent
|
||||
// if no explicit information about the person who created the share was send
|
||||
// we assume that the share comes from the owner
|
||||
if ($sharedByFederatedId === null) {
|
||||
$sharedBy = $owner;
|
||||
$sharedByFederatedId = $ownerFederatedId;
|
||||
}
|
||||
|
||||
@@ -119,9 +128,7 @@ class CloudFederationProviderFiles implements ISignedCloudFederationProvider {
|
||||
throw new ProviderCouldNotAddShareException('The mountpoint name contains invalid characters.', '', Http::STATUS_BAD_REQUEST);
|
||||
}
|
||||
|
||||
$user = null;
|
||||
$group = null;
|
||||
|
||||
// FIXME this should be a method in the user management instead
|
||||
if ($shareType === IShare::TYPE_USER) {
|
||||
$this->logger->debug('shareWith before, ' . $shareWith, ['app' => 'files_sharing']);
|
||||
Util::emitHook(
|
||||
@@ -131,32 +138,20 @@ class CloudFederationProviderFiles implements ISignedCloudFederationProvider {
|
||||
);
|
||||
$this->logger->debug('shareWith after, ' . $shareWith, ['app' => 'files_sharing']);
|
||||
|
||||
$user = $this->userManager->get($shareWith);
|
||||
if ($user === null) {
|
||||
if (!$this->userManager->userExists($shareWith)) {
|
||||
throw new ProviderCouldNotAddShareException('User does not exists', '', Http::STATUS_BAD_REQUEST);
|
||||
}
|
||||
|
||||
$this->setupManager->setupForUser($user);
|
||||
} else {
|
||||
$group = $this->groupManager->get($shareWith);
|
||||
if ($group === null) {
|
||||
throw new ProviderCouldNotAddShareException('Group does not exists', '', Http::STATUS_BAD_REQUEST);
|
||||
}
|
||||
\OC_Util::setupFS($shareWith);
|
||||
}
|
||||
|
||||
$externalShare = new ExternalShare();
|
||||
$externalShare->setId($this->snowflakeGenerator->nextId());
|
||||
$externalShare->setRemote($remote);
|
||||
$externalShare->setRemoteId($remoteId);
|
||||
$externalShare->setShareToken($token);
|
||||
$externalShare->setPassword('');
|
||||
$externalShare->setName($name);
|
||||
$externalShare->setOwner($owner);
|
||||
$externalShare->setShareType($shareType);
|
||||
$externalShare->setAccepted(IShare::STATUS_PENDING);
|
||||
if ($shareType === IShare::TYPE_GROUP && !$this->groupManager->groupExists($shareWith)) {
|
||||
throw new ProviderCouldNotAddShareException('Group does not exists', '', Http::STATUS_BAD_REQUEST);
|
||||
}
|
||||
|
||||
try {
|
||||
$this->externalShareManager->addShare($externalShare, $user ?: $group);
|
||||
$this->externalShareManager->addShare($remote, $token, '', $name, $owner, $shareType, false, $shareWith, $remoteId);
|
||||
$shareId = Server::get(IDBConnection::class)->lastInsertId('*PREFIX*share_external');
|
||||
|
||||
// get DisplayName about the owner of the share
|
||||
$ownerDisplayName = $this->getUserDisplayName($ownerFederatedId);
|
||||
@@ -171,40 +166,41 @@ class CloudFederationProviderFiles implements ISignedCloudFederationProvider {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ($shareType === IShare::TYPE_USER) {
|
||||
$event = $this->activityManager->generateEvent();
|
||||
$event->setApp('files_sharing')
|
||||
->setType('remote_share')
|
||||
->setSubject(RemoteShares::SUBJECT_REMOTE_SHARE_RECEIVED, [$ownerFederatedId, trim($name, '/'), $ownerDisplayName])
|
||||
->setAffectedUser($shareWith)
|
||||
->setObject('remote_share', $externalShare->getId(), $name);
|
||||
->setObject('remote_share', $shareId, $name);
|
||||
Server::get(IActivityManager::class)->publish($event);
|
||||
$this->notifyAboutNewShare($shareWith, $externalShare->getId(), $ownerFederatedId, $sharedByFederatedId, $name, $ownerDisplayName);
|
||||
$this->notifyAboutNewShare($shareWith, $shareId, $ownerFederatedId, $sharedByFederatedId, $name, $ownerDisplayName);
|
||||
|
||||
// If auto-accept is enabled, accept the share
|
||||
if ($this->federatedShareProvider->isFederatedTrustedShareAutoAccept() && $trustedServers?->isTrustedServer($remote) === true) {
|
||||
$this->externalShareManager->acceptShare($externalShare, $user);
|
||||
$this->externalShareManager->acceptShare($shareId, $shareWith);
|
||||
}
|
||||
} else {
|
||||
$groupMembers = $group->getUsers();
|
||||
$groupMembers = $this->groupManager->get($shareWith)->getUsers();
|
||||
foreach ($groupMembers as $user) {
|
||||
$event = $this->activityManager->generateEvent();
|
||||
$event->setApp('files_sharing')
|
||||
->setType('remote_share')
|
||||
->setSubject(RemoteShares::SUBJECT_REMOTE_SHARE_RECEIVED, [$ownerFederatedId, trim($name, '/'), $ownerDisplayName])
|
||||
->setAffectedUser($user->getUID())
|
||||
->setObject('remote_share', $externalShare->getId(), $name);
|
||||
->setObject('remote_share', $shareId, $name);
|
||||
Server::get(IActivityManager::class)->publish($event);
|
||||
$this->notifyAboutNewShare($user->getUID(), $externalShare->getId(), $ownerFederatedId, $sharedByFederatedId, $name, $ownerDisplayName);
|
||||
$this->notifyAboutNewShare($user->getUID(), $shareId, $ownerFederatedId, $sharedByFederatedId, $name, $ownerDisplayName);
|
||||
|
||||
// If auto-accept is enabled, accept the share
|
||||
if ($this->federatedShareProvider->isFederatedTrustedShareAutoAccept() && $trustedServers?->isTrustedServer($remote) === true) {
|
||||
$this->externalShareManager->acceptShare($externalShare, $user);
|
||||
$this->externalShareManager->acceptShare($shareId, $user->getUID());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $externalShare->getId();
|
||||
return $shareId;
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error('Server can not add remote share.', [
|
||||
'app' => 'files_sharing',
|
||||
@@ -217,28 +213,56 @@ class CloudFederationProviderFiles implements ISignedCloudFederationProvider {
|
||||
throw new ProviderCouldNotAddShareException('server can not add remote share, missing parameter', '', HTTP::STATUS_BAD_REQUEST);
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function notificationReceived(string $notificationType, string $providerId, array $notification): array {
|
||||
return match ($notificationType) {
|
||||
'SHARE_ACCEPTED' => $this->shareAccepted($providerId, $notification),
|
||||
'SHARE_DECLINED' => $this->shareDeclined($providerId, $notification),
|
||||
'SHARE_UNSHARED' => $this->unshare($providerId, $notification),
|
||||
'REQUEST_RESHARE' => $this->reshareRequested($providerId, $notification),
|
||||
'RESHARE_UNDO' => $this->undoReshare($providerId, $notification),
|
||||
'RESHARE_CHANGE_PERMISSION' => $this->updateResharePermissions($providerId, $notification),
|
||||
default => throw new BadRequestException([$notificationType]),
|
||||
};
|
||||
/**
|
||||
* notification received from another server
|
||||
*
|
||||
* @param string $notificationType (e.g. SHARE_ACCEPTED)
|
||||
* @param string $providerId id of the share
|
||||
* @param array $notification payload of the notification
|
||||
* @return array<string> data send back to the sender
|
||||
*
|
||||
* @throws ActionNotSupportedException
|
||||
* @throws AuthenticationFailedException
|
||||
* @throws BadRequestException
|
||||
* @throws HintException
|
||||
* @since 14.0.0
|
||||
*/
|
||||
public function notificationReceived($notificationType, $providerId, array $notification) {
|
||||
switch ($notificationType) {
|
||||
case 'SHARE_ACCEPTED':
|
||||
return $this->shareAccepted($providerId, $notification);
|
||||
case 'SHARE_DECLINED':
|
||||
return $this->shareDeclined($providerId, $notification);
|
||||
case 'SHARE_UNSHARED':
|
||||
return $this->unshare($providerId, $notification);
|
||||
case 'REQUEST_RESHARE':
|
||||
return $this->reshareRequested($providerId, $notification);
|
||||
case 'RESHARE_UNDO':
|
||||
return $this->undoReshare($providerId, $notification);
|
||||
case 'RESHARE_CHANGE_PERMISSION':
|
||||
return $this->updateResharePermissions($providerId, $notification);
|
||||
}
|
||||
|
||||
|
||||
throw new BadRequestException([$notificationType]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Map OCM share type (strings) to Nextcloud internal share types (integer)
|
||||
* @return IShare::TYPE_GROUP|IShare::TYPE_USER
|
||||
* map OCM share type (strings) to Nextcloud internal share types (integer)
|
||||
*
|
||||
* @param string $shareType
|
||||
* @return int
|
||||
*/
|
||||
private function mapShareTypeToNextcloud(string $shareType): int {
|
||||
return $shareType === 'group' ? IShare::TYPE_GROUP : IShare::TYPE_USER;
|
||||
private function mapShareTypeToNextcloud($shareType) {
|
||||
$result = IShare::TYPE_USER;
|
||||
if ($shareType === 'group') {
|
||||
$result = IShare::TYPE_GROUP;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function notifyAboutNewShare(string $shareWith, string $shareId, $ownerFederatedId, $sharedByFederatedId, string $name, string $displayName): void {
|
||||
private function notifyAboutNewShare($shareWith, $shareId, $ownerFederatedId, $sharedByFederatedId, $name, $displayName): void {
|
||||
$notification = $this->notificationManager->createNotification();
|
||||
$notification->setApp('files_sharing')
|
||||
->setUser($shareWith)
|
||||
@@ -262,14 +286,15 @@ class CloudFederationProviderFiles implements ISignedCloudFederationProvider {
|
||||
/**
|
||||
* process notification that the recipient accepted a share
|
||||
*
|
||||
* @param array{sharedSecret?: string} $notification
|
||||
* @param string $id
|
||||
* @param array $notification
|
||||
* @return array<string>
|
||||
* @throws ActionNotSupportedException
|
||||
* @throws AuthenticationFailedException
|
||||
* @throws BadRequestException
|
||||
* @throws HintException
|
||||
*/
|
||||
private function shareAccepted(string $id, array $notification): array {
|
||||
private function shareAccepted($id, array $notification) {
|
||||
if (!$this->isS2SEnabled()) {
|
||||
throw new ActionNotSupportedException('Server does not support federated cloud sharing');
|
||||
}
|
||||
@@ -308,22 +333,21 @@ class CloudFederationProviderFiles implements ISignedCloudFederationProvider {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param IShare $share
|
||||
* @throws ShareNotFound
|
||||
*/
|
||||
protected function executeAcceptShare(IShare $share): void {
|
||||
$user = $this->getCorrectUser($share);
|
||||
|
||||
protected function executeAcceptShare(IShare $share) {
|
||||
try {
|
||||
$fileId = $share->getNode()->getId();
|
||||
[$file, $link] = $this->getFile($user, $fileId);
|
||||
} catch (\Exception) {
|
||||
$fileId = (int)$share->getNode()->getId();
|
||||
[$file, $link] = $this->getFile($this->getCorrectUid($share), $fileId);
|
||||
} catch (\Exception $e) {
|
||||
throw new ShareNotFound();
|
||||
}
|
||||
|
||||
$event = $this->activityManager->generateEvent();
|
||||
$event->setApp('files_sharing')
|
||||
->setType('remote_share')
|
||||
->setAffectedUser($user->getUID())
|
||||
->setAffectedUser($this->getCorrectUid($share))
|
||||
->setSubject(RemoteShares::SUBJECT_REMOTE_SHARE_ACCEPTED, [$share->getSharedWith(), [$fileId => $file]])
|
||||
->setObject('files', $fileId, $file)
|
||||
->setLink($link);
|
||||
@@ -333,7 +357,8 @@ class CloudFederationProviderFiles implements ISignedCloudFederationProvider {
|
||||
/**
|
||||
* process notification that the recipient declined a share
|
||||
*
|
||||
* @param array{sharedSecret?: string} $notification
|
||||
* @param string $id
|
||||
* @param array $notification
|
||||
* @return array<string>
|
||||
* @throws ActionNotSupportedException
|
||||
* @throws AuthenticationFailedException
|
||||
@@ -342,7 +367,7 @@ class CloudFederationProviderFiles implements ISignedCloudFederationProvider {
|
||||
* @throws HintException
|
||||
*
|
||||
*/
|
||||
protected function shareDeclined(string $id, array $notification): array {
|
||||
protected function shareDeclined($id, array $notification) {
|
||||
if (!$this->isS2SEnabled()) {
|
||||
throw new ActionNotSupportedException('Server does not support federated cloud sharing');
|
||||
}
|
||||
@@ -369,6 +394,7 @@ class CloudFederationProviderFiles implements ISignedCloudFederationProvider {
|
||||
'sharedSecret' => $token,
|
||||
'message' => 'Recipient declined the re-share'
|
||||
]
|
||||
|
||||
);
|
||||
$this->cloudFederationProviderManager->sendNotification($remote, $notification);
|
||||
}
|
||||
@@ -381,24 +407,23 @@ class CloudFederationProviderFiles implements ISignedCloudFederationProvider {
|
||||
/**
|
||||
* delete declined share and create a activity
|
||||
*
|
||||
* @param IShare $share
|
||||
* @throws ShareNotFound
|
||||
*/
|
||||
protected function executeDeclineShare(IShare $share): void {
|
||||
protected function executeDeclineShare(IShare $share) {
|
||||
$this->federatedShareProvider->removeShareFromTable($share);
|
||||
|
||||
$user = $this->getCorrectUser($share);
|
||||
|
||||
try {
|
||||
$fileId = $share->getNode()->getId();
|
||||
[$file, $link] = $this->getFile($user, $fileId);
|
||||
} catch (\Exception) {
|
||||
$fileId = (int)$share->getNode()->getId();
|
||||
[$file, $link] = $this->getFile($this->getCorrectUid($share), $fileId);
|
||||
} catch (\Exception $e) {
|
||||
throw new ShareNotFound();
|
||||
}
|
||||
|
||||
$event = $this->activityManager->generateEvent();
|
||||
$event->setApp('files_sharing')
|
||||
->setType('remote_share')
|
||||
->setAffectedUser($user->getUID())
|
||||
->setAffectedUser($this->getCorrectUid($share))
|
||||
->setSubject(RemoteShares::SUBJECT_REMOTE_SHARE_DECLINED, [$share->getSharedWith(), [$fileId => $file]])
|
||||
->setObject('files', $fileId, $file)
|
||||
->setLink($link);
|
||||
@@ -408,12 +433,13 @@ class CloudFederationProviderFiles implements ISignedCloudFederationProvider {
|
||||
/**
|
||||
* received the notification that the owner unshared a file from you
|
||||
*
|
||||
* @param array{sharedSecret?: string} $notification
|
||||
* @param string $id
|
||||
* @param array $notification
|
||||
* @return array<string>
|
||||
* @throws AuthenticationFailedException
|
||||
* @throws BadRequestException
|
||||
*/
|
||||
private function undoReshare(string $id, array $notification): array {
|
||||
private function undoReshare($id, array $notification) {
|
||||
if (!isset($notification['sharedSecret'])) {
|
||||
throw new BadRequestException(['sharedSecret']);
|
||||
}
|
||||
@@ -429,12 +455,13 @@ class CloudFederationProviderFiles implements ISignedCloudFederationProvider {
|
||||
/**
|
||||
* unshare file from self
|
||||
*
|
||||
* @param array{sharedSecret?: string} $notification
|
||||
* @param string $id
|
||||
* @param array $notification
|
||||
* @return array<string>
|
||||
* @throws ActionNotSupportedException
|
||||
* @throws BadRequestException
|
||||
*/
|
||||
private function unshare(string $id, array $notification): array {
|
||||
private function unshare($id, array $notification) {
|
||||
if (!$this->isS2SEnabled(true)) {
|
||||
throw new ActionNotSupportedException('incoming shares disabled!');
|
||||
}
|
||||
@@ -444,29 +471,56 @@ class CloudFederationProviderFiles implements ISignedCloudFederationProvider {
|
||||
}
|
||||
$token = $notification['sharedSecret'];
|
||||
|
||||
$share = $this->externalShareMapper->getShareByRemoteIdAndToken($id, $token);
|
||||
$qb = $this->connection->getQueryBuilder();
|
||||
$qb->select('*')
|
||||
->from('share_external')
|
||||
->where(
|
||||
$qb->expr()->andX(
|
||||
$qb->expr()->eq('remote_id', $qb->createNamedParameter($id)),
|
||||
$qb->expr()->eq('share_token', $qb->createNamedParameter($token))
|
||||
)
|
||||
);
|
||||
|
||||
if ($token && $id && $share !== null) {
|
||||
$remote = $this->cleanupRemote($share->getRemote());
|
||||
$result = $qb->executeQuery();
|
||||
$share = $result->fetchAssociative();
|
||||
$result->closeCursor();
|
||||
|
||||
$owner = $this->cloudIdManager->getCloudId($share->getOwner(), $remote);
|
||||
$mountpoint = $share->getMountpoint();
|
||||
$user = $share->getUser();
|
||||
if ($token && $id && !empty($share)) {
|
||||
$remote = $this->cleanupRemote($share['remote']);
|
||||
|
||||
$this->externalShareMapper->delete($share);
|
||||
$owner = $this->cloudIdManager->getCloudId($share['owner'], $remote);
|
||||
$mountpoint = $share['mountpoint'];
|
||||
$user = $share['user'];
|
||||
|
||||
$qb = $this->connection->getQueryBuilder();
|
||||
$qb->delete('share_external')
|
||||
->where(
|
||||
$qb->expr()->andX(
|
||||
$qb->expr()->eq('remote_id', $qb->createNamedParameter($id)),
|
||||
$qb->expr()->eq('share_token', $qb->createNamedParameter($token))
|
||||
)
|
||||
);
|
||||
|
||||
$qb->executeStatement();
|
||||
|
||||
// delete all child in case of a group share
|
||||
$qb = $this->connection->getQueryBuilder();
|
||||
$qb->delete('share_external')
|
||||
->where($qb->expr()->eq('parent', $qb->createNamedParameter((int)$share['id'])));
|
||||
$qb->executeStatement();
|
||||
|
||||
$ownerDisplayName = $this->getUserDisplayName($owner->getId());
|
||||
|
||||
if ($share->getShareType() === IShare::TYPE_USER) {
|
||||
if ($share->getAccepted()) {
|
||||
if ((int)$share['share_type'] === IShare::TYPE_USER) {
|
||||
if ($share['accepted']) {
|
||||
$path = trim($mountpoint, '/');
|
||||
} else {
|
||||
$path = trim($share->getName(), '/');
|
||||
$path = trim($share['name'], '/');
|
||||
}
|
||||
$notification = $this->notificationManager->createNotification();
|
||||
$notification->setApp('files_sharing')
|
||||
->setUser($share->getUser())
|
||||
->setObject('remote_share', $share->getId());
|
||||
->setUser($share['user'])
|
||||
->setObject('remote_share', (string)$share['id']);
|
||||
$this->notificationManager->markProcessed($notification);
|
||||
|
||||
$event = $this->activityManager->generateEvent();
|
||||
@@ -474,7 +528,7 @@ class CloudFederationProviderFiles implements ISignedCloudFederationProvider {
|
||||
->setType('remote_share')
|
||||
->setSubject(RemoteShares::SUBJECT_REMOTE_SHARE_UNSHARED, [$owner->getId(), $path, $ownerDisplayName])
|
||||
->setAffectedUser($user)
|
||||
->setObject('remote_share', $share->getId(), $path);
|
||||
->setObject('remote_share', (int)$share['id'], $path);
|
||||
Server::get(IActivityManager::class)->publish($event);
|
||||
}
|
||||
}
|
||||
@@ -482,7 +536,7 @@ class CloudFederationProviderFiles implements ISignedCloudFederationProvider {
|
||||
return [];
|
||||
}
|
||||
|
||||
private function cleanupRemote(string $remote): string {
|
||||
private function cleanupRemote($remote) {
|
||||
$remote = substr($remote, strpos($remote, '://') + 3);
|
||||
|
||||
return rtrim($remote, '/');
|
||||
@@ -491,14 +545,15 @@ class CloudFederationProviderFiles implements ISignedCloudFederationProvider {
|
||||
/**
|
||||
* recipient of a share request to re-share the file with another user
|
||||
*
|
||||
* @param array{sharedSecret?: string, shareWith?: string, senderId?: string} $notification
|
||||
* @param string $id
|
||||
* @param array $notification
|
||||
* @return array<string>
|
||||
* @throws AuthenticationFailedException
|
||||
* @throws BadRequestException
|
||||
* @throws ProviderCouldNotAddShareException
|
||||
* @throws ShareNotFound
|
||||
*/
|
||||
protected function reshareRequested(string $id, array $notification) {
|
||||
protected function reshareRequested($id, array $notification) {
|
||||
if (!isset($notification['sharedSecret'])) {
|
||||
throw new BadRequestException(['sharedSecret']);
|
||||
}
|
||||
@@ -540,7 +595,7 @@ class CloudFederationProviderFiles implements ISignedCloudFederationProvider {
|
||||
$share->setSharedBy($share->getSharedWith());
|
||||
$share->setSharedWith($shareWith);
|
||||
$result = $this->federatedShareProvider->create($share);
|
||||
$this->federatedShareProvider->storeRemoteId($result->getId(), $senderId);
|
||||
$this->federatedShareProvider->storeRemoteId((int)$result->getId(), $senderId);
|
||||
return ['token' => $result->getToken(), 'providerId' => $result->getId()];
|
||||
} else {
|
||||
throw new ProviderCouldNotAddShareException('resharing not allowed for share: ' . $id);
|
||||
@@ -548,22 +603,71 @@ class CloudFederationProviderFiles implements ISignedCloudFederationProvider {
|
||||
}
|
||||
|
||||
/**
|
||||
* Update permission of a re-share so that the share dialog shows the right
|
||||
* update permission of a re-share so that the share dialog shows the right
|
||||
* permission if the owner or the sender changes the permission
|
||||
*
|
||||
* @return string[]
|
||||
* @param string $id
|
||||
* @param array $notification
|
||||
* @return array<string>
|
||||
* @throws AuthenticationFailedException
|
||||
* @throws BadRequestException
|
||||
*/
|
||||
protected function updateResharePermissions(string $id, array $notification): array {
|
||||
protected function updateResharePermissions($id, array $notification) {
|
||||
throw new HintException('Updating reshares not allowed');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list{?string, string} with internal path of the file and a absolute link to it
|
||||
* translate OCM Permissions to Nextcloud permissions
|
||||
*
|
||||
* @param array $ocmPermissions
|
||||
* @return int
|
||||
* @throws BadRequestException
|
||||
*/
|
||||
private function getFile(IUser $user, int $fileSource): array {
|
||||
$this->setupManager->setupForUser($user);
|
||||
protected function ocmPermissions2ncPermissions(array $ocmPermissions) {
|
||||
$ncPermissions = 0;
|
||||
foreach ($ocmPermissions as $permission) {
|
||||
switch (strtolower($permission)) {
|
||||
case 'read':
|
||||
$ncPermissions += Constants::PERMISSION_READ;
|
||||
break;
|
||||
case 'write':
|
||||
$ncPermissions += Constants::PERMISSION_CREATE + Constants::PERMISSION_UPDATE;
|
||||
break;
|
||||
case 'share':
|
||||
$ncPermissions += Constants::PERMISSION_SHARE;
|
||||
break;
|
||||
default:
|
||||
throw new BadRequestException(['permission']);
|
||||
}
|
||||
}
|
||||
|
||||
return $ncPermissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* update permissions in database
|
||||
*
|
||||
* @param IShare $share
|
||||
* @param int $permissions
|
||||
*/
|
||||
protected function updatePermissionsInDatabase(IShare $share, $permissions) {
|
||||
$query = $this->connection->getQueryBuilder();
|
||||
$query->update('share')
|
||||
->where($query->expr()->eq('id', $query->createNamedParameter($share->getId())))
|
||||
->set('permissions', $query->createNamedParameter($permissions))
|
||||
->executeStatement();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* get file
|
||||
*
|
||||
* @param string $user
|
||||
* @param int $fileSource
|
||||
* @return array with internal path of the file and a absolute link to it
|
||||
*/
|
||||
private function getFile($user, $fileSource) {
|
||||
\OC_Util::setupFS($user);
|
||||
|
||||
try {
|
||||
$file = Filesystem::getPath($fileSource);
|
||||
@@ -577,26 +681,30 @@ class CloudFederationProviderFiles implements ISignedCloudFederationProvider {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we are the initiator or the owner of a re-share and return the correct UID
|
||||
* check if we are the initiator or the owner of a re-share and return the correct UID
|
||||
*
|
||||
* @param IShare $share
|
||||
* @return string
|
||||
*/
|
||||
protected function getCorrectUser(IShare $share): IUser {
|
||||
if ($user = $this->userManager->get($share->getShareOwner())) {
|
||||
return $user;
|
||||
protected function getCorrectUid(IShare $share) {
|
||||
if ($this->userManager->userExists($share->getShareOwner())) {
|
||||
return $share->getShareOwner();
|
||||
}
|
||||
|
||||
$user = $this->userManager->get($share->getSharedBy());
|
||||
if ($user === null) {
|
||||
throw new \RuntimeException('Neither the share owner or the share initiator exist');
|
||||
}
|
||||
return $user;
|
||||
return $share->getSharedBy();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* check if we got the right share
|
||||
*
|
||||
* @param IShare $share
|
||||
* @param string $token
|
||||
* @return bool
|
||||
* @throws AuthenticationFailedException
|
||||
*/
|
||||
protected function verifyShare(IShare $share, string $token): bool {
|
||||
protected function verifyShare(IShare $share, $token) {
|
||||
if (
|
||||
$share->getShareType() === IShare::TYPE_REMOTE
|
||||
&& $share->getToken() === $token
|
||||
@@ -620,9 +728,12 @@ class CloudFederationProviderFiles implements ISignedCloudFederationProvider {
|
||||
|
||||
|
||||
/**
|
||||
* Check if server-to-server sharing is enabled
|
||||
* check if server-to-server sharing is enabled
|
||||
*
|
||||
* @param bool $incoming
|
||||
* @return bool
|
||||
*/
|
||||
private function isS2SEnabled(bool $incoming = false): bool {
|
||||
private function isS2SEnabled($incoming = false) {
|
||||
$result = $this->appManager->isEnabledForUser('files_sharing');
|
||||
|
||||
if ($incoming) {
|
||||
@@ -634,11 +745,19 @@ class CloudFederationProviderFiles implements ISignedCloudFederationProvider {
|
||||
return $result;
|
||||
}
|
||||
|
||||
#[Override]
|
||||
public function getSupportedShareTypes(): array {
|
||||
|
||||
/**
|
||||
* get the supported share types, e.g. "user", "group", etc.
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @since 14.0.0
|
||||
*/
|
||||
public function getSupportedShareTypes() {
|
||||
return ['user', 'group'];
|
||||
}
|
||||
|
||||
|
||||
public function getUserDisplayName(string $userId): string {
|
||||
// check if gss is enabled and available
|
||||
if (!$this->appManager->isEnabledForAnyone('globalsiteselector')
|
||||
@@ -649,7 +768,7 @@ class CloudFederationProviderFiles implements ISignedCloudFederationProvider {
|
||||
try {
|
||||
$slaveService = Server::get(SlaveService::class);
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->error(
|
||||
Server::get(LoggerInterface::class)->error(
|
||||
$e->getMessage(),
|
||||
['exception' => $e]
|
||||
);
|
||||
@@ -659,7 +778,13 @@ class CloudFederationProviderFiles implements ISignedCloudFederationProvider {
|
||||
return $slaveService->getUserDisplayName($this->cloudIdManager->removeProtocolFromUrl($userId), false);
|
||||
}
|
||||
|
||||
#[Override]
|
||||
/**
|
||||
* @inheritDoc
|
||||
*
|
||||
* @param string $sharedSecret
|
||||
* @param array $payload
|
||||
* @return string
|
||||
*/
|
||||
public function getFederationIdFromSharedSecret(
|
||||
#[SensitiveParameter]
|
||||
string $sharedSecret,
|
||||
@@ -675,7 +800,7 @@ class CloudFederationProviderFiles implements ISignedCloudFederationProvider {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $share->getUser() . '@' . $share->getRemote();
|
||||
return $share['user'] . '@' . $share['remote'];
|
||||
}
|
||||
|
||||
// if uid_owner is a local account, the request comes from the recipient
|
||||
|
||||
@@ -192,7 +192,8 @@
|
||||
"description": "ID of the user that receives the share"
|
||||
},
|
||||
"remoteId": {
|
||||
"type": "string",
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"nullable": true,
|
||||
"default": null,
|
||||
"description": "ID of the remote"
|
||||
@@ -312,7 +313,8 @@
|
||||
"description": "ID of the share",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -403,7 +405,7 @@
|
||||
"/ocs/v2.php/cloud/shares/{id}/permissions": {
|
||||
"post": {
|
||||
"operationId": "request_handler-update-permissions",
|
||||
"summary": "Update share information to keep federated re-shares in sync.",
|
||||
"summary": "update share information to keep federated re-shares in sync",
|
||||
"tags": [
|
||||
"request_handler"
|
||||
],
|
||||
@@ -448,7 +450,8 @@
|
||||
"description": "ID of the share",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -563,7 +566,8 @@
|
||||
"description": "ID of the remote share",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -660,7 +664,8 @@
|
||||
"description": "ID of the remote share",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -747,7 +752,8 @@
|
||||
"description": "ID of the share",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -834,7 +840,8 @@
|
||||
"description": "ID of the share",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -8,8 +8,10 @@ declare(strict_types=1);
|
||||
*/
|
||||
namespace OCA\FederatedFileSharing\Tests;
|
||||
|
||||
use OCA\FederatedFileSharing\AddressHandler;
|
||||
use OCA\FederatedFileSharing\Controller\RequestHandlerController;
|
||||
use OCA\FederatedFileSharing\FederatedShareProvider;
|
||||
use OCA\FederatedFileSharing\Notifications;
|
||||
use OCP\AppFramework\Http\DataResponse;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
use OCP\Federation\ICloudFederationFactory;
|
||||
@@ -19,6 +21,8 @@ use OCP\Federation\ICloudFederationShare;
|
||||
use OCP\Federation\ICloudIdManager;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\IRequest;
|
||||
use OCP\IUserManager;
|
||||
use OCP\Share;
|
||||
use OCP\Share\IShare;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Psr\Log\LoggerInterface;
|
||||
@@ -38,11 +42,15 @@ class RequestHandlerControllerTest extends \Test\TestCase {
|
||||
|
||||
private RequestHandlerController $requestHandler;
|
||||
private FederatedShareProvider&MockObject $federatedShareProvider;
|
||||
private Notifications&MockObject $notifications;
|
||||
private AddressHandler&MockObject $addressHandler;
|
||||
private IUserManager&MockObject $userManager;
|
||||
private IShare&MockObject $share;
|
||||
private ICloudIdManager&MockObject $cloudIdManager;
|
||||
private LoggerInterface&MockObject $logger;
|
||||
private IRequest&MockObject $request;
|
||||
private IDBConnection&MockObject $connection;
|
||||
private Share\IManager&MockObject $shareManager;
|
||||
private ICloudFederationFactory&MockObject $cloudFederationFactory;
|
||||
private ICloudFederationProviderManager&MockObject $cloudFederationProviderManager;
|
||||
private ICloudFederationProvider&MockObject $cloudFederationProvider;
|
||||
@@ -59,9 +67,13 @@ class RequestHandlerControllerTest extends \Test\TestCase {
|
||||
$this->federatedShareProvider->expects($this->any())->method('getShareById')
|
||||
->willReturn($this->share);
|
||||
|
||||
$this->notifications = $this->createMock(Notifications::class);
|
||||
$this->addressHandler = $this->createMock(AddressHandler::class);
|
||||
$this->userManager = $this->createMock(IUserManager::class);
|
||||
$this->cloudIdManager = $this->createMock(ICloudIdManager::class);
|
||||
$this->request = $this->createMock(IRequest::class);
|
||||
$this->connection = $this->createMock(IDBConnection::class);
|
||||
$this->shareManager = $this->createMock(Share\IManager::class);
|
||||
$this->cloudFederationFactory = $this->createMock(ICloudFederationFactory::class);
|
||||
$this->cloudFederationProviderManager = $this->createMock(ICloudFederationProviderManager::class);
|
||||
$this->cloudFederationProvider = $this->createMock(ICloudFederationProvider::class);
|
||||
@@ -76,6 +88,10 @@ class RequestHandlerControllerTest extends \Test\TestCase {
|
||||
$this->request,
|
||||
$this->federatedShareProvider,
|
||||
$this->connection,
|
||||
$this->shareManager,
|
||||
$this->notifications,
|
||||
$this->addressHandler,
|
||||
$this->userManager,
|
||||
$this->cloudIdManager,
|
||||
$this->logger,
|
||||
$this->cloudFederationFactory,
|
||||
@@ -90,7 +106,7 @@ class RequestHandlerControllerTest extends \Test\TestCase {
|
||||
$this->user2,
|
||||
'name',
|
||||
'',
|
||||
'1',
|
||||
1,
|
||||
$this->ownerCloudId,
|
||||
$this->owner,
|
||||
$this->user1CloudId,
|
||||
@@ -109,13 +125,13 @@ class RequestHandlerControllerTest extends \Test\TestCase {
|
||||
$this->cloudFederationProvider->expects($this->once())->method('shareReceived')
|
||||
->with($this->cloudFederationShare);
|
||||
|
||||
$result = $this->requestHandler->createShare('localhost', 'token', 'name', $this->owner, $this->user1, $this->user2, '1', $this->user1CloudId, $this->ownerCloudId);
|
||||
$result = $this->requestHandler->createShare('localhost', 'token', 'name', $this->owner, $this->user1, $this->user2, 1, $this->user1CloudId, $this->ownerCloudId);
|
||||
|
||||
$this->assertInstanceOf(DataResponse::class, $result);
|
||||
}
|
||||
|
||||
public function testDeclineShare(): void {
|
||||
$id = '42';
|
||||
$id = 42;
|
||||
|
||||
$notification = [
|
||||
'sharedSecret' => 'token',
|
||||
@@ -138,7 +154,7 @@ class RequestHandlerControllerTest extends \Test\TestCase {
|
||||
|
||||
|
||||
public function testAcceptShare(): void {
|
||||
$id = '42';
|
||||
$id = 42;
|
||||
|
||||
$notification = [
|
||||
'sharedSecret' => 'token',
|
||||
|
||||
@@ -309,7 +309,7 @@ OC.L10N.register(
|
||||
"Move" : "Flyt",
|
||||
"Move or copy operation failed" : "Flytte- eller kopioperationen fejlede",
|
||||
"Move or copy" : "Flyt eller kopiér",
|
||||
"Open folder" : "Åbn mappe",
|
||||
"Open folder" : "Åben mappe",
|
||||
"Open folder {displayName}" : "Åben mappe {displayName}",
|
||||
"Open in Files" : "Åben i Filer",
|
||||
"Open locally" : "Åben lokalt",
|
||||
|
||||
@@ -307,7 +307,7 @@
|
||||
"Move" : "Flyt",
|
||||
"Move or copy operation failed" : "Flytte- eller kopioperationen fejlede",
|
||||
"Move or copy" : "Flyt eller kopiér",
|
||||
"Open folder" : "Åbn mappe",
|
||||
"Open folder" : "Åben mappe",
|
||||
"Open folder {displayName}" : "Åben mappe {displayName}",
|
||||
"Open in Files" : "Åben i Filer",
|
||||
"Open locally" : "Åben lokalt",
|
||||
|
||||
@@ -183,7 +183,7 @@ OC.L10N.register(
|
||||
"Preparing …" : "Bereite vor …",
|
||||
"Refresh" : "Aktualisieren",
|
||||
"All files have been santized for Windows filename support." : "Alle Dateien wurden für die Windows-Dateinamenunterstützung bereinigt.",
|
||||
"Some files could not be sanitized, please check your logs." : "Einige Dateien konnten nicht bereinigt werden. Bitte die Protokolle überprüfen.",
|
||||
"Some files could not be sanitized, please check your logs." : "Einige Dateien konnten nicht bereinigt werden. Bitte überprüfen Sie Ihre Protokolle.",
|
||||
"Sanitization errors" : "Bereinigungsfehler",
|
||||
"Not sanitized filenames" : "Nicht bereinigte Dateinamen",
|
||||
"Windows filename support has been enabled." : "Windows Dateinamenunterstützung wurde aktiviert.",
|
||||
|
||||
@@ -181,7 +181,7 @@
|
||||
"Preparing …" : "Bereite vor …",
|
||||
"Refresh" : "Aktualisieren",
|
||||
"All files have been santized for Windows filename support." : "Alle Dateien wurden für die Windows-Dateinamenunterstützung bereinigt.",
|
||||
"Some files could not be sanitized, please check your logs." : "Einige Dateien konnten nicht bereinigt werden. Bitte die Protokolle überprüfen.",
|
||||
"Some files could not be sanitized, please check your logs." : "Einige Dateien konnten nicht bereinigt werden. Bitte überprüfen Sie Ihre Protokolle.",
|
||||
"Sanitization errors" : "Bereinigungsfehler",
|
||||
"Not sanitized filenames" : "Nicht bereinigte Dateinamen",
|
||||
"Windows filename support has been enabled." : "Windows Dateinamenunterstützung wurde aktiviert.",
|
||||
|
||||
@@ -116,7 +116,6 @@ OC.L10N.register(
|
||||
"General" : "Ogólne",
|
||||
"Sort favorites first" : "Najpierw sortuj ulubione",
|
||||
"Sort folders before files" : "Sortuj katalogi przed plikami",
|
||||
"Enable folder tree view" : "Włącz widok drzewa folderów",
|
||||
"Default view" : "Widok domyślny",
|
||||
"All files" : "Wszystkie pliki",
|
||||
"Personal files" : "Pliki osobiste",
|
||||
@@ -140,10 +139,7 @@ OC.L10N.register(
|
||||
"Show those shortcuts" : "Pokaż te skróty",
|
||||
"Warnings" : "Ostrzeżenie",
|
||||
"Warn before changing a file extension" : "Ostrzegaj przed zmianą rozszerzenia pliku",
|
||||
"Warn before deleting a file" : "Ostrzegaj przed usunięciem pliku",
|
||||
"WebDAV URL" : "URL WebDAV",
|
||||
"Create an app password" : "Utwórz hasło aplikacji",
|
||||
"Required for WebDAV authentication because Two-Factor Authentication is enabled for this account." : "Wymagane do uwierzytelniania WebDAV, ponieważ dla tego konta włączono uwierzytelnianie dwuskładnikowe.",
|
||||
"How to access files using WebDAV" : "Jak uzyskać dostęp do plików przez WebDAV",
|
||||
"Total rows summary" : "Podsumowanie wszystkich wierszy",
|
||||
"Toggle selection for all files and folders" : "Przełącz zaznaczenie dla wszystkich plików i katalogów",
|
||||
@@ -269,9 +265,6 @@ OC.L10N.register(
|
||||
"Failed to convert files: {message}" : "Nie udało się przekonwertować plików: {message}",
|
||||
"All files failed to be converted" : "Nie udało się przekonwertować żadnego pliku",
|
||||
"One file could not be converted: {message}" : "Jednego pliku nie można przekonwertować: {message}",
|
||||
"_%n file could not be converted_::_%n files could not be converted_" : ["%n plik nie mógł zostać przekonwertowany","%n pliki nie mogły zostać przekonwertowane","%n plików nie mogło zostać przekonwertowanych","%n plików nie mogło zostać przekonwertowanych"],
|
||||
"_%n file converted_::_%n files converted_" : ["%n plik przekonwertowany","%n pliki przekonwertowane","%n plików przekonwertowanych","%n plików przekonwertowanych"],
|
||||
"Files converted" : "Pliki przekonwertowane",
|
||||
"Failed to convert files" : "Nie udało się przekonwertować plików",
|
||||
"Converting file …" : "Konwertowanie pliku …",
|
||||
"File successfully converted" : "Plik pomyślnie przekonwertowany",
|
||||
|
||||
@@ -114,7 +114,6 @@
|
||||
"General" : "Ogólne",
|
||||
"Sort favorites first" : "Najpierw sortuj ulubione",
|
||||
"Sort folders before files" : "Sortuj katalogi przed plikami",
|
||||
"Enable folder tree view" : "Włącz widok drzewa folderów",
|
||||
"Default view" : "Widok domyślny",
|
||||
"All files" : "Wszystkie pliki",
|
||||
"Personal files" : "Pliki osobiste",
|
||||
@@ -138,10 +137,7 @@
|
||||
"Show those shortcuts" : "Pokaż te skróty",
|
||||
"Warnings" : "Ostrzeżenie",
|
||||
"Warn before changing a file extension" : "Ostrzegaj przed zmianą rozszerzenia pliku",
|
||||
"Warn before deleting a file" : "Ostrzegaj przed usunięciem pliku",
|
||||
"WebDAV URL" : "URL WebDAV",
|
||||
"Create an app password" : "Utwórz hasło aplikacji",
|
||||
"Required for WebDAV authentication because Two-Factor Authentication is enabled for this account." : "Wymagane do uwierzytelniania WebDAV, ponieważ dla tego konta włączono uwierzytelnianie dwuskładnikowe.",
|
||||
"How to access files using WebDAV" : "Jak uzyskać dostęp do plików przez WebDAV",
|
||||
"Total rows summary" : "Podsumowanie wszystkich wierszy",
|
||||
"Toggle selection for all files and folders" : "Przełącz zaznaczenie dla wszystkich plików i katalogów",
|
||||
@@ -267,9 +263,6 @@
|
||||
"Failed to convert files: {message}" : "Nie udało się przekonwertować plików: {message}",
|
||||
"All files failed to be converted" : "Nie udało się przekonwertować żadnego pliku",
|
||||
"One file could not be converted: {message}" : "Jednego pliku nie można przekonwertować: {message}",
|
||||
"_%n file could not be converted_::_%n files could not be converted_" : ["%n plik nie mógł zostać przekonwertowany","%n pliki nie mogły zostać przekonwertowane","%n plików nie mogło zostać przekonwertowanych","%n plików nie mogło zostać przekonwertowanych"],
|
||||
"_%n file converted_::_%n files converted_" : ["%n plik przekonwertowany","%n pliki przekonwertowane","%n plików przekonwertowanych","%n plików przekonwertowanych"],
|
||||
"Files converted" : "Pliki przekonwertowane",
|
||||
"Failed to convert files" : "Nie udało się przekonwertować plików",
|
||||
"Converting file …" : "Konwertowanie pliku …",
|
||||
"File successfully converted" : "Plik pomyślnie przekonwertowany",
|
||||
|
||||
+1
-80
@@ -51,10 +51,6 @@ OC.L10N.register(
|
||||
"You do not have permission to create a file at the specified location" : "Nemáte oprávnenie vytvoriť súbor v zadanom umiestnení",
|
||||
"The file could not be converted." : "Súbor nemohol byť skonvertovaný.",
|
||||
"Could not get relative path to converted file" : "Nepodarilo sa získať relatícnu cestu ku skonvertovanému súboru",
|
||||
"Limit must be a positive integer." : "Limit musí byť kladné celé číslo.",
|
||||
"The replacement character may only be a single character." : "Zástupný znak môže byť iba jeden znak.",
|
||||
"Filename sanitization already started." : "Sanitizácia názvu súboru bola už zahájená.",
|
||||
"No filename sanitization in progress." : "Žiadna sanitácia názvu súboru neprebieha.",
|
||||
"Favorite files" : "Obľúbené súbory",
|
||||
"No favorites" : "Žiadne obľúbené",
|
||||
"More favorites" : "Viac obľúbených",
|
||||
@@ -95,11 +91,6 @@ OC.L10N.register(
|
||||
"Renamed \"{oldName}\" to \"{newName}\"" : "Premenované z \"{oldName}\" na \"{newName}\"",
|
||||
"Rename file" : "Premenovať súbor",
|
||||
"Folder" : "Priečinok",
|
||||
"Unknown file type" : "Neznámy typ súboru",
|
||||
"{ext} image" : "{ext} obrázok",
|
||||
"{ext} video" : "{ext} video",
|
||||
"{ext} audio" : "{ext} zvuk",
|
||||
"{ext} text" : "{ext} text",
|
||||
"Pending" : "Čaká",
|
||||
"Unknown date" : "Neznámy dátum",
|
||||
"Clear filter" : "Vyčistiť filter",
|
||||
@@ -110,14 +101,10 @@ OC.L10N.register(
|
||||
"Remove filter" : "Odstrániť filter",
|
||||
"Appearance" : "Vzhľad",
|
||||
"Show hidden files" : "Zobraziť skryté súbory",
|
||||
"Show file type column" : "Zobraziť stĺpec typu súboru",
|
||||
"Show file extensions" : "Zobraziť prípony súborov",
|
||||
"Crop image previews" : "Orezať náhľady obrázkov",
|
||||
"General" : "Všeobecné",
|
||||
"Sort favorites first" : "Zoradiť od najobľúbenejších",
|
||||
"Sort folders before files" : "Zoradiť adresáre pred súbormi",
|
||||
"Enable folder tree view" : "Povoliť zobrazenie stromu priečinkov",
|
||||
"Default view" : "Predvolené zobrazenie",
|
||||
"All files" : "Všetky súbory",
|
||||
"Personal files" : "Osobné súbory",
|
||||
"Additional settings" : "Ďalšie nastavenia",
|
||||
@@ -126,33 +113,16 @@ OC.L10N.register(
|
||||
"Selection" : "Výber",
|
||||
"Select all files" : "Vybrať všetky súbory",
|
||||
"Deselect all" : "Odznačiť všetko",
|
||||
"Select or deselect" : "Vybrať alebo zrušiť výber",
|
||||
"Select a range" : "Vybrať rozsah",
|
||||
"Navigation" : "Navigácia",
|
||||
"Go to parent folder" : "Prejsť do nadriadeného priečinka",
|
||||
"Go to file above" : "Prejsť na súbor vyššie",
|
||||
"Go to file below" : "Prejsť na súbor nižšie",
|
||||
"Go left in grid" : "Prejsť doľava v mriežke",
|
||||
"Go right in grid" : "Prejsť doprava v mriežke",
|
||||
"View" : "Zobraziť",
|
||||
"Toggle grid view" : "Prepnúť zobrazenie mriežky",
|
||||
"Open file sidebar" : "Otvoriť bočný panel súboru",
|
||||
"Show those shortcuts" : "Zobraziť klávesové skratky",
|
||||
"Warnings" : "Upozornenia",
|
||||
"Warn before changing a file extension" : "Upozorniť pred zmenou prípony súboru",
|
||||
"Warn before deleting a file" : "Upozorniť pred vymazaním súboru",
|
||||
"WebDAV URL" : "WebDAV URL",
|
||||
"Create an app password" : "Vytvoriť heslo pre aplikáciu",
|
||||
"Required for WebDAV authentication because Two-Factor Authentication is enabled for this account." : "Vyžaduje sa pre autentifikáciu WebDAV, pretože pre tento účet je povolená dvojfaktorová autentifikácia.",
|
||||
"How to access files using WebDAV" : "Ako získať prístup k súborom pomocou WebDAV",
|
||||
"Total rows summary" : "Súčet všetkých riadkov",
|
||||
"Toggle selection for all files and folders" : "Prepnúť výber pre všetky súbory a adresáre",
|
||||
"Name" : "Názov",
|
||||
"File type" : "Typ súboru",
|
||||
"Size" : "Veľkosť",
|
||||
"{displayName}: failed on some elements" : "{displayName}: zlyhalo na niektorých prvkoch",
|
||||
"{displayName}: done" : "{displayName}: hotovo",
|
||||
"{displayName}: failed" : "{displayName}: zlyhalo",
|
||||
"(selected)" : "(vybrané)",
|
||||
"List of files and folders." : "Zoznam súborov a priečinkov.",
|
||||
"You have used your space quota and cannot upload files anymore." : "Už ste využili kapacitu úložného priestoru a nie je možné nahrávať ďalšie súbory.",
|
||||
@@ -160,10 +130,6 @@ OC.L10N.register(
|
||||
"This list is not fully rendered for performance reasons. The files will be rendered as you navigate through the list." : "Tento zoznam nie je úplne vykreslený z dôvodov výkonu. Súbory budú vykreslené, keď budete prechádzať zoznamom.",
|
||||
"File not found" : "Súbor nenájdený",
|
||||
"_{count} selected_::_{count} selected_" : ["{count} vybraný","{count} vybrané","{count} vybraných","{count} vybraných"],
|
||||
"Search everywhere …" : "Hľadať všade ...",
|
||||
"Search here …" : "Hľadať tu …",
|
||||
"Search scope options" : "Možnosti rozsahu vyhľadávania",
|
||||
"Search here" : "Hľadať tu",
|
||||
"{usedQuotaByte} used" : "{usedQuotaByte} použitých",
|
||||
"{used} of {quota} used" : "použitých {used} z {quota}",
|
||||
"{relative}% used" : "{relative}% použitých",
|
||||
@@ -175,27 +141,9 @@ OC.L10N.register(
|
||||
"Create new folder" : "Vytvoriť nový priečinok",
|
||||
"This name is already in use." : "Toto meno je už používané.",
|
||||
"Create" : "Vytvoriť",
|
||||
"Files starting with a dot are hidden by default" : "Súbory, ktoré začínajú bodkou, sú predvolene skryté.",
|
||||
"Failed to start filename sanitization." : "Nezískalo sa spustenie sanitizácie názvu súboru.",
|
||||
"Failed to refresh filename sanitization status." : "Nepodarilo sa obnoviť stav sanitácie názvu súboru.",
|
||||
"Filename sanitization in progress." : "Prebieha sanitácia názvu súboru.",
|
||||
"Currently {processedUsers} of {totalUsers} accounts are already processed." : "Aktuálne je spracovaných {processedUsers} z {totalUsers} účtov.",
|
||||
"Preparing …" : "Pripravuje sa …",
|
||||
"Refresh" : "Obnoviť",
|
||||
"All files have been santized for Windows filename support." : "Všetky súbory boli upravené na podporu názvov súborov vo Windows.",
|
||||
"Some files could not be sanitized, please check your logs." : "Niektoré súbory nebolo možné upravené, prosím, skontrolujte svoje protokoly.",
|
||||
"Sanitization errors" : "Chyby v sanitizácii",
|
||||
"Not sanitized filenames" : "Nezabezpečené názvy súborov",
|
||||
"Windows filename support has been enabled." : "Podpora názvov súborov vo Windows bola povolená.",
|
||||
"While this blocks users from creating new files with unsupported filenames, existing files are not yet renamed and thus still may break sync on Windows." : "Kým toto blokuje používateľov v tvorbe nových súborov s nepodporovanými názvami, existujúce súbory ešte neboli premenované a teda môžu stále spôsobiť problémy so synchronizáciou na Windows.",
|
||||
"You can trigger a rename of files with invalid filenames, this will be done in the background and may take some time." : "Môžete spustiť premenovanie súborov s neplatnými názvami, tento proces sa vykoná na pozadí a môže trvať nejaký čas.",
|
||||
"Please note that this may cause high workload on the sync clients." : "Upozorňujeme, že to môže spôsobiť vysokú záťaž pre klientov synchronizácie.",
|
||||
"Limit" : "Limit",
|
||||
"This allows to configure how many users should be processed in one background job run." : "Toto umožňuje nastaviť, koľko používateľov by malo byť spracovaných v jednom spustení úlohy na pozadí.",
|
||||
"Sanitize filenames" : "Vyčistiť názvy súborov",
|
||||
"(starting)" : "(začína sa)",
|
||||
"Fill template fields" : "Vyplňte položky šablóny",
|
||||
"Submitting fields …" : "Odosielanie polí …",
|
||||
"Submit" : "Odoslať",
|
||||
"Choose a file or folder to transfer" : "Vyberte súbor alebo priečinok na prevod",
|
||||
"Transfer" : "Prevod",
|
||||
@@ -250,11 +198,7 @@ OC.L10N.register(
|
||||
"Open in files" : "Otvoriť v súboroch",
|
||||
"File cannot be accessed" : "Súbor nie je možné sprístupniť",
|
||||
"The file could not be found or you do not have permissions to view it. Ask the sender to share it." : "Súbor sa nenašiel alebo nemáte oprávnenie na jeho zobrazenie. Požiadajte odosielateľa, aby ho sprístupnil.",
|
||||
"No search results for “{query}”" : "Žiadne výsledky vyhľadávania pre „{query}“",
|
||||
"Search for files" : "Vyhľadať súbory",
|
||||
"Allow to restrict filenames to ensure files can be synced with all clients. By default all filenames valid on POSIX (e.g. Linux or macOS) are allowed." : "Povoliť obmedzenie názvov súborov, aby sa zabezpečila synchronizácia súborov so všetkými klientmi. V predvolenom nastavení sú povolené všetky názvy súborov platné pre POSIX (napr. Linux alebo macOS).",
|
||||
"After enabling the Windows compatible filenames, existing files cannot be modified anymore but can be renamed to valid new names by their owner." : "Po povolení kompatibilných názvov súborov pre Windows už nie je možné upravovať existujúce súbory, ale ich vlastník ich môže premenovať na platné nové názvy.",
|
||||
"Failed to toggle Windows filename support" : "Nepodarilo sa prepnúť podporu názvov súborov systému Windows",
|
||||
"Files compatibility" : "Kompatibilita súborov",
|
||||
"Enforce Windows compatibility" : "Vynútiť kompatibilitu s Windows",
|
||||
"This will block filenames not valid on Windows systems, like using reserved names or special characters. But this will not enforce compatibility of case sensitivity." : "Tým sa zablokujú názvy súborov, ktoré nie sú platné v systémoch Windows, ako napríklad používanie vyhradených názvov alebo špeciálnych znakov. To však nevynúti kompatibilitu rozlišovania malých a veľkých písmen.",
|
||||
@@ -264,16 +208,10 @@ OC.L10N.register(
|
||||
"Create a new file with the selected template" : "Vytvoriť nový súbor pomocou vybranej šablóny",
|
||||
"Creating file" : "Vytvára sa súbor",
|
||||
"Save as {displayName}" : "Uložiť ako {displayName}",
|
||||
"Save as …" : "Uložiť ako ...",
|
||||
"Converting files …" : "Konverzia súborov ...",
|
||||
"Failed to convert files: {message}" : "Nepodarilo sa skonvertovať súbory: {message}",
|
||||
"All files failed to be converted" : "Nepodarilo sa skonvertovať žiadne súbory",
|
||||
"One file could not be converted: {message}" : "Jeden súbor sa nepodarilo skonvertovať: {message}",
|
||||
"_%n file could not be converted_::_%n files could not be converted_" : ["%n súbor sa nepodarilo skonvertovať","%n súbory sa nepodarilo skonvertovať","%n súborov sa nepodarilo skonvertovať","%n súborov sa nepodarilo skonvertovať"],
|
||||
"_%n file converted_::_%n files converted_" : ["%n prevedený súbor","%n prevedených súborov","%n prevedených súborov","%n prevedené súbory"],
|
||||
"Files converted" : "Súbory boli konvertované",
|
||||
"Failed to convert files" : "Konverzia súborov zlyhala",
|
||||
"Converting file …" : "Konverzia súborov ...",
|
||||
"File successfully converted" : "Súbor bol úspešne skonvertovaný",
|
||||
"Failed to convert file: {message}" : "Nepodarilo sa skonvertovať súbor: {message}",
|
||||
"Failed to convert file" : "Konverzia súboru zlyhala",
|
||||
@@ -293,11 +231,6 @@ OC.L10N.register(
|
||||
"Confirm deletion" : "Potvrdiť vymazanie",
|
||||
"Cancel" : "Zrušiť",
|
||||
"Download" : "Stiahnuť",
|
||||
"The requested file is not available." : "Požadovaný súbor nie je k dispozícii.",
|
||||
"The requested files are not available." : "Požadované súbory nie sú k dispozícii.",
|
||||
"Add or remove favorite" : "Pridať alebo odstrániť obľúbené",
|
||||
"Moving \"{source}\" to \"{destination}\" …" : "Presúvanie \"{source}\" do \"{destination}\" …",
|
||||
"Copying \"{source}\" to \"{destination}\" …" : "Kopírovanie \"{source}\" do \"{destination}\" …",
|
||||
"Destination is not a folder" : "Cieľ nie je priečinok",
|
||||
"This file/folder is already in that directory" : "Tento súbor/priečinok sa už v danom adresári nachádza",
|
||||
"You cannot move a file/folder onto itself or into a subfolder of itself" : "Nemôžete presunúť súbor/priečinok do seba alebo do jeho podpriečinka.",
|
||||
@@ -344,7 +277,6 @@ OC.L10N.register(
|
||||
"Templates" : "Šablóny",
|
||||
"New template folder" : "Nový adresár šablóny",
|
||||
"In folder" : "V adresári",
|
||||
"Search in all files" : "Hľadať vo všetkých súboroch",
|
||||
"Search in folder: {folder}" : "Hľadať v adresári: {folder}",
|
||||
"One of the dropped files could not be processed" : "Jeden z vložených súborov nemohol byť spracovaný.",
|
||||
"Your browser does not support the Filesystem API. Directories will not be uploaded" : "Váš prehliadač nepodporuje Filesystem API. Adresáre nebudú nahrané",
|
||||
@@ -363,12 +295,10 @@ OC.L10N.register(
|
||||
"The name \"{newName}\" is already used in the folder \"{dir}\". Please choose a different name." : "Názov \"{newName}\" sa už používa v priečinku \"{dir}\". Vyberte prosím iný názov.",
|
||||
"Could not rename \"{oldName}\"" : "Nebolo možné premenovať \"{oldName}\"",
|
||||
"This operation is forbidden" : "Táto operácia je zakázaná",
|
||||
"This folder is unavailable, please try again later or contact the administration" : "Táto zložka nie je k dispozícii, skúste to prosím neskôr alebo kontaktujte správu.",
|
||||
"Storage is temporarily not available" : "Úložisko je dočasne nedostupné",
|
||||
"Unexpected error: {error}" : "Neočakávaná chyba: {error}",
|
||||
"_%n file_::_%n files_" : ["%n súbor","%n súbory","%n súborov","%n súborov"],
|
||||
"_%n folder_::_%n folders_" : ["%n priečinok","%n priečinky","%n priečinkov","%n priečinkov"],
|
||||
"_%n hidden_::_%n hidden_" : ["%n skrytý","%n skrytých","%n skrytých","%n sktyré"],
|
||||
"Filename must not be empty." : "Názov súboru nesmie byť prázdny.",
|
||||
"\"{char}\" is not allowed inside a filename." : "Znak \"{char}\" nie je povolený v názve súboru.",
|
||||
"\"{segment}\" is a reserved name and not allowed for filenames." : "\"{segment}\" je rezervované slovo a nie je možné ho použiť v názvoch súborov.",
|
||||
@@ -378,7 +308,6 @@ OC.L10N.register(
|
||||
"No favorites yet" : "Zatiaľ žiadne obľúbené",
|
||||
"Files and folders you mark as favorite will show up here" : "Súbory a priečinky označené ako obľúbené budú zobrazené tu",
|
||||
"List of your files and folders." : "Zoznam vašich súborov a priečinkov.",
|
||||
"Folder tree" : "Strom priečinkov",
|
||||
"List of your files and folders that are not shared." : "Zoznam vašich súborov a priečinkov, ktoré nie sú zdieľané.",
|
||||
"No personal files found" : "Žiadne osobné súbory nenájdené",
|
||||
"Files that are not shared will show up here." : "Súbory, ktoré nie sú zdieľané, sa tu nezobrazia.",
|
||||
@@ -387,16 +316,11 @@ OC.L10N.register(
|
||||
"No recently modified files" : "Žiadne nedávno upravené súbory",
|
||||
"Files and folders you recently modified will show up here." : "Súbory a priečinky, ktoré ste nedávno upravili sa zobrazia tu",
|
||||
"Search" : "Hľadať",
|
||||
"Search results within your files." : "Výsledky vyhľadávania vo vašich súboroch.",
|
||||
"No entries found in this folder" : "V tomto priečinku nebolo nič nájdené",
|
||||
"Select all" : "Vybrať všetko",
|
||||
"Upload too large" : "Nahrávanie je príliš veľké",
|
||||
"The files you are trying to upload exceed the maximum size for file uploads on this server." : "Súbory, ktoré sa snažíte nahrať, presahujú maximálnu veľkosť pre nahratie súborov na tento server.",
|
||||
"%1$s (renamed)" : "%1$s (premenovaný)",
|
||||
"renamed file" : "premenovaný súbor",
|
||||
"Upload (max. %s)" : "Nahrať (max. %s)",
|
||||
"After enabling the windows compatible filenames, existing files cannot be modified anymore but can be renamed to valid new names by their owner." : "Po povolení názvov súborov kompatibilných s Windows už nie je možné upravovať existujúce súbory, ale ich vlastník ich môže premenovať na platné nové názvy.",
|
||||
"It is also possible to migrate files automatically after enabling this setting, please refer to the documentation about the occ command." : "Je tiež možné automaticky migrovať súbory po povolení tohto nastavenia, prosím, prečítajte si dokumentáciu o príkaze occ.",
|
||||
"\"{displayName}\" failed on some elements" : "\"{displayName}\" zlyhalo na niektorých prvkoch.",
|
||||
"\"{displayName}\" batch action executed successfully" : "Hromadná operácia \"{displayName}\" bola úspešne vykonaná",
|
||||
"\"{displayName}\" action failed" : "\"{displayName}\" akcia zlýhala",
|
||||
@@ -444,9 +368,6 @@ OC.L10N.register(
|
||||
"Photos and images" : "Fotky a obrázky",
|
||||
"New folder creation cancelled" : "Vytvorenie adresára bolo zrušené",
|
||||
"This directory is unavailable, please check the logs or contact the administrator" : "Priečinok je nedostupný, skontrolujte prosím logy, alebo kontaktujte správcu",
|
||||
"All folders" : "Všetky adresáre",
|
||||
"Search everywhere …" : "Hľadať všade ...",
|
||||
"Search here …" : "Hľadať tu …",
|
||||
"Preparing …" : "Pripravuje sa …"
|
||||
"All folders" : "Všetky adresáre"
|
||||
},
|
||||
"nplurals=4; plural=(n % 1 == 0 && n == 1 ? 0 : n % 1 == 0 && n >= 2 && n <= 4 ? 1 : n % 1 != 0 ? 2: 3);");
|
||||
|
||||
+1
-80
@@ -49,10 +49,6 @@
|
||||
"You do not have permission to create a file at the specified location" : "Nemáte oprávnenie vytvoriť súbor v zadanom umiestnení",
|
||||
"The file could not be converted." : "Súbor nemohol byť skonvertovaný.",
|
||||
"Could not get relative path to converted file" : "Nepodarilo sa získať relatícnu cestu ku skonvertovanému súboru",
|
||||
"Limit must be a positive integer." : "Limit musí byť kladné celé číslo.",
|
||||
"The replacement character may only be a single character." : "Zástupný znak môže byť iba jeden znak.",
|
||||
"Filename sanitization already started." : "Sanitizácia názvu súboru bola už zahájená.",
|
||||
"No filename sanitization in progress." : "Žiadna sanitácia názvu súboru neprebieha.",
|
||||
"Favorite files" : "Obľúbené súbory",
|
||||
"No favorites" : "Žiadne obľúbené",
|
||||
"More favorites" : "Viac obľúbených",
|
||||
@@ -93,11 +89,6 @@
|
||||
"Renamed \"{oldName}\" to \"{newName}\"" : "Premenované z \"{oldName}\" na \"{newName}\"",
|
||||
"Rename file" : "Premenovať súbor",
|
||||
"Folder" : "Priečinok",
|
||||
"Unknown file type" : "Neznámy typ súboru",
|
||||
"{ext} image" : "{ext} obrázok",
|
||||
"{ext} video" : "{ext} video",
|
||||
"{ext} audio" : "{ext} zvuk",
|
||||
"{ext} text" : "{ext} text",
|
||||
"Pending" : "Čaká",
|
||||
"Unknown date" : "Neznámy dátum",
|
||||
"Clear filter" : "Vyčistiť filter",
|
||||
@@ -108,14 +99,10 @@
|
||||
"Remove filter" : "Odstrániť filter",
|
||||
"Appearance" : "Vzhľad",
|
||||
"Show hidden files" : "Zobraziť skryté súbory",
|
||||
"Show file type column" : "Zobraziť stĺpec typu súboru",
|
||||
"Show file extensions" : "Zobraziť prípony súborov",
|
||||
"Crop image previews" : "Orezať náhľady obrázkov",
|
||||
"General" : "Všeobecné",
|
||||
"Sort favorites first" : "Zoradiť od najobľúbenejších",
|
||||
"Sort folders before files" : "Zoradiť adresáre pred súbormi",
|
||||
"Enable folder tree view" : "Povoliť zobrazenie stromu priečinkov",
|
||||
"Default view" : "Predvolené zobrazenie",
|
||||
"All files" : "Všetky súbory",
|
||||
"Personal files" : "Osobné súbory",
|
||||
"Additional settings" : "Ďalšie nastavenia",
|
||||
@@ -124,33 +111,16 @@
|
||||
"Selection" : "Výber",
|
||||
"Select all files" : "Vybrať všetky súbory",
|
||||
"Deselect all" : "Odznačiť všetko",
|
||||
"Select or deselect" : "Vybrať alebo zrušiť výber",
|
||||
"Select a range" : "Vybrať rozsah",
|
||||
"Navigation" : "Navigácia",
|
||||
"Go to parent folder" : "Prejsť do nadriadeného priečinka",
|
||||
"Go to file above" : "Prejsť na súbor vyššie",
|
||||
"Go to file below" : "Prejsť na súbor nižšie",
|
||||
"Go left in grid" : "Prejsť doľava v mriežke",
|
||||
"Go right in grid" : "Prejsť doprava v mriežke",
|
||||
"View" : "Zobraziť",
|
||||
"Toggle grid view" : "Prepnúť zobrazenie mriežky",
|
||||
"Open file sidebar" : "Otvoriť bočný panel súboru",
|
||||
"Show those shortcuts" : "Zobraziť klávesové skratky",
|
||||
"Warnings" : "Upozornenia",
|
||||
"Warn before changing a file extension" : "Upozorniť pred zmenou prípony súboru",
|
||||
"Warn before deleting a file" : "Upozorniť pred vymazaním súboru",
|
||||
"WebDAV URL" : "WebDAV URL",
|
||||
"Create an app password" : "Vytvoriť heslo pre aplikáciu",
|
||||
"Required for WebDAV authentication because Two-Factor Authentication is enabled for this account." : "Vyžaduje sa pre autentifikáciu WebDAV, pretože pre tento účet je povolená dvojfaktorová autentifikácia.",
|
||||
"How to access files using WebDAV" : "Ako získať prístup k súborom pomocou WebDAV",
|
||||
"Total rows summary" : "Súčet všetkých riadkov",
|
||||
"Toggle selection for all files and folders" : "Prepnúť výber pre všetky súbory a adresáre",
|
||||
"Name" : "Názov",
|
||||
"File type" : "Typ súboru",
|
||||
"Size" : "Veľkosť",
|
||||
"{displayName}: failed on some elements" : "{displayName}: zlyhalo na niektorých prvkoch",
|
||||
"{displayName}: done" : "{displayName}: hotovo",
|
||||
"{displayName}: failed" : "{displayName}: zlyhalo",
|
||||
"(selected)" : "(vybrané)",
|
||||
"List of files and folders." : "Zoznam súborov a priečinkov.",
|
||||
"You have used your space quota and cannot upload files anymore." : "Už ste využili kapacitu úložného priestoru a nie je možné nahrávať ďalšie súbory.",
|
||||
@@ -158,10 +128,6 @@
|
||||
"This list is not fully rendered for performance reasons. The files will be rendered as you navigate through the list." : "Tento zoznam nie je úplne vykreslený z dôvodov výkonu. Súbory budú vykreslené, keď budete prechádzať zoznamom.",
|
||||
"File not found" : "Súbor nenájdený",
|
||||
"_{count} selected_::_{count} selected_" : ["{count} vybraný","{count} vybrané","{count} vybraných","{count} vybraných"],
|
||||
"Search everywhere …" : "Hľadať všade ...",
|
||||
"Search here …" : "Hľadať tu …",
|
||||
"Search scope options" : "Možnosti rozsahu vyhľadávania",
|
||||
"Search here" : "Hľadať tu",
|
||||
"{usedQuotaByte} used" : "{usedQuotaByte} použitých",
|
||||
"{used} of {quota} used" : "použitých {used} z {quota}",
|
||||
"{relative}% used" : "{relative}% použitých",
|
||||
@@ -173,27 +139,9 @@
|
||||
"Create new folder" : "Vytvoriť nový priečinok",
|
||||
"This name is already in use." : "Toto meno je už používané.",
|
||||
"Create" : "Vytvoriť",
|
||||
"Files starting with a dot are hidden by default" : "Súbory, ktoré začínajú bodkou, sú predvolene skryté.",
|
||||
"Failed to start filename sanitization." : "Nezískalo sa spustenie sanitizácie názvu súboru.",
|
||||
"Failed to refresh filename sanitization status." : "Nepodarilo sa obnoviť stav sanitácie názvu súboru.",
|
||||
"Filename sanitization in progress." : "Prebieha sanitácia názvu súboru.",
|
||||
"Currently {processedUsers} of {totalUsers} accounts are already processed." : "Aktuálne je spracovaných {processedUsers} z {totalUsers} účtov.",
|
||||
"Preparing …" : "Pripravuje sa …",
|
||||
"Refresh" : "Obnoviť",
|
||||
"All files have been santized for Windows filename support." : "Všetky súbory boli upravené na podporu názvov súborov vo Windows.",
|
||||
"Some files could not be sanitized, please check your logs." : "Niektoré súbory nebolo možné upravené, prosím, skontrolujte svoje protokoly.",
|
||||
"Sanitization errors" : "Chyby v sanitizácii",
|
||||
"Not sanitized filenames" : "Nezabezpečené názvy súborov",
|
||||
"Windows filename support has been enabled." : "Podpora názvov súborov vo Windows bola povolená.",
|
||||
"While this blocks users from creating new files with unsupported filenames, existing files are not yet renamed and thus still may break sync on Windows." : "Kým toto blokuje používateľov v tvorbe nových súborov s nepodporovanými názvami, existujúce súbory ešte neboli premenované a teda môžu stále spôsobiť problémy so synchronizáciou na Windows.",
|
||||
"You can trigger a rename of files with invalid filenames, this will be done in the background and may take some time." : "Môžete spustiť premenovanie súborov s neplatnými názvami, tento proces sa vykoná na pozadí a môže trvať nejaký čas.",
|
||||
"Please note that this may cause high workload on the sync clients." : "Upozorňujeme, že to môže spôsobiť vysokú záťaž pre klientov synchronizácie.",
|
||||
"Limit" : "Limit",
|
||||
"This allows to configure how many users should be processed in one background job run." : "Toto umožňuje nastaviť, koľko používateľov by malo byť spracovaných v jednom spustení úlohy na pozadí.",
|
||||
"Sanitize filenames" : "Vyčistiť názvy súborov",
|
||||
"(starting)" : "(začína sa)",
|
||||
"Fill template fields" : "Vyplňte položky šablóny",
|
||||
"Submitting fields …" : "Odosielanie polí …",
|
||||
"Submit" : "Odoslať",
|
||||
"Choose a file or folder to transfer" : "Vyberte súbor alebo priečinok na prevod",
|
||||
"Transfer" : "Prevod",
|
||||
@@ -248,11 +196,7 @@
|
||||
"Open in files" : "Otvoriť v súboroch",
|
||||
"File cannot be accessed" : "Súbor nie je možné sprístupniť",
|
||||
"The file could not be found or you do not have permissions to view it. Ask the sender to share it." : "Súbor sa nenašiel alebo nemáte oprávnenie na jeho zobrazenie. Požiadajte odosielateľa, aby ho sprístupnil.",
|
||||
"No search results for “{query}”" : "Žiadne výsledky vyhľadávania pre „{query}“",
|
||||
"Search for files" : "Vyhľadať súbory",
|
||||
"Allow to restrict filenames to ensure files can be synced with all clients. By default all filenames valid on POSIX (e.g. Linux or macOS) are allowed." : "Povoliť obmedzenie názvov súborov, aby sa zabezpečila synchronizácia súborov so všetkými klientmi. V predvolenom nastavení sú povolené všetky názvy súborov platné pre POSIX (napr. Linux alebo macOS).",
|
||||
"After enabling the Windows compatible filenames, existing files cannot be modified anymore but can be renamed to valid new names by their owner." : "Po povolení kompatibilných názvov súborov pre Windows už nie je možné upravovať existujúce súbory, ale ich vlastník ich môže premenovať na platné nové názvy.",
|
||||
"Failed to toggle Windows filename support" : "Nepodarilo sa prepnúť podporu názvov súborov systému Windows",
|
||||
"Files compatibility" : "Kompatibilita súborov",
|
||||
"Enforce Windows compatibility" : "Vynútiť kompatibilitu s Windows",
|
||||
"This will block filenames not valid on Windows systems, like using reserved names or special characters. But this will not enforce compatibility of case sensitivity." : "Tým sa zablokujú názvy súborov, ktoré nie sú platné v systémoch Windows, ako napríklad používanie vyhradených názvov alebo špeciálnych znakov. To však nevynúti kompatibilitu rozlišovania malých a veľkých písmen.",
|
||||
@@ -262,16 +206,10 @@
|
||||
"Create a new file with the selected template" : "Vytvoriť nový súbor pomocou vybranej šablóny",
|
||||
"Creating file" : "Vytvára sa súbor",
|
||||
"Save as {displayName}" : "Uložiť ako {displayName}",
|
||||
"Save as …" : "Uložiť ako ...",
|
||||
"Converting files …" : "Konverzia súborov ...",
|
||||
"Failed to convert files: {message}" : "Nepodarilo sa skonvertovať súbory: {message}",
|
||||
"All files failed to be converted" : "Nepodarilo sa skonvertovať žiadne súbory",
|
||||
"One file could not be converted: {message}" : "Jeden súbor sa nepodarilo skonvertovať: {message}",
|
||||
"_%n file could not be converted_::_%n files could not be converted_" : ["%n súbor sa nepodarilo skonvertovať","%n súbory sa nepodarilo skonvertovať","%n súborov sa nepodarilo skonvertovať","%n súborov sa nepodarilo skonvertovať"],
|
||||
"_%n file converted_::_%n files converted_" : ["%n prevedený súbor","%n prevedených súborov","%n prevedených súborov","%n prevedené súbory"],
|
||||
"Files converted" : "Súbory boli konvertované",
|
||||
"Failed to convert files" : "Konverzia súborov zlyhala",
|
||||
"Converting file …" : "Konverzia súborov ...",
|
||||
"File successfully converted" : "Súbor bol úspešne skonvertovaný",
|
||||
"Failed to convert file: {message}" : "Nepodarilo sa skonvertovať súbor: {message}",
|
||||
"Failed to convert file" : "Konverzia súboru zlyhala",
|
||||
@@ -291,11 +229,6 @@
|
||||
"Confirm deletion" : "Potvrdiť vymazanie",
|
||||
"Cancel" : "Zrušiť",
|
||||
"Download" : "Stiahnuť",
|
||||
"The requested file is not available." : "Požadovaný súbor nie je k dispozícii.",
|
||||
"The requested files are not available." : "Požadované súbory nie sú k dispozícii.",
|
||||
"Add or remove favorite" : "Pridať alebo odstrániť obľúbené",
|
||||
"Moving \"{source}\" to \"{destination}\" …" : "Presúvanie \"{source}\" do \"{destination}\" …",
|
||||
"Copying \"{source}\" to \"{destination}\" …" : "Kopírovanie \"{source}\" do \"{destination}\" …",
|
||||
"Destination is not a folder" : "Cieľ nie je priečinok",
|
||||
"This file/folder is already in that directory" : "Tento súbor/priečinok sa už v danom adresári nachádza",
|
||||
"You cannot move a file/folder onto itself or into a subfolder of itself" : "Nemôžete presunúť súbor/priečinok do seba alebo do jeho podpriečinka.",
|
||||
@@ -342,7 +275,6 @@
|
||||
"Templates" : "Šablóny",
|
||||
"New template folder" : "Nový adresár šablóny",
|
||||
"In folder" : "V adresári",
|
||||
"Search in all files" : "Hľadať vo všetkých súboroch",
|
||||
"Search in folder: {folder}" : "Hľadať v adresári: {folder}",
|
||||
"One of the dropped files could not be processed" : "Jeden z vložených súborov nemohol byť spracovaný.",
|
||||
"Your browser does not support the Filesystem API. Directories will not be uploaded" : "Váš prehliadač nepodporuje Filesystem API. Adresáre nebudú nahrané",
|
||||
@@ -361,12 +293,10 @@
|
||||
"The name \"{newName}\" is already used in the folder \"{dir}\". Please choose a different name." : "Názov \"{newName}\" sa už používa v priečinku \"{dir}\". Vyberte prosím iný názov.",
|
||||
"Could not rename \"{oldName}\"" : "Nebolo možné premenovať \"{oldName}\"",
|
||||
"This operation is forbidden" : "Táto operácia je zakázaná",
|
||||
"This folder is unavailable, please try again later or contact the administration" : "Táto zložka nie je k dispozícii, skúste to prosím neskôr alebo kontaktujte správu.",
|
||||
"Storage is temporarily not available" : "Úložisko je dočasne nedostupné",
|
||||
"Unexpected error: {error}" : "Neočakávaná chyba: {error}",
|
||||
"_%n file_::_%n files_" : ["%n súbor","%n súbory","%n súborov","%n súborov"],
|
||||
"_%n folder_::_%n folders_" : ["%n priečinok","%n priečinky","%n priečinkov","%n priečinkov"],
|
||||
"_%n hidden_::_%n hidden_" : ["%n skrytý","%n skrytých","%n skrytých","%n sktyré"],
|
||||
"Filename must not be empty." : "Názov súboru nesmie byť prázdny.",
|
||||
"\"{char}\" is not allowed inside a filename." : "Znak \"{char}\" nie je povolený v názve súboru.",
|
||||
"\"{segment}\" is a reserved name and not allowed for filenames." : "\"{segment}\" je rezervované slovo a nie je možné ho použiť v názvoch súborov.",
|
||||
@@ -376,7 +306,6 @@
|
||||
"No favorites yet" : "Zatiaľ žiadne obľúbené",
|
||||
"Files and folders you mark as favorite will show up here" : "Súbory a priečinky označené ako obľúbené budú zobrazené tu",
|
||||
"List of your files and folders." : "Zoznam vašich súborov a priečinkov.",
|
||||
"Folder tree" : "Strom priečinkov",
|
||||
"List of your files and folders that are not shared." : "Zoznam vašich súborov a priečinkov, ktoré nie sú zdieľané.",
|
||||
"No personal files found" : "Žiadne osobné súbory nenájdené",
|
||||
"Files that are not shared will show up here." : "Súbory, ktoré nie sú zdieľané, sa tu nezobrazia.",
|
||||
@@ -385,16 +314,11 @@
|
||||
"No recently modified files" : "Žiadne nedávno upravené súbory",
|
||||
"Files and folders you recently modified will show up here." : "Súbory a priečinky, ktoré ste nedávno upravili sa zobrazia tu",
|
||||
"Search" : "Hľadať",
|
||||
"Search results within your files." : "Výsledky vyhľadávania vo vašich súboroch.",
|
||||
"No entries found in this folder" : "V tomto priečinku nebolo nič nájdené",
|
||||
"Select all" : "Vybrať všetko",
|
||||
"Upload too large" : "Nahrávanie je príliš veľké",
|
||||
"The files you are trying to upload exceed the maximum size for file uploads on this server." : "Súbory, ktoré sa snažíte nahrať, presahujú maximálnu veľkosť pre nahratie súborov na tento server.",
|
||||
"%1$s (renamed)" : "%1$s (premenovaný)",
|
||||
"renamed file" : "premenovaný súbor",
|
||||
"Upload (max. %s)" : "Nahrať (max. %s)",
|
||||
"After enabling the windows compatible filenames, existing files cannot be modified anymore but can be renamed to valid new names by their owner." : "Po povolení názvov súborov kompatibilných s Windows už nie je možné upravovať existujúce súbory, ale ich vlastník ich môže premenovať na platné nové názvy.",
|
||||
"It is also possible to migrate files automatically after enabling this setting, please refer to the documentation about the occ command." : "Je tiež možné automaticky migrovať súbory po povolení tohto nastavenia, prosím, prečítajte si dokumentáciu o príkaze occ.",
|
||||
"\"{displayName}\" failed on some elements" : "\"{displayName}\" zlyhalo na niektorých prvkoch.",
|
||||
"\"{displayName}\" batch action executed successfully" : "Hromadná operácia \"{displayName}\" bola úspešne vykonaná",
|
||||
"\"{displayName}\" action failed" : "\"{displayName}\" akcia zlýhala",
|
||||
@@ -442,9 +366,6 @@
|
||||
"Photos and images" : "Fotky a obrázky",
|
||||
"New folder creation cancelled" : "Vytvorenie adresára bolo zrušené",
|
||||
"This directory is unavailable, please check the logs or contact the administrator" : "Priečinok je nedostupný, skontrolujte prosím logy, alebo kontaktujte správcu",
|
||||
"All folders" : "Všetky adresáre",
|
||||
"Search everywhere …" : "Hľadať všade ...",
|
||||
"Search here …" : "Hľadať tu …",
|
||||
"Preparing …" : "Pripravuje sa …"
|
||||
"All folders" : "Všetky adresáre"
|
||||
},"pluralForm" :"nplurals=4; plural=(n % 1 == 0 && n == 1 ? 0 : n % 1 == 0 && n >= 2 && n <= 4 ? 1 : n % 1 != 0 ? 2: 3);"
|
||||
}
|
||||
@@ -375,7 +375,7 @@ OC.L10N.register(
|
||||
"\"{extension}\" is not an allowed filetype." : "\"{extension}\" не є дозволеним типом файлів.",
|
||||
"Filenames must not end with \"{extension}\"." : "Імена файлів не мають закінчуватися на \"{extension}\".",
|
||||
"List of favorite files and folders." : "Список файлів та каталогів із зірочкою.",
|
||||
"No favorites yet" : "Відсутні файли чи каталоги, позначені зірочкою",
|
||||
"No favorites yet" : "Поки немає нічого, позначеного зірочкою",
|
||||
"Files and folders you mark as favorite will show up here" : "Файли та каталоги із зірочкою з’являться тут",
|
||||
"List of your files and folders." : "Список ваших файлів та каталогів.",
|
||||
"Folder tree" : "Дерево каталогів",
|
||||
|
||||
@@ -373,7 +373,7 @@
|
||||
"\"{extension}\" is not an allowed filetype." : "\"{extension}\" не є дозволеним типом файлів.",
|
||||
"Filenames must not end with \"{extension}\"." : "Імена файлів не мають закінчуватися на \"{extension}\".",
|
||||
"List of favorite files and folders." : "Список файлів та каталогів із зірочкою.",
|
||||
"No favorites yet" : "Відсутні файли чи каталоги, позначені зірочкою",
|
||||
"No favorites yet" : "Поки немає нічого, позначеного зірочкою",
|
||||
"Files and folders you mark as favorite will show up here" : "Файли та каталоги із зірочкою з’являться тут",
|
||||
"List of your files and folders." : "Список ваших файлів та каталогів.",
|
||||
"Folder tree" : "Дерево каталогів",
|
||||
|
||||
@@ -198,6 +198,7 @@ class ApiController extends Controller {
|
||||
IShare::TYPE_EMAIL,
|
||||
IShare::TYPE_ROOM,
|
||||
IShare::TYPE_DECK,
|
||||
IShare::TYPE_SCIENCEMESH,
|
||||
];
|
||||
$shareTypes = [];
|
||||
|
||||
|
||||
@@ -328,6 +328,7 @@ class OwnershipTransferService {
|
||||
IShare::TYPE_EMAIL,
|
||||
IShare::TYPE_CIRCLE,
|
||||
IShare::TYPE_DECK,
|
||||
IShare::TYPE_SCIENCEMESH,
|
||||
];
|
||||
|
||||
foreach ($supportedShareTypes as $shareType) {
|
||||
|
||||
@@ -367,7 +367,7 @@ export default defineComponent({
|
||||
const metaKeyPressed = event.ctrlKey || event.metaKey || event.button === 1
|
||||
if (metaKeyPressed || !this.defaultFileAction) {
|
||||
// If no download permission, then we can not allow to download (direct link) the files
|
||||
if (!isDownloadable(this.source)) {
|
||||
if (isPublicShare() && !isDownloadable(this.source)) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ describe('Composables: useNavigation', () => {
|
||||
it('should return already active navigation', async () => {
|
||||
const view = new nextcloudFiles.View({ getContents: () => Promise.reject(new Error()), icon: '<svg></svg>', id: 'view-1', name: 'My View 1', order: 0 })
|
||||
navigation.register(view)
|
||||
navigation.setActive(view.id)
|
||||
navigation.setActive(view)
|
||||
// Now the navigation is already set it should take the active navigation
|
||||
const wrapper = mount(TestComponent)
|
||||
expect((wrapper.vm as unknown as { currentView: View | null }).currentView).toBe(view)
|
||||
@@ -55,7 +55,7 @@ describe('Composables: useNavigation', () => {
|
||||
// no active navigation
|
||||
expect((wrapper.vm as unknown as { currentView: View | null }).currentView).toBe(null)
|
||||
|
||||
navigation.setActive(view.id)
|
||||
navigation.setActive(view)
|
||||
// Now the navigation is set it should take the active navigation
|
||||
expect((wrapper.vm as unknown as { currentView: View | null }).currentView).toBe(view)
|
||||
})
|
||||
|
||||
@@ -10,9 +10,10 @@ import type { RootDirectory } from './DropServiceUtils.ts'
|
||||
import { showError, showInfo, showSuccess, showWarning } from '@nextcloud/dialogs'
|
||||
import { NodeStatus } from '@nextcloud/files'
|
||||
import { getRootPath } from '@nextcloud/files/dav'
|
||||
import { t } from '@nextcloud/l10n'
|
||||
import { join } from '@nextcloud/paths'
|
||||
import { translate as t } from '@nextcloud/l10n'
|
||||
import { joinPaths } from '@nextcloud/paths'
|
||||
import { getUploader, hasConflict } from '@nextcloud/upload'
|
||||
import { join } from 'path'
|
||||
import Vue from 'vue'
|
||||
import { handleCopyMoveNodeTo } from '../actions/moveOrCopyAction.ts'
|
||||
import { MoveCopyAction } from '../actions/moveOrCopyActionUtils.ts'
|
||||
@@ -125,7 +126,7 @@ export async function onDropExternalFiles(root: RootDirectory, destination: Fold
|
||||
// If the file is a directory, we need to create it first
|
||||
// then browse its tree and upload its contents.
|
||||
if (file instanceof Directory) {
|
||||
const absolutePath = join(getRootPath(), destination.path, relativePath)
|
||||
const absolutePath = joinPaths(getRootPath(), destination.path, relativePath)
|
||||
try {
|
||||
logger.debug('Processing directory', { relativePath })
|
||||
await createDirectoryIfNotExists(absolutePath)
|
||||
|
||||
@@ -1,49 +1,45 @@
|
||||
/*!
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { ContentsWithRoot } from '@nextcloud/files'
|
||||
|
||||
import { getCurrentUser } from '@nextcloud/auth'
|
||||
import { Folder, Permission } from '@nextcloud/files'
|
||||
import { getFavoriteNodes, getRemoteURL, getRootPath } from '@nextcloud/files/dav'
|
||||
import logger from '../logger.ts'
|
||||
import { CancelablePromise } from 'cancelable-promise'
|
||||
import { getContents as filesContents } from './Files.ts'
|
||||
import { client } from './WebdavClient.ts'
|
||||
|
||||
/**
|
||||
* Get the contents for the favorites view
|
||||
*
|
||||
* @param path - The path to get the contents for
|
||||
* @param options - Additional options
|
||||
* @param options.signal - Optional AbortSignal to cancel the request
|
||||
* @return A promise resolving to the contents with root folder
|
||||
* @param path
|
||||
*/
|
||||
export async function getContents(path = '/', options: { signal: AbortSignal }): Promise<ContentsWithRoot> {
|
||||
export function getContents(path = '/'): CancelablePromise<ContentsWithRoot> {
|
||||
// We only filter root files for favorites, for subfolders we can simply reuse the files contents
|
||||
if (path && path !== '/') {
|
||||
return filesContents(path, options)
|
||||
if (path !== '/') {
|
||||
return filesContents(path)
|
||||
}
|
||||
|
||||
try {
|
||||
const contents = await getFavoriteNodes({ client, signal: options.signal })
|
||||
return {
|
||||
contents,
|
||||
folder: new Folder({
|
||||
id: 0,
|
||||
source: `${getRemoteURL()}${getRootPath()}`,
|
||||
root: getRootPath(),
|
||||
owner: getCurrentUser()?.uid || null,
|
||||
permissions: Permission.READ,
|
||||
}),
|
||||
}
|
||||
} catch (error) {
|
||||
if (options.signal.aborted) {
|
||||
logger.debug('Favorite nodes request was aborted')
|
||||
throw new DOMException('Aborted', 'AbortError')
|
||||
}
|
||||
logger.error('Failed to load favorite nodes via WebDAV', { error })
|
||||
throw error
|
||||
}
|
||||
return new CancelablePromise((resolve, reject, cancel) => {
|
||||
const promise = getFavoriteNodes(client)
|
||||
.catch(reject)
|
||||
.then((contents) => {
|
||||
if (!contents) {
|
||||
reject()
|
||||
return
|
||||
}
|
||||
resolve({
|
||||
contents,
|
||||
folder: new Folder({
|
||||
id: 0,
|
||||
source: `${getRemoteURL()}${getRootPath()}`,
|
||||
root: getRootPath(),
|
||||
owner: getCurrentUser()?.uid || null,
|
||||
permissions: Permission.READ,
|
||||
}),
|
||||
})
|
||||
})
|
||||
cancel(() => promise.cancel())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import type { ContentsWithRoot, File, Folder } from '@nextcloud/files'
|
||||
import type { FileStat, ResponseDataDetailed } from 'webdav'
|
||||
|
||||
import { getDefaultPropfind, getRootPath, resultToNode } from '@nextcloud/files/dav'
|
||||
import { CancelablePromise } from 'cancelable-promise'
|
||||
import { join } from 'path'
|
||||
import logger from '../logger.ts'
|
||||
import { useFilesStore } from '../store/files.ts'
|
||||
@@ -19,55 +20,66 @@ import { searchNodes } from './WebDavSearch.ts'
|
||||
* This also allows to fetch local search results when the user is currently filtering.
|
||||
*
|
||||
* @param path - The path to query
|
||||
* @param options - Options
|
||||
* @param options.signal - Abort signal to cancel the request
|
||||
*/
|
||||
export async function getContents(path = '/', options?: { signal: AbortSignal }): Promise<ContentsWithRoot> {
|
||||
export function getContents(path = '/'): CancelablePromise<ContentsWithRoot> {
|
||||
const controller = new AbortController()
|
||||
const searchStore = useSearchStore(getPinia())
|
||||
|
||||
if (searchStore.query.length < 3) {
|
||||
return await defaultGetContents(path, options)
|
||||
if (searchStore.query.length >= 3) {
|
||||
return new CancelablePromise((resolve, reject, cancel) => {
|
||||
cancel(() => controller.abort())
|
||||
getLocalSearch(path, searchStore.query, controller.signal)
|
||||
.then(resolve)
|
||||
.catch(reject)
|
||||
})
|
||||
} else {
|
||||
return defaultGetContents(path)
|
||||
}
|
||||
|
||||
return await getLocalSearch(path, searchStore.query, options?.signal)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic `getContents` implementation for the users files.
|
||||
*
|
||||
* @param path - The path to get the contents
|
||||
* @param options - Options
|
||||
* @param options.signal - Abort signal to cancel the request
|
||||
*/
|
||||
export async function defaultGetContents(path: string, options?: { signal: AbortSignal }): Promise<ContentsWithRoot> {
|
||||
export function defaultGetContents(path: string): CancelablePromise<ContentsWithRoot> {
|
||||
path = join(getRootPath(), path)
|
||||
const controller = new AbortController()
|
||||
const propfindPayload = getDefaultPropfind()
|
||||
|
||||
const contentsResponse = await client.getDirectoryContents(path, {
|
||||
details: true,
|
||||
data: propfindPayload,
|
||||
includeSelf: true,
|
||||
signal: options?.signal,
|
||||
}) as ResponseDataDetailed<FileStat[]>
|
||||
return new CancelablePromise(async (resolve, reject, onCancel) => {
|
||||
onCancel(() => controller.abort())
|
||||
|
||||
const root = contentsResponse.data[0]!
|
||||
const contents = contentsResponse.data.slice(1)
|
||||
if (root?.filename !== path && `${root?.filename}/` !== path) {
|
||||
logger.debug(`Exepected "${path}" but got filename "${root.filename}" instead.`)
|
||||
throw new Error('Root node does not match requested path')
|
||||
}
|
||||
try {
|
||||
const contentsResponse = await client.getDirectoryContents(path, {
|
||||
details: true,
|
||||
data: propfindPayload,
|
||||
includeSelf: true,
|
||||
signal: controller.signal,
|
||||
}) as ResponseDataDetailed<FileStat[]>
|
||||
|
||||
return {
|
||||
folder: resultToNode(root) as Folder,
|
||||
contents: contents.map((result) => {
|
||||
try {
|
||||
return resultToNode(result)
|
||||
} catch (error) {
|
||||
logger.error(`Invalid node detected '${result.basename}'`, { error })
|
||||
return null
|
||||
const root = contentsResponse.data[0]
|
||||
const contents = contentsResponse.data.slice(1)
|
||||
if (root?.filename !== path && `${root?.filename}/` !== path) {
|
||||
logger.debug(`Exepected "${path}" but got filename "${root.filename}" instead.`)
|
||||
throw new Error('Root node does not match requested path')
|
||||
}
|
||||
}).filter(Boolean) as File[],
|
||||
}
|
||||
|
||||
resolve({
|
||||
folder: resultToNode(root) as Folder,
|
||||
contents: contents.map((result) => {
|
||||
try {
|
||||
return resultToNode(result)
|
||||
} catch (error) {
|
||||
logger.error(`Invalid node detected '${result.basename}'`, { error })
|
||||
return null
|
||||
}
|
||||
}).filter(Boolean) as File[],
|
||||
})
|
||||
} catch (error) {
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,7 +89,7 @@ export async function defaultGetContents(path: string, options?: { signal: Abort
|
||||
* @param query - The current search query
|
||||
* @param signal - The aboort signal
|
||||
*/
|
||||
async function getLocalSearch(path: string, query: string, signal?: AbortSignal): Promise<ContentsWithRoot> {
|
||||
async function getLocalSearch(path: string, query: string, signal: AbortSignal): Promise<ContentsWithRoot> {
|
||||
const filesStore = useFilesStore(getPinia())
|
||||
let folder = filesStore.getDirectoryByPath('files', path)
|
||||
if (!folder) {
|
||||
|
||||
@@ -4,12 +4,13 @@
|
||||
*/
|
||||
|
||||
import type { ContentsWithRoot } from '@nextcloud/files'
|
||||
import type { CancelablePromise } from 'cancelable-promise'
|
||||
|
||||
import { getCurrentUser } from '@nextcloud/auth'
|
||||
import axios from '@nextcloud/axios'
|
||||
import { getRemoteURL } from '@nextcloud/files/dav'
|
||||
import { getCanonicalLocale, getLanguage } from '@nextcloud/l10n'
|
||||
import { dirname, encodePath, join } from '@nextcloud/paths'
|
||||
import { dirname, encodePath, joinPaths } from '@nextcloud/paths'
|
||||
import { generateOcsUrl } from '@nextcloud/router'
|
||||
import { getContents as getFiles } from './Files.ts'
|
||||
|
||||
@@ -46,16 +47,15 @@ const collator = Intl.Collator(
|
||||
const compareNodes = (a: TreeNodeData, b: TreeNodeData) => collator.compare(a.displayName ?? a.basename, b.displayName ?? b.basename)
|
||||
|
||||
/**
|
||||
* Get all tree nodes recursively
|
||||
*
|
||||
* @param tree - The tree to process
|
||||
* @param currentPath - The current path
|
||||
* @param nodes - The nodes collected so far
|
||||
* @param tree
|
||||
* @param currentPath
|
||||
* @param nodes
|
||||
*/
|
||||
function getTreeNodes(tree: Tree, currentPath: string = '/', nodes: TreeNode[] = []): TreeNode[] {
|
||||
const sortedTree = tree.toSorted(compareNodes)
|
||||
for (const { id, basename, displayName, children } of sortedTree) {
|
||||
const path = join(currentPath, basename)
|
||||
const path = joinPaths(currentPath, basename)
|
||||
const source = `${sourceRoot}${path}`
|
||||
const node: TreeNode = {
|
||||
source,
|
||||
@@ -76,10 +76,9 @@ function getTreeNodes(tree: Tree, currentPath: string = '/', nodes: TreeNode[] =
|
||||
}
|
||||
|
||||
/**
|
||||
* Get folder tree nodes
|
||||
*
|
||||
* @param path - The path to get the tree from
|
||||
* @param depth - The depth to fetch
|
||||
* @param path
|
||||
* @param depth
|
||||
*/
|
||||
export async function getFolderTreeNodes(path: string = '/', depth: number = 1): Promise<TreeNode[]> {
|
||||
const { data: tree } = await axios.get<Tree>(generateOcsUrl('/apps/files/api/v1/folder-tree'), {
|
||||
@@ -89,12 +88,11 @@ export async function getFolderTreeNodes(path: string = '/', depth: number = 1):
|
||||
return nodes
|
||||
}
|
||||
|
||||
export const getContents = (path: string, options: { signal: AbortSignal }): Promise<ContentsWithRoot> => getFiles(path, options)
|
||||
export const getContents = (path: string): CancelablePromise<ContentsWithRoot> => getFiles(path)
|
||||
|
||||
/**
|
||||
* Encode source URL
|
||||
*
|
||||
* @param source - The source URL
|
||||
* @param source
|
||||
*/
|
||||
export function encodeSource(source: string): string {
|
||||
const { origin } = new URL(source)
|
||||
@@ -102,9 +100,8 @@ export function encodeSource(source: string): string {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get parent source URL
|
||||
*
|
||||
* @param source - The source URL
|
||||
* @param source
|
||||
*/
|
||||
export function getSourceParent(source: string): string {
|
||||
const parent = dirname(source)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
|
||||
import type { ContentsWithRoot, Node } from '@nextcloud/files'
|
||||
import type { CancelablePromise } from 'cancelable-promise'
|
||||
|
||||
import { getCurrentUser } from '@nextcloud/auth'
|
||||
import { getContents as getFiles } from './Files.ts'
|
||||
@@ -30,17 +31,13 @@ export function isPersonalFile(node: Node): boolean {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get personal files from a given path
|
||||
*
|
||||
* @param path - The path to get the personal files from
|
||||
* @param options - Options
|
||||
* @param options.signal - Abort signal to cancel the request
|
||||
* @return A promise that resolves to the personal files
|
||||
* @param path
|
||||
*/
|
||||
export function getContents(path: string = '/', options: { signal: AbortSignal }): Promise<ContentsWithRoot> {
|
||||
export function getContents(path: string = '/'): CancelablePromise<ContentsWithRoot> {
|
||||
// get all the files from the current path as a cancellable promise
|
||||
// then filter the files that the user does not own, or has shared / is a group folder
|
||||
return getFiles(path, options)
|
||||
return getFiles(path)
|
||||
.then((content) => {
|
||||
content.contents = content.contents.filter(isPersonalFile)
|
||||
return content
|
||||
|
||||
@@ -8,7 +8,7 @@ import type { ResponseDataDetailed, SearchResult } from 'webdav'
|
||||
import { getCurrentUser } from '@nextcloud/auth'
|
||||
import { Folder, Permission } from '@nextcloud/files'
|
||||
import { getRecentSearch, getRemoteURL, getRootPath, resultToNode } from '@nextcloud/files/dav'
|
||||
import logger from '../logger.ts'
|
||||
import { CancelablePromise } from 'cancelable-promise'
|
||||
import { getPinia } from '../store/index.ts'
|
||||
import { useUserConfigStore } from '../store/userconfig.ts'
|
||||
import { client } from './WebdavClient.ts'
|
||||
@@ -22,10 +22,8 @@ const lastTwoWeeksTimestamp = Math.round((Date.now() / 1000) - (60 * 60 * 24 * 1
|
||||
* If hidden files are not shown, then also recently changed files *in* hidden directories are filtered.
|
||||
*
|
||||
* @param path Path to search for recent changes
|
||||
* @param options Options including abort signal
|
||||
* @param options.signal Abort signal to cancel the request
|
||||
*/
|
||||
export async function getContents(path = '/', options: { signal: AbortSignal }): Promise<ContentsWithRoot> {
|
||||
export function getContents(path = '/'): CancelablePromise<ContentsWithRoot> {
|
||||
const store = useUserConfigStore(getPinia())
|
||||
|
||||
/**
|
||||
@@ -37,9 +35,10 @@ export async function getContents(path = '/', options: { signal: AbortSignal }):
|
||||
|| store.userConfig.show_hidden // If configured to show hidden files we can early return
|
||||
|| !node.dirname.split('/').some((dir) => dir.startsWith('.')) // otherwise only include the file if non of the parent directories is hidden
|
||||
|
||||
try {
|
||||
const controller = new AbortController()
|
||||
const handler = async () => {
|
||||
const contentsResponse = await client.search('/', {
|
||||
signal: options.signal,
|
||||
signal: controller.signal,
|
||||
details: true,
|
||||
data: getRecentSearch(lastTwoWeeksTimestamp),
|
||||
}) as ResponseDataDetailed<SearchResult>
|
||||
@@ -62,12 +61,10 @@ export async function getContents(path = '/', options: { signal: AbortSignal }):
|
||||
}),
|
||||
contents,
|
||||
}
|
||||
} catch (error) {
|
||||
if (options.signal.aborted) {
|
||||
logger.info('Fetching recent files aborted')
|
||||
throw new DOMException('Aborted', 'AbortError')
|
||||
}
|
||||
logger.error('Failed to fetch recent files', { error })
|
||||
throw error
|
||||
}
|
||||
|
||||
return new CancelablePromise(async (resolve, reject, cancel) => {
|
||||
cancel(() => controller.abort())
|
||||
resolve(handler())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -35,12 +35,12 @@ describe('Search service', () => {
|
||||
searchNodes.mockImplementationOnce(() => {
|
||||
throw new Error('expected error')
|
||||
})
|
||||
expect(() => getContents('', { signal: new AbortController().signal })).rejects.toThrow('expected error')
|
||||
expect(getContents).rejects.toThrow('expected error')
|
||||
})
|
||||
|
||||
it('returns the search results and a fake root', async () => {
|
||||
searchNodes.mockImplementationOnce(() => [fakeFolder])
|
||||
const { contents, folder } = await getContents('', { signal: new AbortController().signal })
|
||||
const { contents, folder } = await getContents()
|
||||
|
||||
expect(searchNodes).toHaveBeenCalledOnce()
|
||||
expect(contents).toHaveLength(1)
|
||||
@@ -57,9 +57,8 @@ describe('Search service', () => {
|
||||
return []
|
||||
})
|
||||
|
||||
const controller = new AbortController()
|
||||
getContents('', { signal: controller.signal })
|
||||
controller.abort()
|
||||
const content = getContents()
|
||||
content.cancel()
|
||||
|
||||
// its cancelled thus the promise returns the event
|
||||
const event = await promise
|
||||
|
||||
@@ -8,6 +8,7 @@ import type { ContentsWithRoot } from '@nextcloud/files'
|
||||
import { getCurrentUser } from '@nextcloud/auth'
|
||||
import { Folder, Permission } from '@nextcloud/files'
|
||||
import { defaultRemoteURL, getRootPath } from '@nextcloud/files/dav'
|
||||
import { CancelablePromise } from 'cancelable-promise'
|
||||
import logger from '../logger.ts'
|
||||
import { getPinia } from '../store/index.ts'
|
||||
import { useSearchStore } from '../store/search.ts'
|
||||
@@ -15,32 +16,29 @@ import { searchNodes } from './WebDavSearch.ts'
|
||||
|
||||
/**
|
||||
* Get the contents for a search view
|
||||
*
|
||||
* @param path - (not used)
|
||||
* @param options - Options including abort signal
|
||||
* @param options.signal - Abort signal to cancel the request
|
||||
*/
|
||||
export async function getContents(path, options: { signal: AbortSignal }): Promise<ContentsWithRoot> {
|
||||
export function getContents(): CancelablePromise<ContentsWithRoot> {
|
||||
const controller = new AbortController()
|
||||
|
||||
const searchStore = useSearchStore(getPinia())
|
||||
|
||||
try {
|
||||
const contents = await searchNodes(searchStore.query, { signal: options.signal })
|
||||
return {
|
||||
contents,
|
||||
folder: new Folder({
|
||||
id: 0,
|
||||
source: `${defaultRemoteURL}${getRootPath()}}#search`,
|
||||
owner: getCurrentUser()!.uid,
|
||||
permissions: Permission.READ,
|
||||
root: getRootPath(),
|
||||
}),
|
||||
return new CancelablePromise<ContentsWithRoot>(async (resolve, reject, cancel) => {
|
||||
cancel(() => controller.abort())
|
||||
try {
|
||||
const contents = await searchNodes(searchStore.query, { signal: controller.signal })
|
||||
resolve({
|
||||
contents,
|
||||
folder: new Folder({
|
||||
id: 0,
|
||||
source: `${defaultRemoteURL}${getRootPath()}}#search`,
|
||||
owner: getCurrentUser()!.uid,
|
||||
permissions: Permission.READ,
|
||||
root: getRootPath(),
|
||||
}),
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Failed to fetch search results', { error })
|
||||
reject(error)
|
||||
}
|
||||
} catch (error) {
|
||||
if (options.signal.aborted) {
|
||||
logger.info('Fetching search results aborted')
|
||||
throw new DOMException('Aborted', 'AbortError')
|
||||
}
|
||||
logger.error('Failed to fetch search results', { error })
|
||||
throw error
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@ import axios, { isAxiosError } from '@nextcloud/axios'
|
||||
import { emit, subscribe } from '@nextcloud/event-bus'
|
||||
import { FileType, NodeStatus } from '@nextcloud/files'
|
||||
import { t } from '@nextcloud/l10n'
|
||||
import { basename, dirname, extname } from '@nextcloud/paths'
|
||||
import { spawnDialog } from '@nextcloud/vue/functions/dialog'
|
||||
import { basename, dirname, extname } from 'path'
|
||||
import { defineStore } from 'pinia'
|
||||
import Vue, { defineAsyncComponent, ref } from 'vue'
|
||||
import logger from '../logger.ts'
|
||||
@@ -36,7 +36,7 @@ export const useRenamingStore = defineStore('renaming', () => {
|
||||
* This will rename the node set as `renamingNode` to the configured new name `newName`.
|
||||
*
|
||||
* @return true if success, false if skipped (e.g. new and old name are the same)
|
||||
* @throws {Error} if renaming fails, details are set in the error message
|
||||
* @throws Error if renaming fails, details are set in the error message
|
||||
*/
|
||||
async function rename(): Promise<boolean> {
|
||||
if (renamingNode.value === undefined) {
|
||||
|
||||
@@ -161,6 +161,7 @@
|
||||
<script lang="ts">
|
||||
import type { ContentsWithRoot, FileListAction, INode, Node } from '@nextcloud/files'
|
||||
import type { Upload } from '@nextcloud/upload'
|
||||
import type { CancelablePromise } from 'cancelable-promise'
|
||||
import type { ComponentPublicInstance } from 'vue'
|
||||
import type { Route } from 'vue-router'
|
||||
import type { UserConfig } from '../types.ts'
|
||||
@@ -173,11 +174,10 @@ import { Folder, getFileListActions, Permission, sortNodes } from '@nextcloud/fi
|
||||
import { getRemoteURL, getRootPath } from '@nextcloud/files/dav'
|
||||
import { loadState } from '@nextcloud/initial-state'
|
||||
import { translate as t } from '@nextcloud/l10n'
|
||||
import { dirname, join } from '@nextcloud/paths'
|
||||
import { ShareType } from '@nextcloud/sharing'
|
||||
import { UploadPicker, UploadStatus } from '@nextcloud/upload'
|
||||
import { useThrottleFn } from '@vueuse/core'
|
||||
import { normalize, relative } from 'path'
|
||||
import { dirname, join, normalize, relative } from 'path'
|
||||
import { defineComponent } from 'vue'
|
||||
import NcActionButton from '@nextcloud/vue/components/NcActionButton'
|
||||
import NcActions from '@nextcloud/vue/components/NcActions'
|
||||
@@ -294,8 +294,7 @@ export default defineComponent({
|
||||
loading: true,
|
||||
loadingAction: null as string | null,
|
||||
error: null as string | null,
|
||||
controller: new AbortController(),
|
||||
promise: null as Promise<ContentsWithRoot> | null,
|
||||
promise: null as CancelablePromise<ContentsWithRoot> | Promise<ContentsWithRoot> | null,
|
||||
|
||||
dirContentsFiltered: [] as INode[],
|
||||
}
|
||||
@@ -640,14 +639,13 @@ export default defineComponent({
|
||||
logger.debug('Fetching contents for directory', { dir, currentView })
|
||||
|
||||
// If we have a cancellable promise ongoing, cancel it
|
||||
if (this.promise) {
|
||||
this.controller.abort()
|
||||
if (this.promise && 'cancel' in this.promise) {
|
||||
this.promise.cancel()
|
||||
logger.debug('Cancelled previous ongoing fetch')
|
||||
}
|
||||
|
||||
// Fetch the current dir contents
|
||||
this.controller = new AbortController()
|
||||
this.promise = currentView.getContents(dir, { signal: this.controller.signal })
|
||||
this.promise = currentView.getContents(dir) as Promise<ContentsWithRoot>
|
||||
try {
|
||||
const { folder, contents } = await this.promise
|
||||
logger.debug('Fetched contents', { dir, folder, contents })
|
||||
|
||||
@@ -178,7 +178,7 @@ export default defineComponent({
|
||||
showView(view: View) {
|
||||
// Closing any opened sidebar
|
||||
window.OCA?.Files?.Sidebar?.close?.()
|
||||
getNavigation().setActive(view.id)
|
||||
getNavigation().setActive(view)
|
||||
emit('files:navigation:changed', view)
|
||||
},
|
||||
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import type { Navigation, Folder as NcFolder } from '@nextcloud/files'
|
||||
import type { Folder as CFolder, Navigation } from '@nextcloud/files'
|
||||
|
||||
import * as eventBus from '@nextcloud/event-bus'
|
||||
import * as filesUtils from '@nextcloud/files'
|
||||
import * as filesDavUtils from '@nextcloud/files/dav'
|
||||
import { CancelablePromise } from 'cancelable-promise'
|
||||
import { basename } from 'path'
|
||||
import { beforeEach, describe, expect, test, vi } from 'vitest'
|
||||
import { action } from '../actions/favoriteAction.ts'
|
||||
@@ -41,8 +42,8 @@ describe('Favorites view definition', () => {
|
||||
|
||||
test('Default empty favorite view', async () => {
|
||||
vi.spyOn(eventBus, 'subscribe')
|
||||
vi.spyOn(filesDavUtils, 'getFavoriteNodes').mockReturnValue(Promise.resolve([]))
|
||||
vi.spyOn(favoritesService, 'getContents').mockReturnValue(Promise.resolve({ folder: {} as NcFolder, contents: [] }))
|
||||
vi.spyOn(filesDavUtils, 'getFavoriteNodes').mockReturnValue(CancelablePromise.resolve([]))
|
||||
vi.spyOn(favoritesService, 'getContents').mockReturnValue(CancelablePromise.resolve({ folder: {} as CFolder, contents: [] }))
|
||||
|
||||
await registerFavoritesView()
|
||||
const favoritesView = Navigation.views.find((view) => view.id === 'favorites')
|
||||
@@ -94,8 +95,8 @@ describe('Favorites view definition', () => {
|
||||
owner: 'admin',
|
||||
}),
|
||||
]
|
||||
vi.spyOn(filesDavUtils, 'getFavoriteNodes').mockReturnValue(Promise.resolve(favoriteFolders))
|
||||
vi.spyOn(favoritesService, 'getContents').mockReturnValue(Promise.resolve({ folder: {} as NcFolder, contents: favoriteFolders }))
|
||||
vi.spyOn(filesDavUtils, 'getFavoriteNodes').mockReturnValue(CancelablePromise.resolve(favoriteFolders))
|
||||
vi.spyOn(favoritesService, 'getContents').mockReturnValue(CancelablePromise.resolve({ folder: {} as CFolder, contents: [] }))
|
||||
|
||||
await registerFavoritesView()
|
||||
const favoritesView = Navigation.views.find((view) => view.id === 'favorites')
|
||||
@@ -139,8 +140,8 @@ describe('Dynamic update of favorite folders', () => {
|
||||
|
||||
test('Add a favorite folder creates a new entry in the navigation', async () => {
|
||||
vi.spyOn(eventBus, 'emit')
|
||||
vi.spyOn(filesDavUtils, 'getFavoriteNodes').mockReturnValue(Promise.resolve([]))
|
||||
vi.spyOn(favoritesService, 'getContents').mockReturnValue(Promise.resolve({ folder: {} as NcFolder, contents: [] }))
|
||||
vi.spyOn(filesDavUtils, 'getFavoriteNodes').mockReturnValue(CancelablePromise.resolve([]))
|
||||
vi.spyOn(favoritesService, 'getContents').mockReturnValue(CancelablePromise.resolve({ folder: {} as CFolder, contents: [] }))
|
||||
|
||||
await registerFavoritesView()
|
||||
const favoritesView = Navigation.views.find((view) => view.id === 'favorites')
|
||||
@@ -163,7 +164,7 @@ describe('Dynamic update of favorite folders', () => {
|
||||
await action.exec({
|
||||
nodes: [folder],
|
||||
view: favoritesView,
|
||||
folder: {} as NcFolder,
|
||||
folder: {} as CFolder,
|
||||
contents: [],
|
||||
})
|
||||
|
||||
@@ -172,15 +173,16 @@ describe('Dynamic update of favorite folders', () => {
|
||||
})
|
||||
|
||||
test('Remove a favorite folder remove the entry from the navigation column', async () => {
|
||||
const favoriteFolders = [new Folder({
|
||||
id: 42,
|
||||
root: '/files/admin',
|
||||
source: 'http://nextcloud.local/remote.php/dav/files/admin/Foo/Bar',
|
||||
owner: 'admin',
|
||||
})]
|
||||
vi.spyOn(eventBus, 'emit')
|
||||
vi.spyOn(filesDavUtils, 'getFavoriteNodes').mockReturnValue(Promise.resolve(favoriteFolders))
|
||||
vi.spyOn(favoritesService, 'getContents').mockReturnValue(Promise.resolve({ folder: {} as NcFolder, contents: favoriteFolders }))
|
||||
vi.spyOn(filesDavUtils, 'getFavoriteNodes').mockReturnValue(CancelablePromise.resolve([
|
||||
new Folder({
|
||||
id: 42,
|
||||
root: '/files/admin',
|
||||
source: 'http://nextcloud.local/remote.php/dav/files/admin/Foo/Bar',
|
||||
owner: 'admin',
|
||||
}),
|
||||
]))
|
||||
vi.spyOn(favoritesService, 'getContents').mockReturnValue(CancelablePromise.resolve({ folder: {} as CFolder, contents: [] }))
|
||||
|
||||
await registerFavoritesView()
|
||||
let favoritesView = Navigation.views.find((view) => view.id === 'favorites')
|
||||
@@ -209,7 +211,7 @@ describe('Dynamic update of favorite folders', () => {
|
||||
await action.exec({
|
||||
nodes: [folder],
|
||||
view: favoritesView,
|
||||
folder: {} as NcFolder,
|
||||
folder: {} as CFolder,
|
||||
contents: [],
|
||||
})
|
||||
|
||||
@@ -228,8 +230,8 @@ describe('Dynamic update of favorite folders', () => {
|
||||
|
||||
test('Renaming a favorite folder updates the navigation', async () => {
|
||||
vi.spyOn(eventBus, 'emit')
|
||||
vi.spyOn(filesDavUtils, 'getFavoriteNodes').mockReturnValue(Promise.resolve([]))
|
||||
vi.spyOn(favoritesService, 'getContents').mockReturnValue(Promise.resolve({ folder: {} as NcFolder, contents: [] }))
|
||||
vi.spyOn(filesDavUtils, 'getFavoriteNodes').mockReturnValue(CancelablePromise.resolve([]))
|
||||
vi.spyOn(favoritesService, 'getContents').mockReturnValue(CancelablePromise.resolve({ folder: {} as CFolder, contents: [] }))
|
||||
|
||||
await registerFavoritesView()
|
||||
const favoritesView = Navigation.views.find((view) => view.id === 'favorites')
|
||||
@@ -254,7 +256,7 @@ describe('Dynamic update of favorite folders', () => {
|
||||
await action.exec({
|
||||
nodes: [folder],
|
||||
view: favoritesView,
|
||||
folder: {} as NcFolder,
|
||||
folder: {} as CFolder,
|
||||
contents: [],
|
||||
})
|
||||
expect(eventBus.emit).toHaveBeenNthCalledWith(1, 'files:favorites:added', folder)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/*!
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
@@ -9,16 +9,17 @@ import FolderSvg from '@mdi/svg/svg/folder-outline.svg?raw'
|
||||
import StarSvg from '@mdi/svg/svg/star-outline.svg?raw'
|
||||
import { subscribe } from '@nextcloud/event-bus'
|
||||
import { FileType, getNavigation, View } from '@nextcloud/files'
|
||||
import { getFavoriteNodes } from '@nextcloud/files/dav'
|
||||
import { getCanonicalLocale, getLanguage, t } from '@nextcloud/l10n'
|
||||
import logger from '../logger.ts'
|
||||
import { getContents } from '../services/Favorites.ts'
|
||||
import { client } from '../services/WebdavClient.ts'
|
||||
import { hashCode } from '../utils/hashUtils.ts'
|
||||
|
||||
/**
|
||||
* Generate a favorite folder view
|
||||
*
|
||||
* @param folder - The folder to generate the view for
|
||||
* @param index - The order index
|
||||
* @param folder
|
||||
* @param index
|
||||
*/
|
||||
function generateFavoriteFolderView(folder: Folder, index = 0): View {
|
||||
return new View({
|
||||
@@ -43,16 +44,15 @@ function generateFavoriteFolderView(folder: Folder, index = 0): View {
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a unique id from the folder path
|
||||
*
|
||||
* @param path - The folder path
|
||||
* @param path
|
||||
*/
|
||||
function generateIdFromPath(path: string): string {
|
||||
return `favorite-${hashCode(path)}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the favorites view and setup event listeners to update it
|
||||
*
|
||||
*/
|
||||
export async function registerFavoritesView() {
|
||||
const Navigation = getNavigation()
|
||||
@@ -72,11 +72,8 @@ export async function registerFavoritesView() {
|
||||
getContents,
|
||||
}))
|
||||
|
||||
const controller = new AbortController()
|
||||
const favoriteFolders = (await getContents('', { signal: controller.signal })).contents
|
||||
.filter((node) => node.type === FileType.Folder) as Folder[]
|
||||
const favoriteFoldersViews = favoriteFolders
|
||||
.map((folder, index) => generateFavoriteFolderView(folder, index)) as View[]
|
||||
const favoriteFolders = (await getFavoriteNodes(client)).filter((node) => node.type === FileType.Folder) as Folder[]
|
||||
const favoriteFoldersViews = favoriteFolders.map((folder, index) => generateFavoriteFolderView(folder, index)) as View[]
|
||||
logger.debug('Generating favorites view', { favoriteFolders })
|
||||
favoriteFoldersViews.forEach((view) => Navigation.register(view))
|
||||
|
||||
@@ -146,7 +143,7 @@ export async function registerFavoritesView() {
|
||||
/**
|
||||
* Add a folder to the favorites paths array and update the views
|
||||
*
|
||||
* @param node - The folder node
|
||||
* @param node
|
||||
*/
|
||||
function addToFavorites(node: Folder) {
|
||||
const view = generateFavoriteFolderView(node)
|
||||
@@ -168,7 +165,7 @@ export async function registerFavoritesView() {
|
||||
/**
|
||||
* Remove a folder from the favorites paths array and update the views
|
||||
*
|
||||
* @param path - The folder path
|
||||
* @param path
|
||||
*/
|
||||
function removePathFromFavorites(path: string) {
|
||||
const id = generateIdFromPath(path)
|
||||
@@ -191,7 +188,7 @@ export async function registerFavoritesView() {
|
||||
/**
|
||||
* Update a folder from the favorites paths array and update the views
|
||||
*
|
||||
* @param node - The updated folder node
|
||||
* @param node
|
||||
*/
|
||||
function updateNodeFromFavorites(node: Folder) {
|
||||
const favoriteFolder = favoriteFolders.find((folder) => folder.fileid === node.fileid)
|
||||
|
||||
@@ -130,7 +130,6 @@ OC.L10N.register(
|
||||
"Delete storage?" : "저장소를 삭제하시겠습니까?",
|
||||
"Click to recheck the configuration" : "설정을 다시 확인하려면 클릭",
|
||||
"Saved" : "저장됨",
|
||||
"Saving …" : "저장 중 …",
|
||||
"Save" : "저장",
|
||||
"No external storage configured or you don't have the permission to configure them" : "외부 저장소가 구성되지 않았거나 외부 저장소를 구성할 권한이 없습니다.",
|
||||
"Open documentation" : "문서 열기",
|
||||
|
||||
@@ -128,7 +128,6 @@
|
||||
"Delete storage?" : "저장소를 삭제하시겠습니까?",
|
||||
"Click to recheck the configuration" : "설정을 다시 확인하려면 클릭",
|
||||
"Saved" : "저장됨",
|
||||
"Saving …" : "저장 중 …",
|
||||
"Save" : "저장",
|
||||
"No external storage configured or you don't have the permission to configure them" : "외부 저장소가 구성되지 않았거나 외부 저장소를 구성할 권한이 없습니다.",
|
||||
"Open documentation" : "문서 열기",
|
||||
|
||||
@@ -49,7 +49,6 @@ OC.L10N.register(
|
||||
"Kerberos default realm, defaults to \"WORKGROUP\"" : "Predvolená oblasť Kerberos, ak nie je nastavené použije sa \"WORKGROUP\"",
|
||||
"Kerberos ticket Apache mode" : "Kerberos ticket v režime Apache",
|
||||
"Kerberos ticket" : "Kerberos tiket",
|
||||
"S3 Storage" : "S3 Úložisko",
|
||||
"Bucket" : "Sektor",
|
||||
"Hostname" : "Hostname",
|
||||
"Port" : "Port",
|
||||
@@ -103,7 +102,6 @@ OC.L10N.register(
|
||||
"Enter missing credentials" : "Zadajte chýbajúce prihlasovacie údaje",
|
||||
"Credentials successfully set" : "Prístupové údaje boli úspešne nastavené",
|
||||
"Error while setting credentials: {error}" : "Chyba pri nastavovaní prístupových údajov: {error}",
|
||||
"Checking storage …" : "Kontrola úložiska …",
|
||||
"There was an error with this external storage." : "Vyskytla sa chyba s externým úložiskom.",
|
||||
"We were unable to check the external storage {basename}" : "Nepodarilo sa nám skontrolovať externé úložisko {basename}",
|
||||
"Examine this faulty external storage configuration" : "Skúste preskúmať chybnú konfiguráciu externého úložiska",
|
||||
@@ -133,8 +131,6 @@ OC.L10N.register(
|
||||
"Unknown backend: {backendName}" : "Neznámy backend: {backendName}",
|
||||
"Admin defined" : "Nastavené správcom",
|
||||
"Automatic status checking is disabled due to the large number of configured storages, click to check status" : "Automatická kontrola stavu je zakázaná z dôvodu veľkého počtu nakonfigurovaných úložísk, kliknutím skontrolujte stav",
|
||||
"Are you sure you want to disconnect this external storage?" : "Naozaj chcete odpojiť tento externý úložný priestor?",
|
||||
"It will make the storage unavailable in {instanceName} and will lead to a deletion of these files and folders on any sync client that is currently connected but will not delete any files and folders on the external storage itself." : "Spôsobí, že úložisko v {instanceName} bude nedostupné a vedie k vymazaniu týchto súborov a priečinkov na akomkoľvek synchronizačnom klientovi, ktorý je aktuálne pripojený, ale nevymaže žiadne súbory a priečinky na samotnom externom úložisku.",
|
||||
"Delete storage?" : "Zmazať externé úložisko?",
|
||||
"Click to recheck the configuration" : "Kliknite na opätovnú kontrolu konfigurácie",
|
||||
"Saved" : "Uložené",
|
||||
|
||||
@@ -47,7 +47,6 @@
|
||||
"Kerberos default realm, defaults to \"WORKGROUP\"" : "Predvolená oblasť Kerberos, ak nie je nastavené použije sa \"WORKGROUP\"",
|
||||
"Kerberos ticket Apache mode" : "Kerberos ticket v režime Apache",
|
||||
"Kerberos ticket" : "Kerberos tiket",
|
||||
"S3 Storage" : "S3 Úložisko",
|
||||
"Bucket" : "Sektor",
|
||||
"Hostname" : "Hostname",
|
||||
"Port" : "Port",
|
||||
@@ -101,7 +100,6 @@
|
||||
"Enter missing credentials" : "Zadajte chýbajúce prihlasovacie údaje",
|
||||
"Credentials successfully set" : "Prístupové údaje boli úspešne nastavené",
|
||||
"Error while setting credentials: {error}" : "Chyba pri nastavovaní prístupových údajov: {error}",
|
||||
"Checking storage …" : "Kontrola úložiska …",
|
||||
"There was an error with this external storage." : "Vyskytla sa chyba s externým úložiskom.",
|
||||
"We were unable to check the external storage {basename}" : "Nepodarilo sa nám skontrolovať externé úložisko {basename}",
|
||||
"Examine this faulty external storage configuration" : "Skúste preskúmať chybnú konfiguráciu externého úložiska",
|
||||
@@ -131,8 +129,6 @@
|
||||
"Unknown backend: {backendName}" : "Neznámy backend: {backendName}",
|
||||
"Admin defined" : "Nastavené správcom",
|
||||
"Automatic status checking is disabled due to the large number of configured storages, click to check status" : "Automatická kontrola stavu je zakázaná z dôvodu veľkého počtu nakonfigurovaných úložísk, kliknutím skontrolujte stav",
|
||||
"Are you sure you want to disconnect this external storage?" : "Naozaj chcete odpojiť tento externý úložný priestor?",
|
||||
"It will make the storage unavailable in {instanceName} and will lead to a deletion of these files and folders on any sync client that is currently connected but will not delete any files and folders on the external storage itself." : "Spôsobí, že úložisko v {instanceName} bude nedostupné a vedie k vymazaniu týchto súborov a priečinkov na akomkoľvek synchronizačnom klientovi, ktorý je aktuálne pripojený, ale nevymaže žiadne súbory a priečinky na samotnom externom úložisku.",
|
||||
"Delete storage?" : "Zmazať externé úložisko?",
|
||||
"Click to recheck the configuration" : "Kliknite na opätovnú kontrolu konfigurácie",
|
||||
"Saved" : "Uložené",
|
||||
|
||||
@@ -49,7 +49,6 @@ OC.L10N.register(
|
||||
"Kerberos default realm, defaults to \"WORKGROUP\"" : "Kerberos standardområde sätts som standard till \"WORKGROUP\"",
|
||||
"Kerberos ticket Apache mode" : "Kerberos-biljett Apache-läge",
|
||||
"Kerberos ticket" : "Kerberos-biljett",
|
||||
"S3 Storage" : "S3-lagring",
|
||||
"Bucket" : "Bucket",
|
||||
"Hostname" : "Värdnamn",
|
||||
"Port" : "Port",
|
||||
|
||||
@@ -47,7 +47,6 @@
|
||||
"Kerberos default realm, defaults to \"WORKGROUP\"" : "Kerberos standardområde sätts som standard till \"WORKGROUP\"",
|
||||
"Kerberos ticket Apache mode" : "Kerberos-biljett Apache-läge",
|
||||
"Kerberos ticket" : "Kerberos-biljett",
|
||||
"S3 Storage" : "S3-lagring",
|
||||
"Bucket" : "Bucket",
|
||||
"Hostname" : "Värdnamn",
|
||||
"Port" : "Port",
|
||||
|
||||
@@ -49,7 +49,6 @@ OC.L10N.register(
|
||||
"Kerberos default realm, defaults to \"WORKGROUP\"" : "Kerberos سۈكۈتتىكى رايون ، «WORKGROUP» غا سۈكۈت قىلىدۇ",
|
||||
"Kerberos ticket Apache mode" : "Kerberos بېلەت Apache ھالىتى",
|
||||
"Kerberos ticket" : "Kerberos بېلەت",
|
||||
"S3 Storage" : "S3 ساقلىغۇچ",
|
||||
"Bucket" : "چېلەك",
|
||||
"Hostname" : "ساھىبجامال",
|
||||
"Port" : "ئېغىز",
|
||||
|
||||
@@ -47,7 +47,6 @@
|
||||
"Kerberos default realm, defaults to \"WORKGROUP\"" : "Kerberos سۈكۈتتىكى رايون ، «WORKGROUP» غا سۈكۈت قىلىدۇ",
|
||||
"Kerberos ticket Apache mode" : "Kerberos بېلەت Apache ھالىتى",
|
||||
"Kerberos ticket" : "Kerberos بېلەت",
|
||||
"S3 Storage" : "S3 ساقلىغۇچ",
|
||||
"Bucket" : "چېلەك",
|
||||
"Hostname" : "ساھىبجامال",
|
||||
"Port" : "ئېغىز",
|
||||
|
||||
@@ -14,11 +14,9 @@ use OCP\IUserManager;
|
||||
use OCP\IUserSession;
|
||||
use OCP\Share\Exceptions\ShareNotFound;
|
||||
use OCP\Share\IManager;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Test\TestCase;
|
||||
|
||||
class UserPlaceholderHandlerTest extends TestCase {
|
||||
class UserPlaceholderHandlerTest extends \Test\TestCase {
|
||||
protected IUser&MockObject $user;
|
||||
protected IUserSession&MockObject $session;
|
||||
protected IManager&MockObject $shareManager;
|
||||
@@ -36,9 +34,6 @@ class UserPlaceholderHandlerTest extends TestCase {
|
||||
$this->session = $this->createMock(IUserSession::class);
|
||||
$this->shareManager = $this->createMock(IManager::class);
|
||||
$this->request = $this->createMock(IRequest::class);
|
||||
$this->request->method('getParam')
|
||||
->with('token')
|
||||
->willReturn('foo');
|
||||
$this->userManager = $this->createMock(IUserManager::class);
|
||||
|
||||
$this->handler = new UserPlaceholderHandler($this->session, $this->shareManager, $this->request, $this->userManager);
|
||||
@@ -58,17 +53,16 @@ class UserPlaceholderHandlerTest extends TestCase {
|
||||
];
|
||||
}
|
||||
|
||||
#[DataProvider('optionProvider')]
|
||||
#[\PHPUnit\Framework\Attributes\DataProvider('optionProvider')]
|
||||
public function testHandle(string|array $option, string|array $expected): void {
|
||||
$this->setUser();
|
||||
$this->assertSame($expected, $this->handler->handle($option));
|
||||
}
|
||||
|
||||
#[DataProvider('optionProvider')]
|
||||
#[\PHPUnit\Framework\Attributes\DataProvider('optionProvider')]
|
||||
public function testHandleNoUser(string|array $option): void {
|
||||
$this->shareManager->expects($this->once())
|
||||
->method('getShareByToken')
|
||||
->with('foo')
|
||||
->willThrowException(new ShareNotFound());
|
||||
$this->assertSame($option, $this->handler->handle($option));
|
||||
}
|
||||
|
||||
@@ -5,9 +5,6 @@ OC.L10N.register(
|
||||
"Reminder for {name}" : "Pripomienka pre {name}",
|
||||
"View file" : "Zobraziť súbor",
|
||||
"View folder" : "Zobraziť adresár",
|
||||
"Files reminder" : "Pripomienka súborov",
|
||||
"The \"files_reminders\" app can work properly." : "Aplikácia \"files_reminders\" môže fungovať správne.",
|
||||
"The \"files_reminders\" app needs the notification app to work properly. You should either enable notifications or disable files_reminder." : "Aplikácia \"files_reminders\" potrebuje aplikáciu na notifikácie na správne fungovanie. Mali by ste buď povoliť notifikácie, alebo vypnúť aplikáciu files_reminder.",
|
||||
"Set file reminders" : "Nastaviť pripomienky súborov",
|
||||
"**📣 File reminders**\n\nSet file reminders.\n\nNote: to use the `File reminders` app, ensure that the `Notifications` app is installed and enabled. The `Notifications` app provides the necessary APIs for the `File reminders` app to work correctly." : "**📣 Pripomienky súborov**\n\nNastavte pripomienky súborov.\n\nPoznámka: Ak chcete použiť aplikáciu „Pripomienky súborov“, uistite sa, že je nainštalovaná a povolená aplikácia „Upozornenia“. Aplikácia „Upozornenia“ poskytuje potrebné rozhrania API, aby aplikácia „Pripomienky súborov“ fungovala správne.",
|
||||
"Please choose a valid date & time" : "Prosím, vyberte platný dátum a čas",
|
||||
@@ -15,14 +12,11 @@ OC.L10N.register(
|
||||
"Failed to set reminder" : "Nepodarilo sa nastavit pripomienku",
|
||||
"Reminder cleared for \"{fileName}\"" : "Pripomienka pre \"{fileName}\" bola odstránená",
|
||||
"Failed to clear reminder" : "Nepodarilo sa odstrániť pripomienku",
|
||||
"Reminder at custom date & time" : "Pripomienka vo vlastný dátum a čas",
|
||||
"We will remind you of this file" : "Pripomenieme vám tento súbor",
|
||||
"Cancel" : "Zrušiť",
|
||||
"Clear reminder" : "Vymazať pripomienku",
|
||||
"Set reminder" : "Nastaviť pripomienku",
|
||||
"Set reminder for " : "Nastaviť pripomienku na",
|
||||
"Reminder set" : "Pripomienka bola nastavená",
|
||||
"Custom reminder" : "Vlastná pripomienka",
|
||||
"Later today" : "Neskôr dnes",
|
||||
"Set reminder for later today" : "Nastaviť pripomienku na dnes-neskôr.",
|
||||
"Tomorrow" : "Zajtra",
|
||||
@@ -31,8 +25,6 @@ OC.L10N.register(
|
||||
"Set reminder for this weekend" : "Nastaviť pripomienku na tento víkend",
|
||||
"Next week" : "Nasledujúci týždeň",
|
||||
"Set reminder for next week" : "Nastaviť pripomienku na budúci týždeň",
|
||||
"This files_reminder can work properly." : "Tento files_reminder môže správne fungovať.",
|
||||
"The files_reminder app needs the notification app to work properly. You should either enable notifications or disable files_reminder." : "Aplikácia files_reminder potrebuje aplikáciu na oznámenia na správne fungovanie. Mali by ste buď povoliť oznámenia, alebo vypnúť files_reminder.",
|
||||
"Set reminder for \"{fileName}\"" : "Nastaviť pripomienku pre \"{fileName}\"",
|
||||
"Set reminder at custom date & time" : "Nastaviť pripomienku na vlastn dátum a čas",
|
||||
"Set custom reminder" : "Nastaviť vlastnú pripomienku"
|
||||
|
||||
@@ -3,9 +3,6 @@
|
||||
"Reminder for {name}" : "Pripomienka pre {name}",
|
||||
"View file" : "Zobraziť súbor",
|
||||
"View folder" : "Zobraziť adresár",
|
||||
"Files reminder" : "Pripomienka súborov",
|
||||
"The \"files_reminders\" app can work properly." : "Aplikácia \"files_reminders\" môže fungovať správne.",
|
||||
"The \"files_reminders\" app needs the notification app to work properly. You should either enable notifications or disable files_reminder." : "Aplikácia \"files_reminders\" potrebuje aplikáciu na notifikácie na správne fungovanie. Mali by ste buď povoliť notifikácie, alebo vypnúť aplikáciu files_reminder.",
|
||||
"Set file reminders" : "Nastaviť pripomienky súborov",
|
||||
"**📣 File reminders**\n\nSet file reminders.\n\nNote: to use the `File reminders` app, ensure that the `Notifications` app is installed and enabled. The `Notifications` app provides the necessary APIs for the `File reminders` app to work correctly." : "**📣 Pripomienky súborov**\n\nNastavte pripomienky súborov.\n\nPoznámka: Ak chcete použiť aplikáciu „Pripomienky súborov“, uistite sa, že je nainštalovaná a povolená aplikácia „Upozornenia“. Aplikácia „Upozornenia“ poskytuje potrebné rozhrania API, aby aplikácia „Pripomienky súborov“ fungovala správne.",
|
||||
"Please choose a valid date & time" : "Prosím, vyberte platný dátum a čas",
|
||||
@@ -13,14 +10,11 @@
|
||||
"Failed to set reminder" : "Nepodarilo sa nastavit pripomienku",
|
||||
"Reminder cleared for \"{fileName}\"" : "Pripomienka pre \"{fileName}\" bola odstránená",
|
||||
"Failed to clear reminder" : "Nepodarilo sa odstrániť pripomienku",
|
||||
"Reminder at custom date & time" : "Pripomienka vo vlastný dátum a čas",
|
||||
"We will remind you of this file" : "Pripomenieme vám tento súbor",
|
||||
"Cancel" : "Zrušiť",
|
||||
"Clear reminder" : "Vymazať pripomienku",
|
||||
"Set reminder" : "Nastaviť pripomienku",
|
||||
"Set reminder for " : "Nastaviť pripomienku na",
|
||||
"Reminder set" : "Pripomienka bola nastavená",
|
||||
"Custom reminder" : "Vlastná pripomienka",
|
||||
"Later today" : "Neskôr dnes",
|
||||
"Set reminder for later today" : "Nastaviť pripomienku na dnes-neskôr.",
|
||||
"Tomorrow" : "Zajtra",
|
||||
@@ -29,8 +23,6 @@
|
||||
"Set reminder for this weekend" : "Nastaviť pripomienku na tento víkend",
|
||||
"Next week" : "Nasledujúci týždeň",
|
||||
"Set reminder for next week" : "Nastaviť pripomienku na budúci týždeň",
|
||||
"This files_reminder can work properly." : "Tento files_reminder môže správne fungovať.",
|
||||
"The files_reminder app needs the notification app to work properly. You should either enable notifications or disable files_reminder." : "Aplikácia files_reminder potrebuje aplikáciu na oznámenia na správne fungovanie. Mali by ste buď povoliť oznámenia, alebo vypnúť files_reminder.",
|
||||
"Set reminder for \"{fileName}\"" : "Nastaviť pripomienku pre \"{fileName}\"",
|
||||
"Set reminder at custom date & time" : "Nastaviť pripomienku na vlastn dátum a čas",
|
||||
"Set custom reminder" : "Nastaviť vlastnú pripomienku"
|
||||
|
||||
@@ -20,7 +20,6 @@ OC.L10N.register(
|
||||
"Cancel" : "Avbryt",
|
||||
"Clear reminder" : "Rensa påminnelse",
|
||||
"Set reminder" : "Ställ in påminnelse",
|
||||
"Set reminder for " : "Ställ in påminnelse för",
|
||||
"Reminder set" : "Påminnelse inställd",
|
||||
"Custom reminder" : "Anpassad påminnelse",
|
||||
"Later today" : "Senare idag",
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
"Cancel" : "Avbryt",
|
||||
"Clear reminder" : "Rensa påminnelse",
|
||||
"Set reminder" : "Ställ in påminnelse",
|
||||
"Set reminder for " : "Ställ in påminnelse för",
|
||||
"Reminder set" : "Påminnelse inställd",
|
||||
"Custom reminder" : "Anpassad påminnelse",
|
||||
"Later today" : "Senare idag",
|
||||
|
||||
@@ -50,8 +50,6 @@ return array(
|
||||
'OCA\\Files_Sharing\\Exceptions\\SharingRightsException' => $baseDir . '/../lib/Exceptions/SharingRightsException.php',
|
||||
'OCA\\Files_Sharing\\ExpireSharesJob' => $baseDir . '/../lib/ExpireSharesJob.php',
|
||||
'OCA\\Files_Sharing\\External\\Cache' => $baseDir . '/../lib/External/Cache.php',
|
||||
'OCA\\Files_Sharing\\External\\ExternalShare' => $baseDir . '/../lib/External/ExternalShare.php',
|
||||
'OCA\\Files_Sharing\\External\\ExternalShareMapper' => $baseDir . '/../lib/External/ExternalShareMapper.php',
|
||||
'OCA\\Files_Sharing\\External\\Manager' => $baseDir . '/../lib/External/Manager.php',
|
||||
'OCA\\Files_Sharing\\External\\Mount' => $baseDir . '/../lib/External/Mount.php',
|
||||
'OCA\\Files_Sharing\\External\\MountProvider' => $baseDir . '/../lib/External/MountProvider.php',
|
||||
@@ -84,7 +82,6 @@ return array(
|
||||
'OCA\\Files_Sharing\\Migration\\Version24000Date20220404142216' => $baseDir . '/../lib/Migration/Version24000Date20220404142216.php',
|
||||
'OCA\\Files_Sharing\\Migration\\Version31000Date20240821142813' => $baseDir . '/../lib/Migration/Version31000Date20240821142813.php',
|
||||
'OCA\\Files_Sharing\\Migration\\Version32000Date20251017081948' => $baseDir . '/../lib/Migration/Version32000Date20251017081948.php',
|
||||
'OCA\\Files_Sharing\\Migration\\Version33000Date20251030081948' => $baseDir . '/../lib/Migration/Version33000Date20251030081948.php',
|
||||
'OCA\\Files_Sharing\\MountProvider' => $baseDir . '/../lib/MountProvider.php',
|
||||
'OCA\\Files_Sharing\\Notification\\Listener' => $baseDir . '/../lib/Notification/Listener.php',
|
||||
'OCA\\Files_Sharing\\Notification\\Notifier' => $baseDir . '/../lib/Notification/Notifier.php',
|
||||
|
||||
@@ -65,8 +65,6 @@ class ComposerStaticInitFiles_Sharing
|
||||
'OCA\\Files_Sharing\\Exceptions\\SharingRightsException' => __DIR__ . '/..' . '/../lib/Exceptions/SharingRightsException.php',
|
||||
'OCA\\Files_Sharing\\ExpireSharesJob' => __DIR__ . '/..' . '/../lib/ExpireSharesJob.php',
|
||||
'OCA\\Files_Sharing\\External\\Cache' => __DIR__ . '/..' . '/../lib/External/Cache.php',
|
||||
'OCA\\Files_Sharing\\External\\ExternalShare' => __DIR__ . '/..' . '/../lib/External/ExternalShare.php',
|
||||
'OCA\\Files_Sharing\\External\\ExternalShareMapper' => __DIR__ . '/..' . '/../lib/External/ExternalShareMapper.php',
|
||||
'OCA\\Files_Sharing\\External\\Manager' => __DIR__ . '/..' . '/../lib/External/Manager.php',
|
||||
'OCA\\Files_Sharing\\External\\Mount' => __DIR__ . '/..' . '/../lib/External/Mount.php',
|
||||
'OCA\\Files_Sharing\\External\\MountProvider' => __DIR__ . '/..' . '/../lib/External/MountProvider.php',
|
||||
@@ -99,7 +97,6 @@ class ComposerStaticInitFiles_Sharing
|
||||
'OCA\\Files_Sharing\\Migration\\Version24000Date20220404142216' => __DIR__ . '/..' . '/../lib/Migration/Version24000Date20220404142216.php',
|
||||
'OCA\\Files_Sharing\\Migration\\Version31000Date20240821142813' => __DIR__ . '/..' . '/../lib/Migration/Version31000Date20240821142813.php',
|
||||
'OCA\\Files_Sharing\\Migration\\Version32000Date20251017081948' => __DIR__ . '/..' . '/../lib/Migration/Version32000Date20251017081948.php',
|
||||
'OCA\\Files_Sharing\\Migration\\Version33000Date20251030081948' => __DIR__ . '/..' . '/../lib/Migration/Version33000Date20251030081948.php',
|
||||
'OCA\\Files_Sharing\\MountProvider' => __DIR__ . '/..' . '/../lib/MountProvider.php',
|
||||
'OCA\\Files_Sharing\\Notification\\Listener' => __DIR__ . '/..' . '/../lib/Notification/Listener.php',
|
||||
'OCA\\Files_Sharing\\Notification\\Notifier' => __DIR__ . '/..' . '/../lib/Notification/Notifier.php',
|
||||
|
||||
@@ -77,6 +77,7 @@ OC.L10N.register(
|
||||
"You cannot share to a Team if the app is not enabled" : "لا يمكنك المشاركة مع فريق إذا لم يكن التطبيق مُمكّناً",
|
||||
"Please specify a valid team" : "من فضلك، قم بتحديد فريق صحيح",
|
||||
"Sharing %s failed because the back end does not support room shares" : "فشلت مشاركة %s لأن الخلفية back end لا تدعم مشاركات الغُرَف room shares",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : " المشاركة %s فشلت بسبب أن الخادم لا يدعم مشاركات ScienceMesh",
|
||||
"Unknown share type" : "نوع مشاركة غير معروف",
|
||||
"Not a directory" : "ليس مُجلّداً صحيحاً",
|
||||
"Could not lock node" : "تعذّر قَفْل lock النقطة node",
|
||||
@@ -372,7 +373,6 @@ OC.L10N.register(
|
||||
"Share not found" : "مشاركة غير موجودة",
|
||||
"Back to %s" : "عودة إلى %s",
|
||||
"Add to your Nextcloud" : "إضافة إلى حسابك على نكست كلاود",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : " المشاركة %s فشلت بسبب أن الخادم لا يدعم مشاركات ScienceMesh",
|
||||
"Link copied to clipboard" : "تمّ نسخ الرابط إلى الحافظة",
|
||||
"Copy to clipboard" : "نسخ الرابط إلى الحافظة",
|
||||
"Copy internal link to clipboard" : "إنسَخ رابطاً داخليّاً إلى الحافظة",
|
||||
|
||||
@@ -75,6 +75,7 @@
|
||||
"You cannot share to a Team if the app is not enabled" : "لا يمكنك المشاركة مع فريق إذا لم يكن التطبيق مُمكّناً",
|
||||
"Please specify a valid team" : "من فضلك، قم بتحديد فريق صحيح",
|
||||
"Sharing %s failed because the back end does not support room shares" : "فشلت مشاركة %s لأن الخلفية back end لا تدعم مشاركات الغُرَف room shares",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : " المشاركة %s فشلت بسبب أن الخادم لا يدعم مشاركات ScienceMesh",
|
||||
"Unknown share type" : "نوع مشاركة غير معروف",
|
||||
"Not a directory" : "ليس مُجلّداً صحيحاً",
|
||||
"Could not lock node" : "تعذّر قَفْل lock النقطة node",
|
||||
@@ -370,7 +371,6 @@
|
||||
"Share not found" : "مشاركة غير موجودة",
|
||||
"Back to %s" : "عودة إلى %s",
|
||||
"Add to your Nextcloud" : "إضافة إلى حسابك على نكست كلاود",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : " المشاركة %s فشلت بسبب أن الخادم لا يدعم مشاركات ScienceMesh",
|
||||
"Link copied to clipboard" : "تمّ نسخ الرابط إلى الحافظة",
|
||||
"Copy to clipboard" : "نسخ الرابط إلى الحافظة",
|
||||
"Copy internal link to clipboard" : "إنسَخ رابطاً داخليّاً إلى الحافظة",
|
||||
|
||||
@@ -73,6 +73,7 @@ OC.L10N.register(
|
||||
"Please specify a valid federated account ID" : "Especifica una ID de cuenta federada válida",
|
||||
"Please specify a valid federated group ID" : "Especifica una ID de grupu federáu válida",
|
||||
"Sharing %s failed because the back end does not support room shares" : "Nun se pudo compartir «%s» porque'l backend nun ye compatible coles comparticiones con sales",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : "Nun se pudo compartir «%s» porque nun ye compatible coles comparticiones de ScienceMesh",
|
||||
"Unknown share type" : "Tipu de compartición desconocida",
|
||||
"Not a directory" : "Nun ye un direutoriu",
|
||||
"Could not lock node" : "Nun se pudo bloquiar el noyu",
|
||||
@@ -268,7 +269,6 @@ OC.L10N.register(
|
||||
"Share not found" : "Nun s'atopó la compartición",
|
||||
"Back to %s" : "Volver a «%s»",
|
||||
"Add to your Nextcloud" : "Amestar a Nextcloud",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : "Nun se pudo compartir «%s» porque nun ye compatible coles comparticiones de ScienceMesh",
|
||||
"Link copied to clipboard" : "L'enllaz copióse nel cartafueyu",
|
||||
"Copy to clipboard" : "Copiar nel cartafueyu",
|
||||
"Copy internal link to clipboard" : "Copiar l'enllaz internu nel cartafueyu",
|
||||
|
||||
@@ -71,6 +71,7 @@
|
||||
"Please specify a valid federated account ID" : "Especifica una ID de cuenta federada válida",
|
||||
"Please specify a valid federated group ID" : "Especifica una ID de grupu federáu válida",
|
||||
"Sharing %s failed because the back end does not support room shares" : "Nun se pudo compartir «%s» porque'l backend nun ye compatible coles comparticiones con sales",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : "Nun se pudo compartir «%s» porque nun ye compatible coles comparticiones de ScienceMesh",
|
||||
"Unknown share type" : "Tipu de compartición desconocida",
|
||||
"Not a directory" : "Nun ye un direutoriu",
|
||||
"Could not lock node" : "Nun se pudo bloquiar el noyu",
|
||||
@@ -266,7 +267,6 @@
|
||||
"Share not found" : "Nun s'atopó la compartición",
|
||||
"Back to %s" : "Volver a «%s»",
|
||||
"Add to your Nextcloud" : "Amestar a Nextcloud",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : "Nun se pudo compartir «%s» porque nun ye compatible coles comparticiones de ScienceMesh",
|
||||
"Link copied to clipboard" : "L'enllaz copióse nel cartafueyu",
|
||||
"Copy to clipboard" : "Copiar nel cartafueyu",
|
||||
"Copy internal link to clipboard" : "Copiar l'enllaz internu nel cartafueyu",
|
||||
|
||||
@@ -71,6 +71,7 @@ OC.L10N.register(
|
||||
"Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Споделянето %1$s не бе успешно, защото вътрешния сървър не позволява споделяния от тип %2$s",
|
||||
"Please specify a valid federated group ID" : "Моля, посочете валиден идентификатор на федерирана група",
|
||||
"Sharing %s failed because the back end does not support room shares" : "Споделянето %s не бе успешно, защото вътрешния сървър не позволява споделяния на стаите",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : "Споделянето %s не бе успешно, защото вътрешния сървър не позволява споделяния на приложението ScienceMesh",
|
||||
"Unknown share type" : "Неизвестен тип споделяне",
|
||||
"Not a directory" : "Не е директория",
|
||||
"Could not lock node" : "Възелът не можа да се заключи",
|
||||
@@ -223,7 +224,6 @@ OC.L10N.register(
|
||||
"Share not found" : "Споделянето не е открито",
|
||||
"Back to %s" : "Обратно към %s",
|
||||
"Add to your Nextcloud" : "Добавете към Nextcloud",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : "Споделянето %s не бе успешно, защото вътрешния сървър не позволява споделяния на приложението ScienceMesh",
|
||||
"Link copied to clipboard" : "Връзката е копирана в клипборда",
|
||||
"Copy to clipboard" : "Копиране в клипборда",
|
||||
"Copy internal link to clipboard" : "Копиране на вътрешна връзката в клипборда",
|
||||
|
||||
@@ -69,6 +69,7 @@
|
||||
"Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Споделянето %1$s не бе успешно, защото вътрешния сървър не позволява споделяния от тип %2$s",
|
||||
"Please specify a valid federated group ID" : "Моля, посочете валиден идентификатор на федерирана група",
|
||||
"Sharing %s failed because the back end does not support room shares" : "Споделянето %s не бе успешно, защото вътрешния сървър не позволява споделяния на стаите",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : "Споделянето %s не бе успешно, защото вътрешния сървър не позволява споделяния на приложението ScienceMesh",
|
||||
"Unknown share type" : "Неизвестен тип споделяне",
|
||||
"Not a directory" : "Не е директория",
|
||||
"Could not lock node" : "Възелът не можа да се заключи",
|
||||
@@ -221,7 +222,6 @@
|
||||
"Share not found" : "Споделянето не е открито",
|
||||
"Back to %s" : "Обратно към %s",
|
||||
"Add to your Nextcloud" : "Добавете към Nextcloud",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : "Споделянето %s не бе успешно, защото вътрешния сървър не позволява споделяния на приложението ScienceMesh",
|
||||
"Link copied to clipboard" : "Връзката е копирана в клипборда",
|
||||
"Copy to clipboard" : "Копиране в клипборда",
|
||||
"Copy internal link to clipboard" : "Копиране на вътрешна връзката в клипборда",
|
||||
|
||||
@@ -77,6 +77,7 @@ OC.L10N.register(
|
||||
"You cannot share to a Team if the app is not enabled" : "No podeu compartir amb un Equip si l'aplicació no està habilitada",
|
||||
"Please specify a valid team" : "Especifiqueu un equip vàlid",
|
||||
"Sharing %s failed because the back end does not support room shares" : "No s'ha pogut compartir %s perquè el rerefons no permet l'ús sales compartides",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : "No s'ha pogut compartir %s perquè el rerefons no permet elements compartits de ScienceMesh",
|
||||
"Unknown share type" : "Tipus d'element compartit desconegut",
|
||||
"Not a directory" : "No és una carpeta",
|
||||
"Could not lock node" : "No s'ha pogut blocar el node",
|
||||
@@ -374,7 +375,6 @@ OC.L10N.register(
|
||||
"Share not found" : "No s'ha trobat la compartició",
|
||||
"Back to %s" : "Torna a %s",
|
||||
"Add to your Nextcloud" : "Afegeix al Nextcloud",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : "No s'ha pogut compartir %s perquè el rerefons no permet elements compartits de ScienceMesh",
|
||||
"Link copied to clipboard" : "Enllaç copiat al porta-retalls",
|
||||
"Copy to clipboard" : "Copia-ho al porta-retalls",
|
||||
"Copy internal link to clipboard" : "Copia l'enllaç intern al porta-retalls",
|
||||
|
||||
@@ -75,6 +75,7 @@
|
||||
"You cannot share to a Team if the app is not enabled" : "No podeu compartir amb un Equip si l'aplicació no està habilitada",
|
||||
"Please specify a valid team" : "Especifiqueu un equip vàlid",
|
||||
"Sharing %s failed because the back end does not support room shares" : "No s'ha pogut compartir %s perquè el rerefons no permet l'ús sales compartides",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : "No s'ha pogut compartir %s perquè el rerefons no permet elements compartits de ScienceMesh",
|
||||
"Unknown share type" : "Tipus d'element compartit desconegut",
|
||||
"Not a directory" : "No és una carpeta",
|
||||
"Could not lock node" : "No s'ha pogut blocar el node",
|
||||
@@ -372,7 +373,6 @@
|
||||
"Share not found" : "No s'ha trobat la compartició",
|
||||
"Back to %s" : "Torna a %s",
|
||||
"Add to your Nextcloud" : "Afegeix al Nextcloud",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : "No s'ha pogut compartir %s perquè el rerefons no permet elements compartits de ScienceMesh",
|
||||
"Link copied to clipboard" : "Enllaç copiat al porta-retalls",
|
||||
"Copy to clipboard" : "Copia-ho al porta-retalls",
|
||||
"Copy internal link to clipboard" : "Copia l'enllaç intern al porta-retalls",
|
||||
|
||||
@@ -77,6 +77,7 @@ OC.L10N.register(
|
||||
"You cannot share to a Team if the app is not enabled" : "Týmu nemůžete sdílet, pokud není příslušná aplikace zapnutá",
|
||||
"Please specify a valid team" : "Zadejte platný tým",
|
||||
"Sharing %s failed because the back end does not support room shares" : "Sdílení %s se nezdařilo protože podpůrná vrstva nepodporuje sdílení místností",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : "Sdílení %s se nezdařilo protože podpůrná vrstva nepodporuje ScienceMesh sdílení",
|
||||
"Unknown share type" : "Neznámý typ sdílení",
|
||||
"Not a directory" : "Není adresář",
|
||||
"Could not lock node" : "Uzel se nedaří uzamknout",
|
||||
@@ -403,7 +404,6 @@ OC.L10N.register(
|
||||
"Share not found" : "Sdílení nenalezeno",
|
||||
"Back to %s" : "Zpět na %s",
|
||||
"Add to your Nextcloud" : "Přidat do Nextcloud",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : "Sdílení %s se nezdařilo protože podpůrná vrstva nepodporuje ScienceMesh sdílení",
|
||||
"Link copied to clipboard" : "Odkaz zkopírován do schánky",
|
||||
"Copy to clipboard" : "Zkopírovat do schránky",
|
||||
"Copy internal link to clipboard" : "Zkopírovat interní odkaz do schránky",
|
||||
|
||||
@@ -75,6 +75,7 @@
|
||||
"You cannot share to a Team if the app is not enabled" : "Týmu nemůžete sdílet, pokud není příslušná aplikace zapnutá",
|
||||
"Please specify a valid team" : "Zadejte platný tým",
|
||||
"Sharing %s failed because the back end does not support room shares" : "Sdílení %s se nezdařilo protože podpůrná vrstva nepodporuje sdílení místností",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : "Sdílení %s se nezdařilo protože podpůrná vrstva nepodporuje ScienceMesh sdílení",
|
||||
"Unknown share type" : "Neznámý typ sdílení",
|
||||
"Not a directory" : "Není adresář",
|
||||
"Could not lock node" : "Uzel se nedaří uzamknout",
|
||||
@@ -401,7 +402,6 @@
|
||||
"Share not found" : "Sdílení nenalezeno",
|
||||
"Back to %s" : "Zpět na %s",
|
||||
"Add to your Nextcloud" : "Přidat do Nextcloud",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : "Sdílení %s se nezdařilo protože podpůrná vrstva nepodporuje ScienceMesh sdílení",
|
||||
"Link copied to clipboard" : "Odkaz zkopírován do schánky",
|
||||
"Copy to clipboard" : "Zkopírovat do schránky",
|
||||
"Copy internal link to clipboard" : "Zkopírovat interní odkaz do schránky",
|
||||
|
||||
@@ -77,6 +77,7 @@ OC.L10N.register(
|
||||
"You cannot share to a Team if the app is not enabled" : "Du kan ikke dele til et Team, hvis app'en ikke er aktiveret",
|
||||
"Please specify a valid team" : "Angiv venligst et gyldigt team",
|
||||
"Sharing %s failed because the back end does not support room shares" : "Deling af %s mislykkedes fordi backenden ikke tillader delinger af rumdeling",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : "Kunne ikke dele %s fordi backenden ikke understøtter deling af ScienceMesh",
|
||||
"Unknown share type" : "Ukendt deletype",
|
||||
"Not a directory" : "Ikke en mappe",
|
||||
"Could not lock node" : "Kunne ikke låse node",
|
||||
@@ -403,7 +404,6 @@ OC.L10N.register(
|
||||
"Share not found" : "Delt fil ikke fundet",
|
||||
"Back to %s" : "Tilbage til %s",
|
||||
"Add to your Nextcloud" : "Tilføj til din Nextcloud",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : "Kunne ikke dele %s fordi backenden ikke understøtter deling af ScienceMesh",
|
||||
"Link copied to clipboard" : "Link kopieret til udklipsholder",
|
||||
"Copy to clipboard" : "Kopier til udklipsholder",
|
||||
"Copy internal link to clipboard" : "Kopier internt link til klippebord",
|
||||
|
||||
@@ -75,6 +75,7 @@
|
||||
"You cannot share to a Team if the app is not enabled" : "Du kan ikke dele til et Team, hvis app'en ikke er aktiveret",
|
||||
"Please specify a valid team" : "Angiv venligst et gyldigt team",
|
||||
"Sharing %s failed because the back end does not support room shares" : "Deling af %s mislykkedes fordi backenden ikke tillader delinger af rumdeling",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : "Kunne ikke dele %s fordi backenden ikke understøtter deling af ScienceMesh",
|
||||
"Unknown share type" : "Ukendt deletype",
|
||||
"Not a directory" : "Ikke en mappe",
|
||||
"Could not lock node" : "Kunne ikke låse node",
|
||||
@@ -401,7 +402,6 @@
|
||||
"Share not found" : "Delt fil ikke fundet",
|
||||
"Back to %s" : "Tilbage til %s",
|
||||
"Add to your Nextcloud" : "Tilføj til din Nextcloud",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : "Kunne ikke dele %s fordi backenden ikke understøtter deling af ScienceMesh",
|
||||
"Link copied to clipboard" : "Link kopieret til udklipsholder",
|
||||
"Copy to clipboard" : "Kopier til udklipsholder",
|
||||
"Copy internal link to clipboard" : "Kopier internt link til klippebord",
|
||||
|
||||
@@ -77,6 +77,7 @@ OC.L10N.register(
|
||||
"You cannot share to a Team if the app is not enabled" : "Du kannst nichts mit einem Team teilen, wenn die App nicht aktiviert ist",
|
||||
"Please specify a valid team" : "Bitte ein gültiges Team angeben",
|
||||
"Sharing %s failed because the back end does not support room shares" : "Freigabe von %s fehlgeschlagen, da das Backend die Freigabe von Räumen nicht unterstützt",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : "Freigabe von %s fehlgeschlagen, da das Backend keine ScienceMesh-Freigaben unterstützt",
|
||||
"Unknown share type" : "Unbekannter Freigabetyp",
|
||||
"Not a directory" : "Kein Verzeichnis",
|
||||
"Could not lock node" : "Node konnte nicht gesperrt werden",
|
||||
@@ -304,8 +305,8 @@ OC.L10N.register(
|
||||
"Unable to fetch inherited shares" : "Vererbte Freigaben konnten nicht geladen werden",
|
||||
"Link shares" : "Freigaben teilen",
|
||||
"Shares" : "Freigaben",
|
||||
"Share files within your organization. Recipients who can already view the file can also use this link for easy access." : "Dateien innerhalb deiner Organisation teilen. Auch Empfänger, die auf die Datei bereits zugreifen können, können diesen Link für einen einfachen Zugriff nutzen.",
|
||||
"Share files with others outside your organization via public links and email addresses. You can also share to {productName} accounts on other instances using their federated cloud ID." : "Dateien über öffentliche Links und E-Mail-Adressen mit anderen außerhalb deiner Organisation teilen. Du kannst {productName}-Konten auch auf anderen Instanzen mithilfe deiner Federated-Cloud-ID teilen.",
|
||||
"Share files within your organization. Recipients who can already view the file can also use this link for easy access." : "Dateien innerhalb Ihrer Organisation teilen. Auch Empfänger, die auf die Datei bereits zugreifen können, können diesen Link für einen einfachen Zugriff nutzen.",
|
||||
"Share files with others outside your organization via public links and email addresses. You can also share to {productName} accounts on other instances using their federated cloud ID." : "Dateien über öffentliche Links und E-Mail-Adressen mit anderen außerhalb Ihrer Organisation teilen. Sie können {productName}-Konten auch auf anderen Instanzen mithilfe ihrer Federated-Cloud-ID teilen.",
|
||||
"Shares from apps or other sources which are not included in internal or external shares." : "Freigaben aus Apps oder anderen Quellen, die nicht in internen oder externen Freigaben enthalten sind.",
|
||||
"Type names, teams, federated cloud IDs" : "Namen, Teams oder Federated-Cloud-IDs eingeben",
|
||||
"Type names or teams" : "Namen oder Federated-Cloud-IDs eingeben",
|
||||
@@ -403,7 +404,6 @@ OC.L10N.register(
|
||||
"Share not found" : "Freigabe nicht gefunden",
|
||||
"Back to %s" : "Zurück zu %s",
|
||||
"Add to your Nextcloud" : "Zu deiner Nextcloud hinzufügen",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : "Freigabe von %s fehlgeschlagen, da das Backend keine ScienceMesh-Freigaben unterstützt",
|
||||
"Link copied to clipboard" : "Link wurde in die Zwischenablage kopiert",
|
||||
"Copy to clipboard" : "In die Zwischenablage kopieren",
|
||||
"Copy internal link to clipboard" : "Internen Link in die Zwischenablage kopieren",
|
||||
|
||||
@@ -75,6 +75,7 @@
|
||||
"You cannot share to a Team if the app is not enabled" : "Du kannst nichts mit einem Team teilen, wenn die App nicht aktiviert ist",
|
||||
"Please specify a valid team" : "Bitte ein gültiges Team angeben",
|
||||
"Sharing %s failed because the back end does not support room shares" : "Freigabe von %s fehlgeschlagen, da das Backend die Freigabe von Räumen nicht unterstützt",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : "Freigabe von %s fehlgeschlagen, da das Backend keine ScienceMesh-Freigaben unterstützt",
|
||||
"Unknown share type" : "Unbekannter Freigabetyp",
|
||||
"Not a directory" : "Kein Verzeichnis",
|
||||
"Could not lock node" : "Node konnte nicht gesperrt werden",
|
||||
@@ -302,8 +303,8 @@
|
||||
"Unable to fetch inherited shares" : "Vererbte Freigaben konnten nicht geladen werden",
|
||||
"Link shares" : "Freigaben teilen",
|
||||
"Shares" : "Freigaben",
|
||||
"Share files within your organization. Recipients who can already view the file can also use this link for easy access." : "Dateien innerhalb deiner Organisation teilen. Auch Empfänger, die auf die Datei bereits zugreifen können, können diesen Link für einen einfachen Zugriff nutzen.",
|
||||
"Share files with others outside your organization via public links and email addresses. You can also share to {productName} accounts on other instances using their federated cloud ID." : "Dateien über öffentliche Links und E-Mail-Adressen mit anderen außerhalb deiner Organisation teilen. Du kannst {productName}-Konten auch auf anderen Instanzen mithilfe deiner Federated-Cloud-ID teilen.",
|
||||
"Share files within your organization. Recipients who can already view the file can also use this link for easy access." : "Dateien innerhalb Ihrer Organisation teilen. Auch Empfänger, die auf die Datei bereits zugreifen können, können diesen Link für einen einfachen Zugriff nutzen.",
|
||||
"Share files with others outside your organization via public links and email addresses. You can also share to {productName} accounts on other instances using their federated cloud ID." : "Dateien über öffentliche Links und E-Mail-Adressen mit anderen außerhalb Ihrer Organisation teilen. Sie können {productName}-Konten auch auf anderen Instanzen mithilfe ihrer Federated-Cloud-ID teilen.",
|
||||
"Shares from apps or other sources which are not included in internal or external shares." : "Freigaben aus Apps oder anderen Quellen, die nicht in internen oder externen Freigaben enthalten sind.",
|
||||
"Type names, teams, federated cloud IDs" : "Namen, Teams oder Federated-Cloud-IDs eingeben",
|
||||
"Type names or teams" : "Namen oder Federated-Cloud-IDs eingeben",
|
||||
@@ -401,7 +402,6 @@
|
||||
"Share not found" : "Freigabe nicht gefunden",
|
||||
"Back to %s" : "Zurück zu %s",
|
||||
"Add to your Nextcloud" : "Zu deiner Nextcloud hinzufügen",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : "Freigabe von %s fehlgeschlagen, da das Backend keine ScienceMesh-Freigaben unterstützt",
|
||||
"Link copied to clipboard" : "Link wurde in die Zwischenablage kopiert",
|
||||
"Copy to clipboard" : "In die Zwischenablage kopieren",
|
||||
"Copy internal link to clipboard" : "Internen Link in die Zwischenablage kopieren",
|
||||
|
||||
@@ -77,6 +77,7 @@ OC.L10N.register(
|
||||
"You cannot share to a Team if the app is not enabled" : "Sie können nichts mit einem Team teilen, wenn die App nicht aktiviert ist",
|
||||
"Please specify a valid team" : "Bitte ein gültiges Team angeben",
|
||||
"Sharing %s failed because the back end does not support room shares" : "Freigabe von %s fehlgeschlagen, da das Backend die Freigabe von Räumen nicht unterstützt",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : "Freigabe von %s fehlgeschlagen, da das Backend keine ScienceMesh-Freigaben unterstützt",
|
||||
"Unknown share type" : "Unbekannter Freigabetyp",
|
||||
"Not a directory" : "Kein Verzeichnis",
|
||||
"Could not lock node" : "Knotenpunkt konnte nicht gesperrt werden",
|
||||
@@ -403,7 +404,6 @@ OC.L10N.register(
|
||||
"Share not found" : "Freigabe nicht gefunden",
|
||||
"Back to %s" : "Zurück zu %s",
|
||||
"Add to your Nextcloud" : "Zu Ihrer Nextcloud hinzufügen",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : "Freigabe von %s fehlgeschlagen, da das Backend keine ScienceMesh-Freigaben unterstützt",
|
||||
"Link copied to clipboard" : "Link wurde in die Zwischenablage kopiert",
|
||||
"Copy to clipboard" : "In die Zwischenablage kopieren",
|
||||
"Copy internal link to clipboard" : "Internen Link in die Zwischenablage kopieren",
|
||||
|
||||
@@ -75,6 +75,7 @@
|
||||
"You cannot share to a Team if the app is not enabled" : "Sie können nichts mit einem Team teilen, wenn die App nicht aktiviert ist",
|
||||
"Please specify a valid team" : "Bitte ein gültiges Team angeben",
|
||||
"Sharing %s failed because the back end does not support room shares" : "Freigabe von %s fehlgeschlagen, da das Backend die Freigabe von Räumen nicht unterstützt",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : "Freigabe von %s fehlgeschlagen, da das Backend keine ScienceMesh-Freigaben unterstützt",
|
||||
"Unknown share type" : "Unbekannter Freigabetyp",
|
||||
"Not a directory" : "Kein Verzeichnis",
|
||||
"Could not lock node" : "Knotenpunkt konnte nicht gesperrt werden",
|
||||
@@ -401,7 +402,6 @@
|
||||
"Share not found" : "Freigabe nicht gefunden",
|
||||
"Back to %s" : "Zurück zu %s",
|
||||
"Add to your Nextcloud" : "Zu Ihrer Nextcloud hinzufügen",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : "Freigabe von %s fehlgeschlagen, da das Backend keine ScienceMesh-Freigaben unterstützt",
|
||||
"Link copied to clipboard" : "Link wurde in die Zwischenablage kopiert",
|
||||
"Copy to clipboard" : "In die Zwischenablage kopieren",
|
||||
"Copy internal link to clipboard" : "Internen Link in die Zwischenablage kopieren",
|
||||
|
||||
@@ -77,6 +77,7 @@ OC.L10N.register(
|
||||
"You cannot share to a Team if the app is not enabled" : "Δεν μπορείτε να διαμοιραστείτε σε Ομάδα εάν η εφαρμογή δεν είναι ενεργοποιημένη",
|
||||
"Please specify a valid team" : "Παρακαλώ καθορίστε μια έγκυρη ομάδα",
|
||||
"Sharing %s failed because the back end does not support room shares" : "Διαμοιρασμός %s απέτυχε επειδή ο εξυπηρετητής δεν επιτρέπει διαμοιρασμούς δωματίων",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : "Ο διαμοιρασμός %s απέτυχε επειδή το σύστημα υποστήριξης δεν υποστηρίζει μετοχές ScienceMesh",
|
||||
"Unknown share type" : "Άγνωστος τύπος διαμοιρασμού",
|
||||
"Not a directory" : "Δεν είναι κατάλογος",
|
||||
"Could not lock node" : "Δεν ήταν δυνατό να κλειδώσει ο κόμβος",
|
||||
@@ -401,7 +402,6 @@ OC.L10N.register(
|
||||
"Share not found" : "Δεν βρέθηκε το κονόχρηστο",
|
||||
"Back to %s" : "Πίσω στο %s",
|
||||
"Add to your Nextcloud" : "Προσθήκη στο Nextcloud σου",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : "Ο διαμοιρασμός %s απέτυχε επειδή το σύστημα υποστήριξης δεν υποστηρίζει μετοχές ScienceMesh",
|
||||
"Link copied to clipboard" : "Ο σύνδεσμος αντιγράφηκε στο πρόχειρο",
|
||||
"Copy to clipboard" : "Αντιγραφή στο πρόχειρο",
|
||||
"Copy internal link to clipboard" : "Αντιγραφή εσωτερικού συνδέσμου στο πρόχειρο",
|
||||
|
||||
@@ -75,6 +75,7 @@
|
||||
"You cannot share to a Team if the app is not enabled" : "Δεν μπορείτε να διαμοιραστείτε σε Ομάδα εάν η εφαρμογή δεν είναι ενεργοποιημένη",
|
||||
"Please specify a valid team" : "Παρακαλώ καθορίστε μια έγκυρη ομάδα",
|
||||
"Sharing %s failed because the back end does not support room shares" : "Διαμοιρασμός %s απέτυχε επειδή ο εξυπηρετητής δεν επιτρέπει διαμοιρασμούς δωματίων",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : "Ο διαμοιρασμός %s απέτυχε επειδή το σύστημα υποστήριξης δεν υποστηρίζει μετοχές ScienceMesh",
|
||||
"Unknown share type" : "Άγνωστος τύπος διαμοιρασμού",
|
||||
"Not a directory" : "Δεν είναι κατάλογος",
|
||||
"Could not lock node" : "Δεν ήταν δυνατό να κλειδώσει ο κόμβος",
|
||||
@@ -399,7 +400,6 @@
|
||||
"Share not found" : "Δεν βρέθηκε το κονόχρηστο",
|
||||
"Back to %s" : "Πίσω στο %s",
|
||||
"Add to your Nextcloud" : "Προσθήκη στο Nextcloud σου",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : "Ο διαμοιρασμός %s απέτυχε επειδή το σύστημα υποστήριξης δεν υποστηρίζει μετοχές ScienceMesh",
|
||||
"Link copied to clipboard" : "Ο σύνδεσμος αντιγράφηκε στο πρόχειρο",
|
||||
"Copy to clipboard" : "Αντιγραφή στο πρόχειρο",
|
||||
"Copy internal link to clipboard" : "Αντιγραφή εσωτερικού συνδέσμου στο πρόχειρο",
|
||||
|
||||
@@ -77,6 +77,7 @@ OC.L10N.register(
|
||||
"You cannot share to a Team if the app is not enabled" : "You cannot share to a Team if the app is not enabled",
|
||||
"Please specify a valid team" : "Please specify a valid team",
|
||||
"Sharing %s failed because the back end does not support room shares" : "Sharing %s failed because the back end does not support room shares",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : "Sharing %s failed because the back end does not support ScienceMesh shares",
|
||||
"Unknown share type" : "Unknown share type",
|
||||
"Not a directory" : "Not a directory",
|
||||
"Could not lock node" : "Could not lock node",
|
||||
@@ -403,7 +404,6 @@ OC.L10N.register(
|
||||
"Share not found" : "Share not found",
|
||||
"Back to %s" : "Back to %s",
|
||||
"Add to your Nextcloud" : "Add to your Nextcloud",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : "Sharing %s failed because the back end does not support ScienceMesh shares",
|
||||
"Link copied to clipboard" : "Link copied to clipboard",
|
||||
"Copy to clipboard" : "Copy to clipboard",
|
||||
"Copy internal link to clipboard" : "Copy internal link to clipboard",
|
||||
|
||||
@@ -75,6 +75,7 @@
|
||||
"You cannot share to a Team if the app is not enabled" : "You cannot share to a Team if the app is not enabled",
|
||||
"Please specify a valid team" : "Please specify a valid team",
|
||||
"Sharing %s failed because the back end does not support room shares" : "Sharing %s failed because the back end does not support room shares",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : "Sharing %s failed because the back end does not support ScienceMesh shares",
|
||||
"Unknown share type" : "Unknown share type",
|
||||
"Not a directory" : "Not a directory",
|
||||
"Could not lock node" : "Could not lock node",
|
||||
@@ -401,7 +402,6 @@
|
||||
"Share not found" : "Share not found",
|
||||
"Back to %s" : "Back to %s",
|
||||
"Add to your Nextcloud" : "Add to your Nextcloud",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : "Sharing %s failed because the back end does not support ScienceMesh shares",
|
||||
"Link copied to clipboard" : "Link copied to clipboard",
|
||||
"Copy to clipboard" : "Copy to clipboard",
|
||||
"Copy internal link to clipboard" : "Copy internal link to clipboard",
|
||||
|
||||
@@ -77,6 +77,7 @@ OC.L10N.register(
|
||||
"You cannot share to a Team if the app is not enabled" : "No puede compartir a un equipo si la aplicación no está habilitada",
|
||||
"Please specify a valid team" : "Por favor, especifique un equipo válido",
|
||||
"Sharing %s failed because the back end does not support room shares" : "Compartir %s ha fallado porque el backend no soporta habitaciones compartidas",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : "Compartir %s ha fallado porque el backend no soporta recursos compartidos de ScienceMesh",
|
||||
"Unknown share type" : "Tipo de recurso compartido desconocido",
|
||||
"Not a directory" : "No es un directorio",
|
||||
"Could not lock node" : "No se ha podido bloquear el nodo",
|
||||
@@ -401,7 +402,6 @@ OC.L10N.register(
|
||||
"Share not found" : "Recurso compartido no encontrado",
|
||||
"Back to %s" : "Volver a %s",
|
||||
"Add to your Nextcloud" : "Añadir a tu Nextcloud",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : "Compartir %s ha fallado porque el backend no soporta recursos compartidos de ScienceMesh",
|
||||
"Link copied to clipboard" : "Enlace copiado al portapapeles",
|
||||
"Copy to clipboard" : "Copiar al portapapeles",
|
||||
"Copy internal link to clipboard" : "Copiar enlace interno al portapapeles",
|
||||
|
||||
@@ -75,6 +75,7 @@
|
||||
"You cannot share to a Team if the app is not enabled" : "No puede compartir a un equipo si la aplicación no está habilitada",
|
||||
"Please specify a valid team" : "Por favor, especifique un equipo válido",
|
||||
"Sharing %s failed because the back end does not support room shares" : "Compartir %s ha fallado porque el backend no soporta habitaciones compartidas",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : "Compartir %s ha fallado porque el backend no soporta recursos compartidos de ScienceMesh",
|
||||
"Unknown share type" : "Tipo de recurso compartido desconocido",
|
||||
"Not a directory" : "No es un directorio",
|
||||
"Could not lock node" : "No se ha podido bloquear el nodo",
|
||||
@@ -399,7 +400,6 @@
|
||||
"Share not found" : "Recurso compartido no encontrado",
|
||||
"Back to %s" : "Volver a %s",
|
||||
"Add to your Nextcloud" : "Añadir a tu Nextcloud",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : "Compartir %s ha fallado porque el backend no soporta recursos compartidos de ScienceMesh",
|
||||
"Link copied to clipboard" : "Enlace copiado al portapapeles",
|
||||
"Copy to clipboard" : "Copiar al portapapeles",
|
||||
"Copy internal link to clipboard" : "Copiar enlace interno al portapapeles",
|
||||
|
||||
@@ -71,6 +71,7 @@ OC.L10N.register(
|
||||
"Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Error al compartir %1$s porque el servidor no permite comparticiones del tipo %2$s",
|
||||
"Please specify a valid federated group ID" : "Por favor, especifica un ID de grupo federado válido",
|
||||
"Sharing %s failed because the back end does not support room shares" : "Error al compartir %s porque el servidor no admite comparticiones de salas",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : "Error al compartir %s porque el servidor no admite comparticiones de ScienceMesh",
|
||||
"Unknown share type" : "Tipo de elemento compartido desconocido",
|
||||
"Not a directory" : "No es una carpeta",
|
||||
"Could not lock node" : "No se pudo bloquear el nodo",
|
||||
@@ -224,7 +225,6 @@ OC.L10N.register(
|
||||
"Share not found" : "No se encontró el elemento compartido",
|
||||
"Back to %s" : "Volver a %s",
|
||||
"Add to your Nextcloud" : "Agregar a tu Nextcloud",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : "Error al compartir %s porque el servidor no admite comparticiones de ScienceMesh",
|
||||
"Link copied to clipboard" : "Enlace copiado al portapapeles",
|
||||
"Copy to clipboard" : "Copiar al portapapeles",
|
||||
"Copy internal link to clipboard" : "Copiar enlace interno al portapapeles",
|
||||
|
||||
@@ -69,6 +69,7 @@
|
||||
"Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Error al compartir %1$s porque el servidor no permite comparticiones del tipo %2$s",
|
||||
"Please specify a valid federated group ID" : "Por favor, especifica un ID de grupo federado válido",
|
||||
"Sharing %s failed because the back end does not support room shares" : "Error al compartir %s porque el servidor no admite comparticiones de salas",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : "Error al compartir %s porque el servidor no admite comparticiones de ScienceMesh",
|
||||
"Unknown share type" : "Tipo de elemento compartido desconocido",
|
||||
"Not a directory" : "No es una carpeta",
|
||||
"Could not lock node" : "No se pudo bloquear el nodo",
|
||||
@@ -222,7 +223,6 @@
|
||||
"Share not found" : "No se encontró el elemento compartido",
|
||||
"Back to %s" : "Volver a %s",
|
||||
"Add to your Nextcloud" : "Agregar a tu Nextcloud",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : "Error al compartir %s porque el servidor no admite comparticiones de ScienceMesh",
|
||||
"Link copied to clipboard" : "Enlace copiado al portapapeles",
|
||||
"Copy to clipboard" : "Copiar al portapapeles",
|
||||
"Copy internal link to clipboard" : "Copiar enlace interno al portapapeles",
|
||||
|
||||
@@ -77,6 +77,7 @@ OC.L10N.register(
|
||||
"You cannot share to a Team if the app is not enabled" : "No puede compartir a un equipo si la aplicación no está habilitada",
|
||||
"Please specify a valid team" : "Por favor, especifique un equipo válido",
|
||||
"Sharing %s failed because the back end does not support room shares" : "Compartir %s falló porque el servidor no soporta salas compartidas",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : "Compartir %s falló porque el servidor no soporta recursos compartidos de ScienceMesh",
|
||||
"Unknown share type" : "Tipo de elemento compartido desconocido",
|
||||
"Not a directory" : "No es una carpeta",
|
||||
"Could not lock node" : "No se pudo bloquear el nodo",
|
||||
@@ -320,7 +321,6 @@ OC.L10N.register(
|
||||
"Share not found" : "No se encontró el elemento compartido",
|
||||
"Back to %s" : "Volver a %s",
|
||||
"Add to your Nextcloud" : "Agregar a tu Nextcloud",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : "Compartir %s falló porque el servidor no soporta recursos compartidos de ScienceMesh",
|
||||
"Link copied to clipboard" : "Enlace copiado al portapapeles",
|
||||
"Copy to clipboard" : "Copiar al portapapeles",
|
||||
"Copy internal link to clipboard" : "Copiar enlace interno al portapapeles",
|
||||
|
||||
@@ -75,6 +75,7 @@
|
||||
"You cannot share to a Team if the app is not enabled" : "No puede compartir a un equipo si la aplicación no está habilitada",
|
||||
"Please specify a valid team" : "Por favor, especifique un equipo válido",
|
||||
"Sharing %s failed because the back end does not support room shares" : "Compartir %s falló porque el servidor no soporta salas compartidas",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : "Compartir %s falló porque el servidor no soporta recursos compartidos de ScienceMesh",
|
||||
"Unknown share type" : "Tipo de elemento compartido desconocido",
|
||||
"Not a directory" : "No es una carpeta",
|
||||
"Could not lock node" : "No se pudo bloquear el nodo",
|
||||
@@ -318,7 +319,6 @@
|
||||
"Share not found" : "No se encontró el elemento compartido",
|
||||
"Back to %s" : "Volver a %s",
|
||||
"Add to your Nextcloud" : "Agregar a tu Nextcloud",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : "Compartir %s falló porque el servidor no soporta recursos compartidos de ScienceMesh",
|
||||
"Link copied to clipboard" : "Enlace copiado al portapapeles",
|
||||
"Copy to clipboard" : "Copiar al portapapeles",
|
||||
"Copy internal link to clipboard" : "Copiar enlace interno al portapapeles",
|
||||
|
||||
@@ -77,6 +77,7 @@ OC.L10N.register(
|
||||
"You cannot share to a Team if the app is not enabled" : "Sa ei saa jagada tiimiga, kui see rakendus pole lubatud",
|
||||
"Please specify a valid team" : "Palun määratle korrektne tiim",
|
||||
"Sharing %s failed because the back end does not support room shares" : "„%s“ jagamine ei õnnestunud, sest taustateenus ei toeta jututubadesse jagamist",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : "„%s“ jagamine ei õnnestunud, sest taustateenus ei toeta ScienceMeshi meedia jagamist",
|
||||
"Unknown share type" : "Tundmatu jagamise tüüp",
|
||||
"Not a directory" : "Ei ole kaust",
|
||||
"Could not lock node" : "Sõlme lukustamine ei õnnestunud",
|
||||
@@ -403,7 +404,6 @@ OC.L10N.register(
|
||||
"Share not found" : "Jagamist ei leidu",
|
||||
"Back to %s" : "Tagasi siia: %s",
|
||||
"Add to your Nextcloud" : "Lisa oma Nextcloudi",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : "„%s“ jagamine ei õnnestunud, sest taustateenus ei toeta ScienceMeshi meedia jagamist",
|
||||
"Link copied to clipboard" : "Link on lõikelauale kopeeritud",
|
||||
"Copy to clipboard" : "Kopeeri lõikepuhvrisse",
|
||||
"Copy internal link to clipboard" : "Kopeeri sisemine link lõikelauale",
|
||||
|
||||
@@ -75,6 +75,7 @@
|
||||
"You cannot share to a Team if the app is not enabled" : "Sa ei saa jagada tiimiga, kui see rakendus pole lubatud",
|
||||
"Please specify a valid team" : "Palun määratle korrektne tiim",
|
||||
"Sharing %s failed because the back end does not support room shares" : "„%s“ jagamine ei õnnestunud, sest taustateenus ei toeta jututubadesse jagamist",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : "„%s“ jagamine ei õnnestunud, sest taustateenus ei toeta ScienceMeshi meedia jagamist",
|
||||
"Unknown share type" : "Tundmatu jagamise tüüp",
|
||||
"Not a directory" : "Ei ole kaust",
|
||||
"Could not lock node" : "Sõlme lukustamine ei õnnestunud",
|
||||
@@ -401,7 +402,6 @@
|
||||
"Share not found" : "Jagamist ei leidu",
|
||||
"Back to %s" : "Tagasi siia: %s",
|
||||
"Add to your Nextcloud" : "Lisa oma Nextcloudi",
|
||||
"Sharing %s failed because the back end does not support ScienceMesh shares" : "„%s“ jagamine ei õnnestunud, sest taustateenus ei toeta ScienceMeshi meedia jagamist",
|
||||
"Link copied to clipboard" : "Link on lõikelauale kopeeritud",
|
||||
"Copy to clipboard" : "Kopeeri lõikepuhvrisse",
|
||||
"Copy internal link to clipboard" : "Kopeeri sisemine link lõikelauale",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user