diff options
Diffstat (limited to 'apps')
88 files changed, 590 insertions, 463 deletions
diff --git a/apps/dav/appinfo/v1/carddav.php b/apps/dav/appinfo/v1/carddav.php index bcd66e47090..415a5c9634a 100644 --- a/apps/dav/appinfo/v1/carddav.php +++ b/apps/dav/appinfo/v1/carddav.php @@ -63,6 +63,7 @@ $cardDavBackend = new CardDavBackend( Server::get(IUserManager::class), Server::get(IEventDispatcher::class), Server::get(\OCA\DAV\CardDAV\Sharing\Backend::class), + Server::get(IConfig::class), ); $debugging = Server::get(IConfig::class)->getSystemValue('debug', false); diff --git a/apps/dav/l10n/da.js b/apps/dav/l10n/da.js index c99b8d10b6e..1368b899919 100644 --- a/apps/dav/l10n/da.js +++ b/apps/dav/l10n/da.js @@ -80,7 +80,7 @@ OC.L10N.register( "_In a month on %1$s for the entire day_::_In %n months on %1$s for the entire day_" : ["I en måned på %1$s for hele dagen","Om %n måneder den %1$s for hele dagen"], "_In a year on %1$s for the entire day_::_In %n years on %1$s for the entire day_" : ["I et år på %1$s for hele dagen","Om %n år den %1$s for hele dagen"], "In the past on %1$s between %2$s - %3$s" : "Tidligere den %1$s mellem %2$s - %3$s", - "_In a minute on %1$s between %2$s - %3$s_::_In %n minutes on %1$s between %2$s - %3$s_" : ["I et minut på %1$s mellem% %2$s - %3$s","Om %n minutter den %1$s mellem %2$s - %3$s"], + "_In a minute on %1$s between %2$s - %3$s_::_In %n minutes on %1$s between %2$s - %3$s_" : ["I et minut på %1$s mellem %2$s - %3$s","Om %n minutter den %1$s mellem %2$s - %3$s"], "_In a hour on %1$s between %2$s - %3$s_::_In %n hours on %1$s between %2$s - %3$s_" : ["I en time på %1$s mellem %2$s - %3$s","Om %n timer den %1$s mellem %2$s - %3$s"], "_In a day on %1$s between %2$s - %3$s_::_In %n days on %1$s between %2$s - %3$s_" : ["I en dag på %1$s mellem %2$s - %3$s","Om %n dage den %1$s mellem %2$s - %3$s"], "_In a week on %1$s between %2$s - %3$s_::_In %n weeks on %1$s between %2$s - %3$s_" : ["I en uge på %1$s mellem %2$s - %3$s","Om %n uger den %1$s mellem %2$s - %3$s"], diff --git a/apps/dav/l10n/da.json b/apps/dav/l10n/da.json index fa934db0daa..723ebc26b7d 100644 --- a/apps/dav/l10n/da.json +++ b/apps/dav/l10n/da.json @@ -78,7 +78,7 @@ "_In a month on %1$s for the entire day_::_In %n months on %1$s for the entire day_" : ["I en måned på %1$s for hele dagen","Om %n måneder den %1$s for hele dagen"], "_In a year on %1$s for the entire day_::_In %n years on %1$s for the entire day_" : ["I et år på %1$s for hele dagen","Om %n år den %1$s for hele dagen"], "In the past on %1$s between %2$s - %3$s" : "Tidligere den %1$s mellem %2$s - %3$s", - "_In a minute on %1$s between %2$s - %3$s_::_In %n minutes on %1$s between %2$s - %3$s_" : ["I et minut på %1$s mellem% %2$s - %3$s","Om %n minutter den %1$s mellem %2$s - %3$s"], + "_In a minute on %1$s between %2$s - %3$s_::_In %n minutes on %1$s between %2$s - %3$s_" : ["I et minut på %1$s mellem %2$s - %3$s","Om %n minutter den %1$s mellem %2$s - %3$s"], "_In a hour on %1$s between %2$s - %3$s_::_In %n hours on %1$s between %2$s - %3$s_" : ["I en time på %1$s mellem %2$s - %3$s","Om %n timer den %1$s mellem %2$s - %3$s"], "_In a day on %1$s between %2$s - %3$s_::_In %n days on %1$s between %2$s - %3$s_" : ["I en dag på %1$s mellem %2$s - %3$s","Om %n dage den %1$s mellem %2$s - %3$s"], "_In a week on %1$s between %2$s - %3$s_::_In %n weeks on %1$s between %2$s - %3$s_" : ["I en uge på %1$s mellem %2$s - %3$s","Om %n uger den %1$s mellem %2$s - %3$s"], diff --git a/apps/dav/lib/CalDAV/Schedule/IMipService.php b/apps/dav/lib/CalDAV/Schedule/IMipService.php index f7054eb2d34..54c0bc31849 100644 --- a/apps/dav/lib/CalDAV/Schedule/IMipService.php +++ b/apps/dav/lib/CalDAV/Schedule/IMipService.php @@ -1110,7 +1110,7 @@ class IMipService { $sequence = $iTipMessage->sequence; $recurrenceId = isset($vevent->{'RECURRENCE-ID'}) ? $vevent->{'RECURRENCE-ID'}->serialize() : null; - $uid = $vevent->{'UID'}; + $uid = $vevent->{'UID'}?->getValue(); $query = $this->db->getQueryBuilder(); $query->insert('calendar_invitations') diff --git a/apps/dav/lib/CardDAV/AddressBook.php b/apps/dav/lib/CardDAV/AddressBook.php index d2391880585..4d30d507a7d 100644 --- a/apps/dav/lib/CardDAV/AddressBook.php +++ b/apps/dav/lib/CardDAV/AddressBook.php @@ -8,7 +8,6 @@ namespace OCA\DAV\CardDAV; use OCA\DAV\DAV\Sharing\IShareable; -use OCA\DAV\Exception\UnsupportedLimitOnInitialSyncException; use OCP\DB\Exception; use OCP\IL10N; use OCP\Server; @@ -234,9 +233,6 @@ class AddressBook extends \Sabre\CardDAV\AddressBook implements IShareable, IMov } public function getChanges($syncToken, $syncLevel, $limit = null) { - if (!$syncToken && $limit) { - throw new UnsupportedLimitOnInitialSyncException(); - } return parent::getChanges($syncToken, $syncLevel, $limit); } diff --git a/apps/dav/lib/CardDAV/AddressBookImpl.php b/apps/dav/lib/CardDAV/AddressBookImpl.php index 6bb8e24f628..ae77498539b 100644 --- a/apps/dav/lib/CardDAV/AddressBookImpl.php +++ b/apps/dav/lib/CardDAV/AddressBookImpl.php @@ -152,6 +152,10 @@ class AddressBookImpl implements IAddressBookEnabled { $permissions = $this->addressBook->getACL(); $result = 0; foreach ($permissions as $permission) { + if ($this->addressBookInfo['principaluri'] !== $permission['principal']) { + continue; + } + switch ($permission['privilege']) { case '{DAV:}read': $result |= Constants::PERMISSION_READ; diff --git a/apps/dav/lib/CardDAV/CardDavBackend.php b/apps/dav/lib/CardDAV/CardDavBackend.php index 06bd8d8ee2c..a78686eb61d 100644 --- a/apps/dav/lib/CardDAV/CardDavBackend.php +++ b/apps/dav/lib/CardDAV/CardDavBackend.php @@ -23,6 +23,7 @@ use OCP\AppFramework\Db\TTransactional; use OCP\DB\Exception; use OCP\DB\QueryBuilder\IQueryBuilder; use OCP\EventDispatcher\IEventDispatcher; +use OCP\IConfig; use OCP\IDBConnection; use OCP\IUserManager; use PDO; @@ -59,6 +60,7 @@ class CardDavBackend implements BackendInterface, SyncSupport { private IUserManager $userManager, private IEventDispatcher $dispatcher, private Sharing\Backend $sharingBackend, + private IConfig $config, ) { } @@ -851,6 +853,8 @@ class CardDavBackend implements BackendInterface, SyncSupport { * @return array */ public function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit = null) { + $maxLimit = $this->config->getSystemValueInt('carddav_sync_request_truncation', 2500); + $limit = ($limit === null) ? $maxLimit : min($limit, $maxLimit); // Current synctoken return $this->atomic(function () use ($addressBookId, $syncToken, $syncLevel, $limit) { $qb = $this->db->getQueryBuilder(); @@ -873,10 +877,35 @@ class CardDavBackend implements BackendInterface, SyncSupport { 'modified' => [], 'deleted' => [], ]; - - if ($syncToken) { + if (str_starts_with($syncToken, 'init_')) { + $syncValues = explode('_', $syncToken); + $lastID = $syncValues[1]; + $initialSyncToken = $syncValues[2]; $qb = $this->db->getQueryBuilder(); - $qb->select('uri', 'operation') + $qb->select('id', 'uri') + ->from('cards') + ->where( + $qb->expr()->andX( + $qb->expr()->eq('addressbookid', $qb->createNamedParameter($addressBookId)), + $qb->expr()->gt('id', $qb->createNamedParameter($lastID))) + )->orderBy('id') + ->setMaxResults($limit); + $stmt = $qb->executeQuery(); + $values = $stmt->fetchAll(\PDO::FETCH_ASSOC); + $stmt->closeCursor(); + if (count($values) === 0) { + $result['syncToken'] = $initialSyncToken; + $result['result_truncated'] = false; + $result['added'] = []; + } else { + $lastID = $values[array_key_last($values)]['id']; + $result['added'] = array_column($values, 'uri'); + $result['syncToken'] = count($result['added']) >= $limit ? "init_{$lastID}_$initialSyncToken" : $initialSyncToken ; + $result['result_truncated'] = count($result['added']) >= $limit; + } + } elseif ($syncToken) { + $qb = $this->db->getQueryBuilder(); + $qb->select('uri', 'operation', 'synctoken') ->from('addressbookchanges') ->where( $qb->expr()->andX( @@ -886,22 +915,31 @@ class CardDavBackend implements BackendInterface, SyncSupport { ) )->orderBy('synctoken'); - if (is_int($limit) && $limit > 0) { + if ($limit > 0) { $qb->setMaxResults($limit); } // Fetching all changes $stmt = $qb->executeQuery(); + $rowCount = $stmt->rowCount(); $changes = []; + $highestSyncToken = 0; // This loop ensures that any duplicates are overwritten, only the // last change on a node is relevant. while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) { $changes[$row['uri']] = $row['operation']; + $highestSyncToken = $row['synctoken']; } + $stmt->closeCursor(); + // No changes found, use current token + if (empty($changes)) { + $result['syncToken'] = $currentToken; + } + foreach ($changes as $uri => $operation) { switch ($operation) { case 1: @@ -915,16 +953,43 @@ class CardDavBackend implements BackendInterface, SyncSupport { break; } } + + /* + * The synctoken in oc_addressbooks is always the highest synctoken in oc_addressbookchanges for a given addressbook plus one (see addChange). + * + * For truncated results, it is expected that we return the highest token from the response, so the client can continue from the latest change. + * + * For non-truncated results, it is expected to return the currentToken. If we return the highest token, as with truncated results, the client will always think it is one change behind. + * + * Therefore, we differentiate between truncated and non-truncated results when returning the synctoken. + */ + if ($rowCount === $limit && $highestSyncToken < $currentToken) { + $result['syncToken'] = $highestSyncToken; + $result['result_truncated'] = true; + } } else { $qb = $this->db->getQueryBuilder(); - $qb->select('uri') + $qb->select('id', 'uri') ->from('cards') ->where( $qb->expr()->eq('addressbookid', $qb->createNamedParameter($addressBookId)) ); // No synctoken supplied, this is the initial sync. + $qb->setMaxResults($limit); $stmt = $qb->executeQuery(); - $result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN); + $values = $stmt->fetchAll(\PDO::FETCH_ASSOC); + if (empty($values)) { + $result['added'] = []; + return $result; + } + $lastID = $values[array_key_last($values)]['id']; + if (count($values) >= $limit) { + $result['syncToken'] = 'init_' . $lastID . '_' . $currentToken; + $result['result_truncated'] = true; + } + + $result['added'] = array_column($values, 'uri'); + $stmt->closeCursor(); } return $result; diff --git a/apps/dav/lib/CardDAV/SyncService.php b/apps/dav/lib/CardDAV/SyncService.php index 4a75f8ced6c..e6da3ed5923 100644 --- a/apps/dav/lib/CardDAV/SyncService.php +++ b/apps/dav/lib/CardDAV/SyncService.php @@ -22,6 +22,7 @@ use Psr\Log\LoggerInterface; use Sabre\DAV\Xml\Response\MultiStatus; use Sabre\DAV\Xml\Service; use Sabre\VObject\Reader; +use Sabre\Xml\ParseException; use function is_null; class SyncService { @@ -43,9 +44,10 @@ class SyncService { } /** + * @psalm-return list{0: ?string, 1: boolean} * @throws \Exception */ - public function syncRemoteAddressBook(string $url, string $userName, string $addressBookUrl, string $sharedSecret, ?string $syncToken, string $targetBookHash, string $targetPrincipal, array $targetProperties): string { + public function syncRemoteAddressBook(string $url, string $userName, string $addressBookUrl, string $sharedSecret, ?string $syncToken, string $targetBookHash, string $targetPrincipal, array $targetProperties): array { // 1. create addressbook $book = $this->ensureSystemAddressBookExists($targetPrincipal, $targetBookHash, $targetProperties); $addressBookId = $book['id']; @@ -83,7 +85,10 @@ class SyncService { } } - return $response['token']; + return [ + $response['token'], + $response['truncated'], + ]; } /** @@ -127,7 +132,7 @@ class SyncService { private function prepareUri(string $host, string $path): string { /* - * The trailing slash is important for merging the uris together. + * The trailing slash is important for merging the uris. * * $host is stored in oc_trusted_servers.url and usually without a trailing slash. * @@ -158,7 +163,9 @@ class SyncService { } /** + * @return array{response: array<string, array<array-key, mixed>>, token: ?string, truncated: bool} * @throws ClientExceptionInterface + * @throws ParseException */ protected function requestSyncReport(string $url, string $userName, string $addressBookUrl, string $sharedSecret, ?string $syncToken): array { $client = $this->clientService->newClient(); @@ -181,7 +188,7 @@ class SyncService { $body = $response->getBody(); assert(is_string($body)); - return $this->parseMultiStatus($body); + return $this->parseMultiStatus($body, $addressBookUrl); } protected function download(string $url, string $userName, string $sharedSecret, string $resourcePath): string { @@ -219,22 +226,50 @@ class SyncService { } /** - * @param string $body - * @return array - * @throws \Sabre\Xml\ParseException + * @return array{response: array<string, array<array-key, mixed>>, token: ?string, truncated: bool} + * @throws ParseException */ - private function parseMultiStatus($body) { - $xml = new Service(); - + private function parseMultiStatus(string $body, string $addressBookUrl): array { /** @var MultiStatus $multiStatus */ - $multiStatus = $xml->expect('{DAV:}multistatus', $body); + $multiStatus = (new Service())->expect('{DAV:}multistatus', $body); $result = []; + $truncated = false; + foreach ($multiStatus->getResponses() as $response) { - $result[$response->getHref()] = $response->getResponseProperties(); + $href = $response->getHref(); + if ($response->getHttpStatus() === '507' && $this->isResponseForRequestUri($href, $addressBookUrl)) { + $truncated = true; + } else { + $result[$response->getHref()] = $response->getResponseProperties(); + } } - return ['response' => $result, 'token' => $multiStatus->getSyncToken()]; + return ['response' => $result, 'token' => $multiStatus->getSyncToken(), 'truncated' => $truncated]; + } + + /** + * Determines whether the provided response URI corresponds to the given request URI. + */ + private function isResponseForRequestUri(string $responseUri, string $requestUri): bool { + /* + * Example response uri: + * + * /remote.php/dav/addressbooks/system/system/system/ + * /cloud/remote.php/dav/addressbooks/system/system/system/ (when installed in a subdirectory) + * + * Example request uri: + * + * remote.php/dav/addressbooks/system/system/system + * + * References: + * https://github.com/nextcloud/3rdparty/blob/e0a509739b13820f0a62ff9cad5d0fede00e76ee/sabre/dav/lib/DAV/Sync/Plugin.php#L172-L174 + * https://github.com/nextcloud/server/blob/b40acb34a39592070d8455eb91c5364c07928c50/apps/federation/lib/SyncFederationAddressBooks.php#L41 + */ + return str_ends_with( + rtrim($responseUri, '/'), + rtrim($requestUri, '/') + ); } /** diff --git a/apps/dav/lib/CardDAV/SystemAddressbook.php b/apps/dav/lib/CardDAV/SystemAddressbook.php index e0032044e70..912a2f1dcee 100644 --- a/apps/dav/lib/CardDAV/SystemAddressbook.php +++ b/apps/dav/lib/CardDAV/SystemAddressbook.php @@ -8,7 +8,6 @@ declare(strict_types=1); */ namespace OCA\DAV\CardDAV; -use OCA\DAV\Exception\UnsupportedLimitOnInitialSyncException; use OCA\Federation\TrustedServers; use OCP\Accounts\IAccountManager; use OCP\IConfig; @@ -212,14 +211,7 @@ class SystemAddressbook extends AddressBook { } return new Card($this->carddavBackend, $this->addressBookInfo, $obj); } - - /** - * @throws UnsupportedLimitOnInitialSyncException - */ public function getChanges($syncToken, $syncLevel, $limit = null) { - if (!$syncToken && $limit) { - throw new UnsupportedLimitOnInitialSyncException(); - } if (!$this->carddavBackend instanceof SyncSupport) { return null; diff --git a/apps/dav/lib/Connector/Sabre/File.php b/apps/dav/lib/Connector/Sabre/File.php index 218d38e1c4b..d2a71eb3e7b 100644 --- a/apps/dav/lib/Connector/Sabre/File.php +++ b/apps/dav/lib/Connector/Sabre/File.php @@ -204,6 +204,9 @@ class File extends Node implements IFile { } } + $lengthHeader = $this->request->getHeader('content-length'); + $expected = $lengthHeader !== '' ? (int)$lengthHeader : null; + if ($partStorage->instanceOfStorage(IWriteStreamStorage::class)) { $isEOF = false; $wrappedData = CallbackWrapper::wrap($data, null, null, null, null, function ($stream) use (&$isEOF): void { @@ -215,7 +218,7 @@ class File extends Node implements IFile { $count = -1; try { /** @var IWriteStreamStorage $partStorage */ - $count = $partStorage->writeStream($internalPartPath, $wrappedData); + $count = $partStorage->writeStream($internalPartPath, $wrappedData, $expected); } catch (GenericFileException $e) { $logger = Server::get(LoggerInterface::class); $logger->error('Error while writing stream to storage: ' . $e->getMessage(), ['exception' => $e, 'app' => 'webdav']); @@ -235,10 +238,7 @@ class File extends Node implements IFile { [$count, $result] = Files::streamCopy($data, $target, true); fclose($target); } - - $lengthHeader = $this->request->getHeader('content-length'); - $expected = $lengthHeader !== '' ? (int)$lengthHeader : -1; - if ($result === false && $expected >= 0) { + if ($result === false && $expected !== null) { throw new Exception( $this->l10n->t( 'Error while copying file to target location (copied: %1$s, expected filesize: %2$s)', @@ -253,7 +253,7 @@ class File extends Node implements IFile { // if content length is sent by client: // double check if the file was fully received // compare expected and actual size - if ($expected >= 0 + if ($expected !== null && $expected !== $count && $this->request->getMethod() === 'PUT' ) { diff --git a/apps/dav/lib/RootCollection.php b/apps/dav/lib/RootCollection.php index f1963c0ef01..870aa0d4540 100644 --- a/apps/dav/lib/RootCollection.php +++ b/apps/dav/lib/RootCollection.php @@ -132,6 +132,7 @@ class RootCollection extends SimpleCollection { ); $contactsSharingBackend = Server::get(\OCA\DAV\CardDAV\Sharing\Backend::class); + $config = Server::get(IConfig::class); $pluginManager = new PluginManager(\OC::$server, Server::get(IAppManager::class)); $usersCardDavBackend = new CardDavBackend( @@ -140,6 +141,7 @@ class RootCollection extends SimpleCollection { $userManager, $dispatcher, $contactsSharingBackend, + $config ); $usersAddressBookRoot = new AddressBookRoot($userPrincipalBackend, $usersCardDavBackend, $pluginManager, $userSession->getUser(), $groupManager, 'principals/users'); $usersAddressBookRoot->disableListing = $disableListing; @@ -150,6 +152,7 @@ class RootCollection extends SimpleCollection { $userManager, $dispatcher, $contactsSharingBackend, + $config ); $systemAddressBookRoot = new AddressBookRoot(new SystemPrincipalBackend(), $systemCardDavBackend, $pluginManager, $userSession->getUser(), $groupManager, 'principals/system'); $systemAddressBookRoot->disableListing = $disableListing; diff --git a/apps/dav/tests/unit/CardDAV/AddressBookImplTest.php b/apps/dav/tests/unit/CardDAV/AddressBookImplTest.php index f7daeb41cca..74699cf3925 100644 --- a/apps/dav/tests/unit/CardDAV/AddressBookImplTest.php +++ b/apps/dav/tests/unit/CardDAV/AddressBookImplTest.php @@ -241,14 +241,15 @@ class AddressBookImplTest extends TestCase { public static function dataTestGetPermissions(): array { return [ [[], 0], - [[['privilege' => '{DAV:}read']], 1], - [[['privilege' => '{DAV:}write']], 6], - [[['privilege' => '{DAV:}all']], 31], - [[['privilege' => '{DAV:}read'],['privilege' => '{DAV:}write']], 7], - [[['privilege' => '{DAV:}read'],['privilege' => '{DAV:}all']], 31], - [[['privilege' => '{DAV:}all'],['privilege' => '{DAV:}write']], 31], - [[['privilege' => '{DAV:}read'],['privilege' => '{DAV:}write'],['privilege' => '{DAV:}all']], 31], - [[['privilege' => '{DAV:}all'],['privilege' => '{DAV:}read'],['privilege' => '{DAV:}write']], 31], + [[['privilege' => '{DAV:}read', 'principal' => 'principals/system/system']], 1], + [[['privilege' => '{DAV:}read', 'principal' => 'principals/system/system'], ['privilege' => '{DAV:}write', 'principal' => 'principals/someone/else']], 1], + [[['privilege' => '{DAV:}write', 'principal' => 'principals/system/system']], 6], + [[['privilege' => '{DAV:}all', 'principal' => 'principals/system/system']], 31], + [[['privilege' => '{DAV:}read', 'principal' => 'principals/system/system'],['privilege' => '{DAV:}write', 'principal' => 'principals/system/system']], 7], + [[['privilege' => '{DAV:}read', 'principal' => 'principals/system/system'],['privilege' => '{DAV:}all', 'principal' => 'principals/system/system']], 31], + [[['privilege' => '{DAV:}all', 'principal' => 'principals/system/system'],['privilege' => '{DAV:}write', 'principal' => 'principals/system/system']], 31], + [[['privilege' => '{DAV:}read', 'principal' => 'principals/system/system'],['privilege' => '{DAV:}write', 'principal' => 'principals/system/system'],['privilege' => '{DAV:}all', 'principal' => 'principals/system/system']], 31], + [[['privilege' => '{DAV:}all', 'principal' => 'principals/system/system'],['privilege' => '{DAV:}read', 'principal' => 'principals/system/system'],['privilege' => '{DAV:}write', 'principal' => 'principals/system/system']], 31], ]; } diff --git a/apps/dav/tests/unit/CardDAV/CardDavBackendTest.php b/apps/dav/tests/unit/CardDAV/CardDavBackendTest.php index 1966a8d8c9a..c5eafa0764a 100644 --- a/apps/dav/tests/unit/CardDAV/CardDavBackendTest.php +++ b/apps/dav/tests/unit/CardDAV/CardDavBackendTest.php @@ -50,6 +50,7 @@ class CardDavBackendTest extends TestCase { private IUserManager&MockObject $userManager; private IGroupManager&MockObject $groupManager; private IEventDispatcher&MockObject $dispatcher; + private IConfig&MockObject $config; private Backend $sharingBackend; private IDBConnection $db; private CardDavBackend $backend; @@ -96,6 +97,7 @@ class CardDavBackendTest extends TestCase { $this->userManager = $this->createMock(IUserManager::class); $this->groupManager = $this->createMock(IGroupManager::class); + $this->config = $this->createMock(IConfig::class); $this->principal = $this->getMockBuilder(Principal::class) ->setConstructorArgs([ $this->userManager, @@ -106,7 +108,7 @@ class CardDavBackendTest extends TestCase { $this->createMock(IAppManager::class), $this->createMock(ProxyMapper::class), $this->createMock(KnownUserService::class), - $this->createMock(IConfig::class), + $this->config, $this->createMock(IFactory::class) ]) ->onlyMethods(['getPrincipalByPath', 'getGroupMembership', 'findByUri']) @@ -135,6 +137,7 @@ class CardDavBackendTest extends TestCase { $this->userManager, $this->dispatcher, $this->sharingBackend, + $this->config, ); // start every test with a empty cards_properties and cards table $query = $this->db->getQueryBuilder(); @@ -231,7 +234,7 @@ class CardDavBackendTest extends TestCase { public function testCardOperations(): void { /** @var CardDavBackend&MockObject $backend */ $backend = $this->getMockBuilder(CardDavBackend::class) - ->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend]) + ->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend,$this->config]) ->onlyMethods(['updateProperties', 'purgeProperties']) ->getMock(); @@ -291,7 +294,7 @@ class CardDavBackendTest extends TestCase { public function testMultiCard(): void { $this->backend = $this->getMockBuilder(CardDavBackend::class) - ->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend]) + ->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend,$this->config]) ->onlyMethods(['updateProperties']) ->getMock(); @@ -345,7 +348,7 @@ class CardDavBackendTest extends TestCase { public function testMultipleUIDOnDifferentAddressbooks(): void { $this->backend = $this->getMockBuilder(CardDavBackend::class) - ->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend]) + ->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend,$this->config]) ->onlyMethods(['updateProperties']) ->getMock(); @@ -368,7 +371,7 @@ class CardDavBackendTest extends TestCase { public function testMultipleUIDDenied(): void { $this->backend = $this->getMockBuilder(CardDavBackend::class) - ->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend]) + ->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend, $this->config]) ->onlyMethods(['updateProperties']) ->getMock(); @@ -390,7 +393,7 @@ class CardDavBackendTest extends TestCase { public function testNoValidUID(): void { $this->backend = $this->getMockBuilder(CardDavBackend::class) - ->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend]) + ->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend, $this->config]) ->onlyMethods(['updateProperties']) ->getMock(); @@ -408,7 +411,7 @@ class CardDavBackendTest extends TestCase { public function testDeleteWithoutCard(): void { $this->backend = $this->getMockBuilder(CardDavBackend::class) - ->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend]) + ->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend, $this->config]) ->onlyMethods([ 'getCardId', 'addChange', @@ -453,7 +456,7 @@ class CardDavBackendTest extends TestCase { public function testSyncSupport(): void { $this->backend = $this->getMockBuilder(CardDavBackend::class) - ->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend]) + ->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend, $this->config]) ->onlyMethods(['updateProperties']) ->getMock(); @@ -522,7 +525,7 @@ class CardDavBackendTest extends TestCase { $cardId = 2; $backend = $this->getMockBuilder(CardDavBackend::class) - ->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend]) + ->setConstructorArgs([$this->db, $this->principal, $this->userManager, $this->dispatcher, $this->sharingBackend, $this->config]) ->onlyMethods(['getCardId'])->getMock(); $backend->expects($this->any())->method('getCardId')->willReturn($cardId); diff --git a/apps/dav/tests/unit/CardDAV/SyncServiceTest.php b/apps/dav/tests/unit/CardDAV/SyncServiceTest.php index ea4886a67e6..77caed336f4 100644 --- a/apps/dav/tests/unit/CardDAV/SyncServiceTest.php +++ b/apps/dav/tests/unit/CardDAV/SyncServiceTest.php @@ -108,7 +108,7 @@ class SyncServiceTest extends TestCase { '1', 'principals/system/system', [] - ); + )[0]; $this->assertEquals('http://sabre.io/ns/sync/1', $token); } @@ -179,7 +179,7 @@ END:VCARD'; '1', 'principals/system/system', [] - ); + )[0]; $this->assertEquals('http://sabre.io/ns/sync/2', $token); } @@ -250,7 +250,7 @@ END:VCARD'; '1', 'principals/system/system', [] - ); + )[0]; $this->assertEquals('http://sabre.io/ns/sync/3', $token); } @@ -291,7 +291,7 @@ END:VCARD'; '1', 'principals/system/system', [] - ); + )[0]; $this->assertEquals('http://sabre.io/ns/sync/4', $token); } diff --git a/apps/encryption/l10n/lv.js b/apps/encryption/l10n/lv.js index 4e4d6b4ade3..b44cc20db29 100644 --- a/apps/encryption/l10n/lv.js +++ b/apps/encryption/l10n/lv.js @@ -2,16 +2,16 @@ OC.L10N.register( "encryption", { "Missing recovery key password" : "Pazudusi atkopšanas atslēgas parole", - "Please repeat the recovery key password" : "Lūdzu atkārtot atgūšanas atslēgas paroli", + "Please repeat the recovery key password" : "Lūgums atkārtot atkopes atslēgas paroli", "Repeated recovery key password does not match the provided recovery key password" : "Atkārtota atkopšanas atslēgas parole nesakrīt ar izsniegto atkopšanas atslēgu paroli", "Recovery key successfully enabled" : "Atkopšanas atslēga ir veiksmīgi iespējota", - "Could not enable recovery key. Please check your recovery key password!" : "Atkopšanas atslēgu nevarēja iespējot. Lūdzu, pārbaudiet atkopšanas atslēgas paroli!", + "Could not enable recovery key. Please check your recovery key password!" : "Atkopes atslēgu nevarēja iespējot. Lūgums pārbaudīt atkopes atslēgas paroli.", "Recovery key successfully disabled" : "Atkopšanas atslēga ir veiksmīgi deaktivizēta", - "Could not disable recovery key. Please check your recovery key password!" : "Atkopšanas atslēgu nevarēja atspējot. Lūdzu, pārbaudiet atkopšanas atslēgas paroli!", + "Could not disable recovery key. Please check your recovery key password!" : "Atkopes atslēgu nevarēja atspējot. Lūgums pārbaudīt atkopes atslēgas paroli.", "Missing parameters" : "Trūkstošos parametrs", "Please provide the old recovery password" : "Lūgums norādīt iepriekšējo atkopes paroli", - "Please provide a new recovery password" : "Lūdzu, ievadiet jaunu paroli", - "Please repeat the new recovery password" : "Lūdzu, atkārtojiet jauno atkopšanas paroli", + "Please provide a new recovery password" : "Lūgums norādīt jaunu atkopes paroli", + "Please repeat the new recovery password" : "Lūgums atkārtot jauno atkopes paroli", "Password successfully changed." : "Parole veiksmīgi nomainīta.", "Could not change the password. Maybe the old password was not correct." : "Nevarēja mainīt paroli. Varbūt vecā parole nav pareiza.", "Recovery Key disabled" : "Atkopšanas atslēga deaktivizēta", diff --git a/apps/encryption/l10n/lv.json b/apps/encryption/l10n/lv.json index 4bf8d7fce8e..42787888d86 100644 --- a/apps/encryption/l10n/lv.json +++ b/apps/encryption/l10n/lv.json @@ -1,15 +1,15 @@ { "translations": { "Missing recovery key password" : "Pazudusi atkopšanas atslēgas parole", - "Please repeat the recovery key password" : "Lūdzu atkārtot atgūšanas atslēgas paroli", + "Please repeat the recovery key password" : "Lūgums atkārtot atkopes atslēgas paroli", "Repeated recovery key password does not match the provided recovery key password" : "Atkārtota atkopšanas atslēgas parole nesakrīt ar izsniegto atkopšanas atslēgu paroli", "Recovery key successfully enabled" : "Atkopšanas atslēga ir veiksmīgi iespējota", - "Could not enable recovery key. Please check your recovery key password!" : "Atkopšanas atslēgu nevarēja iespējot. Lūdzu, pārbaudiet atkopšanas atslēgas paroli!", + "Could not enable recovery key. Please check your recovery key password!" : "Atkopes atslēgu nevarēja iespējot. Lūgums pārbaudīt atkopes atslēgas paroli.", "Recovery key successfully disabled" : "Atkopšanas atslēga ir veiksmīgi deaktivizēta", - "Could not disable recovery key. Please check your recovery key password!" : "Atkopšanas atslēgu nevarēja atspējot. Lūdzu, pārbaudiet atkopšanas atslēgas paroli!", + "Could not disable recovery key. Please check your recovery key password!" : "Atkopes atslēgu nevarēja atspējot. Lūgums pārbaudīt atkopes atslēgas paroli.", "Missing parameters" : "Trūkstošos parametrs", "Please provide the old recovery password" : "Lūgums norādīt iepriekšējo atkopes paroli", - "Please provide a new recovery password" : "Lūdzu, ievadiet jaunu paroli", - "Please repeat the new recovery password" : "Lūdzu, atkārtojiet jauno atkopšanas paroli", + "Please provide a new recovery password" : "Lūgums norādīt jaunu atkopes paroli", + "Please repeat the new recovery password" : "Lūgums atkārtot jauno atkopes paroli", "Password successfully changed." : "Parole veiksmīgi nomainīta.", "Could not change the password. Maybe the old password was not correct." : "Nevarēja mainīt paroli. Varbūt vecā parole nav pareiza.", "Recovery Key disabled" : "Atkopšanas atslēga deaktivizēta", diff --git a/apps/encryption/lib/Crypto/EncryptAll.php b/apps/encryption/lib/Crypto/EncryptAll.php index d9db616e6f1..4ed75b85a93 100644 --- a/apps/encryption/lib/Crypto/EncryptAll.php +++ b/apps/encryption/lib/Crypto/EncryptAll.php @@ -12,6 +12,7 @@ use OC\Files\View; use OCA\Encryption\KeyManager; use OCA\Encryption\Users\Setup; use OCA\Encryption\Util; +use OCP\Files\FileInfo; use OCP\IConfig; use OCP\IL10N; use OCP\IUser; @@ -202,15 +203,19 @@ class EncryptAll { while ($root = array_pop($directories)) { $content = $this->rootView->getDirectoryContent($root); foreach ($content as $file) { - $path = $root . '/' . $file['name']; - if ($this->rootView->is_dir($path)) { + $path = $root . '/' . $file->getName(); + if ($file->isShared()) { + $progress->setMessage("Skip shared file/folder $path"); + $progress->advance(); + continue; + } elseif ($file->getType() === FileInfo::TYPE_FOLDER) { $directories[] = $path; continue; } else { $progress->setMessage("encrypt files for user $userCount: $path"); $progress->advance(); try { - if ($this->encryptFile($path) === false) { + if ($this->encryptFile($file, $path) === false) { $progress->setMessage("encrypt files for user $userCount: $path (already encrypted)"); $progress->advance(); } @@ -231,17 +236,9 @@ class EncryptAll { } } - /** - * encrypt file - * - * @param string $path - * @return bool - */ - protected function encryptFile($path) { - + protected function encryptFile(FileInfo $fileInfo, string $path): bool { // skip already encrypted files - $fileInfo = $this->rootView->getFileInfo($path); - if ($fileInfo !== false && $fileInfo->isEncrypted()) { + if ($fileInfo->isEncrypted()) { return true; } diff --git a/apps/encryption/tests/Crypto/EncryptAllTest.php b/apps/encryption/tests/Crypto/EncryptAllTest.php index 9b39c62b650..c56e3375a73 100644 --- a/apps/encryption/tests/Crypto/EncryptAllTest.php +++ b/apps/encryption/tests/Crypto/EncryptAllTest.php @@ -82,7 +82,7 @@ class EncryptAllTest extends TestCase { /** * We need format method to return a string - * @var OutputFormatterInterface|\PHPUnit\Framework\MockObject\MockObject + * @var OutputFormatterInterface&MockObject */ $outputFormatter = $this->createMock(OutputFormatterInterface::class); $outputFormatter->method('isDecorated')->willReturn(false); @@ -114,6 +114,13 @@ class EncryptAllTest extends TestCase { ); } + protected function createFileInfoMock($type, string $name): FileInfo&MockObject { + $fileInfo = $this->createMock(FileInfo::class); + $fileInfo->method('getType')->willReturn($type); + $fileInfo->method('getName')->willReturn($name); + return $fileInfo; + } + public function testEncryptAll(): void { /** @var EncryptAll&MockObject $encryptAll */ $encryptAll = $this->getMockBuilder(EncryptAll::class) @@ -299,8 +306,8 @@ class EncryptAllTest extends TestCase { '', null, [ - ['name' => 'foo', 'type' => 'dir'], - ['name' => 'bar', 'type' => 'file'], + $this->createFileInfoMock(FileInfo::TYPE_FOLDER, 'foo'), + $this->createFileInfoMock(FileInfo::TYPE_FILE, 'bar'), ], ], [ @@ -308,26 +315,17 @@ class EncryptAllTest extends TestCase { '', null, [ - ['name' => 'subfile', 'type' => 'file'] + $this->createFileInfoMock(FileInfo::TYPE_FILE, 'subfile'), ], ], ]); - $this->view->expects($this->any())->method('is_dir') - ->willReturnCallback( - function ($path) { - if ($path === '/user1/files/foo') { - return true; - } - return false; - } - ); - $encryptAllCalls = []; $encryptAll->expects($this->exactly(2)) ->method('encryptFile') - ->willReturnCallback(function (string $path) use (&$encryptAllCalls): void { + ->willReturnCallback(function (FileInfo $file, string $path) use (&$encryptAllCalls): bool { $encryptAllCalls[] = $path; + return true; }); $outputFormatter = $this->createMock(OutputFormatterInterface::class); @@ -362,8 +360,7 @@ class EncryptAllTest extends TestCase { $fileInfo = $this->createMock(FileInfo::class); $fileInfo->expects($this->any())->method('isEncrypted') ->willReturn($isEncrypted); - $this->view->expects($this->any())->method('getFileInfo') - ->willReturn($fileInfo); + $this->view->expects($this->never())->method('getFileInfo'); if ($isEncrypted) { @@ -375,7 +372,7 @@ class EncryptAllTest extends TestCase { } $this->assertTrue( - $this->invokePrivate($this->encryptAll, 'encryptFile', ['foo.txt']) + $this->invokePrivate($this->encryptAll, 'encryptFile', [$fileInfo, 'foo.txt']) ); } diff --git a/apps/federation/lib/SyncFederationAddressBooks.php b/apps/federation/lib/SyncFederationAddressBooks.php index 05144b40879..d11f92b76ef 100644 --- a/apps/federation/lib/SyncFederationAddressBooks.php +++ b/apps/federation/lib/SyncFederationAddressBooks.php @@ -34,7 +34,7 @@ class SyncFederationAddressBooks { $url = $trustedServer['url']; $callback($url, null); $sharedSecret = $trustedServer['shared_secret']; - $syncToken = $trustedServer['sync_token']; + $oldSyncToken = $trustedServer['sync_token']; $endPoints = $this->ocsDiscoveryService->discover($url, 'FEDERATED_SHARING'); $cardDavUser = $endPoints['carddav-user'] ?? 'system'; @@ -49,10 +49,25 @@ class SyncFederationAddressBooks { $targetBookProperties = [ '{DAV:}displayname' => $url ]; + try { - $newToken = $this->syncService->syncRemoteAddressBook($url, $cardDavUser, $addressBookUrl, $sharedSecret, $syncToken, $targetBookId, $targetPrincipal, $targetBookProperties); - if ($newToken !== $syncToken) { - $this->dbHandler->setServerStatus($url, TrustedServers::STATUS_OK, $newToken); + $syncToken = $oldSyncToken; + + do { + [$syncToken, $truncated] = $this->syncService->syncRemoteAddressBook( + $url, + $cardDavUser, + $addressBookUrl, + $sharedSecret, + $syncToken, + $targetBookId, + $targetPrincipal, + $targetBookProperties + ); + } while ($truncated); + + if ($syncToken !== $oldSyncToken) { + $this->dbHandler->setServerStatus($url, TrustedServers::STATUS_OK, $syncToken); } else { $this->logger->debug("Sync Token for $url unchanged from previous sync"); // The server status might have been changed to a failure status in previous runs. diff --git a/apps/federation/tests/SyncFederationAddressbooksTest.php b/apps/federation/tests/SyncFederationAddressbooksTest.php index 8b075204859..ff03f5cf442 100644 --- a/apps/federation/tests/SyncFederationAddressbooksTest.php +++ b/apps/federation/tests/SyncFederationAddressbooksTest.php @@ -45,7 +45,7 @@ class SyncFederationAddressbooksTest extends \Test\TestCase { ->with('https://cloud.example.org', 1, '1'); $syncService = $this->createMock(SyncService::class); $syncService->expects($this->once())->method('syncRemoteAddressBook') - ->willReturn('1'); + ->willReturn(['1', false]); /** @var SyncService $syncService */ $s = new SyncFederationAddressBooks($dbHandler, $syncService, $this->discoveryService, $this->logger); @@ -96,7 +96,7 @@ class SyncFederationAddressbooksTest extends \Test\TestCase { ->with('https://cloud.example.org', 1); $syncService = $this->createMock(SyncService::class); $syncService->expects($this->once())->method('syncRemoteAddressBook') - ->willReturn('0'); + ->willReturn(['0', false]); /** @var SyncService $syncService */ $s = new SyncFederationAddressBooks($dbHandler, $syncService, $this->discoveryService, $this->logger); diff --git a/apps/files/l10n/es.js b/apps/files/l10n/es.js index 6e32740a677..0d21bdef976 100644 --- a/apps/files/l10n/es.js +++ b/apps/files/l10n/es.js @@ -1,9 +1,9 @@ OC.L10N.register( "files", { - "Added to favorites" : "Agregado a favoritos", + "Added to favorites" : "Añadido a favoritos", "Removed from favorites" : "Quitado de favoritos", - "You added {file} to your favorites" : "Has agregado {file} a tus favoritos", + "You added {file} to your favorites" : "Has añadido {file} a tus favoritos", "You removed {file} from your favorites" : "Has quitado {file} de tus favoritos", "Favorites" : "Favoritos", "File changes" : "Cambios del archivo", @@ -39,10 +39,10 @@ OC.L10N.register( "{user} renamed {oldfile} to {newfile}" : "{user} ha renombrado {oldfile} a {newfile}", "You moved {oldfile} to {newfile}" : "Has movido {oldfile} a {newfile}", "{user} moved {oldfile} to {newfile}" : "{user} movió {oldfile} a {newfile}", - "A file has been added to or removed from your <strong>favorites</strong>" : "Un archivo fue agregado o borrado de tus <strong>favoritos</strong>", + "A file has been added to or removed from your <strong>favorites</strong>" : "Un archivo se ha añadido o borrado de tus <strong>favoritos</strong>", "Files" : "Archivos", "A file or folder has been <strong>changed</strong>" : "Se ha <strong>modificado</strong> un archivo o carpeta", - "A favorite file or folder has been <strong>changed</strong>" : "Un archivo o carpeta favorito ha sido <strong>cambiado</strong>", + "A favorite file or folder has been <strong>changed</strong>" : "Un archivo favorito o carpeta se ha <strong>cambiado</strong>", "Failed to authorize" : "Fallo al autorizar", "Invalid folder path" : "Ruta de carpeta inválida", "Folder not found" : "Carpeta no encontrada", @@ -129,6 +129,7 @@ OC.L10N.register( "Search globally by filename …" : "Búsqueda global por nombre de archivo …", "Search here by filename …" : "Buscar aquí por nombre de archivo …", "Search scope options" : "Opciones de alcance de la búsqueda", + "Filter and search from this location" : "Filtrar y buscar en esta ubicación", "Search globally" : "Buscar globalmente", "{usedQuotaByte} used" : "{usedQuotaByte} utilizados", "{used} of {quota} used" : "{used} usados de {quota}", @@ -141,6 +142,7 @@ OC.L10N.register( "Create new folder" : "Crear carpeta nueva", "This name is already in use." : "Este nombre ya está en uso.", "Create" : "Crear", + "Files starting with a dot are hidden by default" : "Los archivos que comienzan con un punto son ocultados por defecto", "Fill template fields" : "Rellenar los campos de la plantilla", "Submitting fields …" : "Enviando campos …", "Submit" : "Enviar", @@ -182,7 +184,7 @@ OC.L10N.register( "Loading current folder" : "Cargando carpeta actual", "Retry" : "Reintentar", "No files in here" : "Aquí no hay archivos", - "Upload some content or sync with your devices!" : "¡Suba contenidos o sincronice sus dispositivos!", + "Upload some content or sync with your devices!" : "¡Sube contenido o sincroniza tus dispositivos!", "Go back" : "Ir atrás", "Views" : "Vistas", "Files settings" : "Configuración de archivos", @@ -200,16 +202,21 @@ OC.L10N.register( "Sort favorites first" : "Ordenar los favoritos primero", "Sort folders before files" : "Ordenar carpetas antes que archivos", "Enable folder tree" : "Habilitar el árbol de carpetas", + "Visual settings" : "Ajustes visuales", "Show hidden files" : "Mostrar archivos ocultos", "Show file type column" : "Mostrar la columna de tipo de archivo", "Crop image previews" : "Recortar la previsualización de las imágenes", + "Show files extensions" : "Mostrar extensiones de archivos", "Additional settings" : "Ajustes adicionales", "WebDAV" : "WebDAV", - "WebDAV URL" : "WebDAV URL", + "WebDAV URL" : "URL de WebDAV", "Copy to clipboard" : "Copiar al portapapeles", + "Use this address to access your Files via WebDAV." : "Usa esta dirección para acceder a tus Archivos a través de WebDAV.", + "Two-Factor Authentication is enabled for your account, and therefore you need to use an app password to connect an external WebDAV client." : "La autenticación en dos pasos está habilitada para su cuenta y, por lo tanto, debe usar una contraseña de aplicación para conectarse a un cliente WebDAV externo.", "Warnings" : "Advertencias", "Prevent warning dialogs from open or reenable them." : "Evitar que se abran los diálogos de advertencia o volver a habilitarlos.", "Show a warning dialog when changing a file extension." : "Mostrar un diálogo de advertencia cuando se cambia la extensión de un archivo.", + "Show a warning dialog when deleting files." : "Mostrar un cuadro de aviso cuando se borren archivos.", "Keyboard shortcuts" : "Atajos de teclado", "Speed up your Files experience with these quick shortcuts." : "Acelere su experiencia con Archivos con esos rápidos atajos de teclado.", "Open the actions menu for a file" : "Abrir el menú de acciones para un archivo", @@ -327,6 +334,7 @@ OC.L10N.register( "Templates" : "Plantillas", "New template folder" : "Nueva carpeta de plantillas", "In folder" : "En carpeta", + "Search in all files" : "Buscar en todos los archivos", "Search in folder: {folder}" : "Buscar en carpeta: {folder}", "One of the dropped files could not be processed" : "Uno de los archivos arrastrados no puede ser procesado", "Your browser does not support the Filesystem API. Directories will not be uploaded" : "Su navegador no soporta la API de Sistema de archivos. Los directorios no se subirán", @@ -360,6 +368,7 @@ OC.L10N.register( "No favorites yet" : "Aún no hay favoritos", "Files and folders you mark as favorite will show up here" : "Aquí aparecerán los archivos y carpetas que has marcado como favoritos", "List of your files and folders." : "Lista de sus archivos y carpetas.", + "Folder tree" : "Árbol de carpetas", "List of your files and folders that are not shared." : "Lista de sus archivos y carpetas que no están compartidos.", "No personal files found" : "No se encontraron archivos personales", "Files that are not shared will show up here." : "Los archivos y carpetas que no ha compartido aparecerán aquí.", @@ -372,12 +381,12 @@ OC.L10N.register( "No entries found in this folder" : "No hay entradas en esta carpeta", "Select all" : "Seleccionar todo", "Upload too large" : "Subida demasido grande", - "The files you are trying to upload exceed the maximum size for file uploads on this server." : "Los archivos que está intentando subir sobrepasan el tamaño máximo permitido en este servidor.", + "The files you are trying to upload exceed the maximum size for file uploads on this server." : "Los archivos que estás intentando subir sobrepasan el tamaño máximo permitido en este servidor.", "File could not be found" : "El archivo no se ha encontrado", "Show list view" : "Mostrar vista de lista", "Show grid view" : "Mostrar vista de cuadrícula", "Close" : "Cerrar", - "Could not create folder \"{dir}\"" : "No se pudo crear la carpeta \"{dir}\"", + "Could not create folder \"{dir}\"" : "No se ha podido crear la carpeta \"{dir}\"", "This will stop your current uploads." : "Esto detendrá las subidas en curso.", "Upload cancelled." : "Subida cancelada.", "Processing files …" : "Procesando archivos …", @@ -385,7 +394,7 @@ OC.L10N.register( "Unable to upload {filename} as it is a directory or has 0 bytes" : "No ha sido posible subir {filename} porque es un directorio o tiene 0 bytes", "Not enough free space, you are uploading {size1} but only {size2} is left" : "No hay suficiente espacio libre. Quiere subir {size1} pero solo quedan {size2}", "Target folder \"{dir}\" does not exist any more" : "La carpeta de destino \"{dir}\" ya no existe", - "An unknown error has occurred" : "Ha ocurrido un error desconocido", + "An unknown error has occurred" : "Se ha producido un error desconocido", "File could not be uploaded" : "No se ha podido subir el archivo", "Uploading …" : "Subiendo …", "{remainingTime} ({currentNumber}/{total})" : "{remainingTime} ({currentNumber}/{total})", @@ -404,18 +413,18 @@ OC.L10N.register( "Select directory \"{dirName}\"" : "Seleccione la carpeta \"{dirName}\"", "Select file \"{fileName}\"" : "Seleccione el archivo \"{fileName}\"", "Unable to determine date" : "No se ha podido determinar la fecha", - "Could not move \"{file}\", target exists" : "No se pudo mover \"{file}\", ya existe", - "Could not move \"{file}\"" : "No se pudo mover \"{file}\"", + "Could not move \"{file}\", target exists" : "No se ha podido mover \"{file}\", ya existe", + "Could not move \"{file}\"" : "No se ha podido mover \"{file}\"", "copy" : "copiar", "Could not copy \"{file}\", target exists" : "No se ha podido copiar \"{file}\", ya existe el destino", "Could not copy \"{file}\"" : "No se ha podido copiar \"{file}\"", "Copied {origin} inside {destination}" : "Se ha copiado {origin} dentro de {destination}", "Copied {origin} and {nbfiles} other files inside {destination}" : "Se han copiado {origin} y {nbfiles} otros archivos dentro de {destination}", "{newName} already exists" : "{newName} ya existe", - "Could not create file \"{file}\"" : "No se pudo crear archivo \"{file}\"", + "Could not create file \"{file}\"" : "No se ha podido crear el archivo \"{file}\"", "Could not create file \"{file}\" because it already exists" : "No se pudo crear archivo \"{file}\" porque ya existe", "Could not create folder \"{dir}\" because it already exists" : "No se ha podido crear la carpeta \"{dir}\" porque ya existe", - "Could not fetch file details \"{file}\"" : "No se pudieron obtener los detalles de \"{file}\"", + "Could not fetch file details \"{file}\"" : "No se han podido obtener los detalles de \"{file}\"", "Error deleting file \"{fileName}\"." : "Error al borrar el archivo \"{fileName}\".", "No search results in other folders for {tag}{filter}{endtag}" : "No hay resultados de búsqueda en otras carpetas para {tag}{filter}{endtag}", "Enter more than two characters to search in other folders" : "Escriba más de dos caracteres para buscar en otras carpetas", @@ -428,7 +437,7 @@ OC.L10N.register( "Select file range" : "Seleccionar el rango de archivos", "{used}%" : "{used}%", "{used} used" : "{used} usados", - "\"{name}\" is an invalid file name." : "\"{name}\" es un nombre de archivo inválido.", + "\"{name}\" is an invalid file name." : "\"{name}\" es un nombre de archivo no válido.", "File name cannot be empty." : "El nombre de archivo no puede estar vacío.", "\"/\" is not allowed inside a file name." : "\"/\" no se permite en un nombre de archivo.", "\"{name}\" is not an allowed filetype" : "\"{name}\" no es un tipo de archivo permitido", @@ -446,10 +455,10 @@ OC.L10N.register( "_%n byte_::_%n bytes_" : ["%n byte","%n bytes","%n bytes"], "Favored" : "Favorecido", "Favor" : "Favorecer", - "Copy direct link (only works for people who have access to this file/folder)" : "El enlace directo fue copiado (solo funciona para usuarios que tienen acceso a este archivo/carpeta)", + "Copy direct link (only works for people who have access to this file/folder)" : "Copiar enlace directo (solo funciona para usuarios que tienen acceso a este archivo/carpeta)", "Upload file" : "Subir archivo", "Not favored" : "No favorecido", - "An error occurred while trying to update the tags" : "Se produjo un error al tratar de actualizar las etiquetas", + "An error occurred while trying to update the tags" : "Se ha producido un error al tratar de actualizar las etiquetas", "Upload (max. %s)" : "Subida (máx. %s)", "Submitting fields…" : "Enviando campos…", "Filter filenames…" : "Filtrar nombres de archivo…", diff --git a/apps/files/l10n/es.json b/apps/files/l10n/es.json index c13b8161666..440e426013c 100644 --- a/apps/files/l10n/es.json +++ b/apps/files/l10n/es.json @@ -1,7 +1,7 @@ { "translations": { - "Added to favorites" : "Agregado a favoritos", + "Added to favorites" : "Añadido a favoritos", "Removed from favorites" : "Quitado de favoritos", - "You added {file} to your favorites" : "Has agregado {file} a tus favoritos", + "You added {file} to your favorites" : "Has añadido {file} a tus favoritos", "You removed {file} from your favorites" : "Has quitado {file} de tus favoritos", "Favorites" : "Favoritos", "File changes" : "Cambios del archivo", @@ -37,10 +37,10 @@ "{user} renamed {oldfile} to {newfile}" : "{user} ha renombrado {oldfile} a {newfile}", "You moved {oldfile} to {newfile}" : "Has movido {oldfile} a {newfile}", "{user} moved {oldfile} to {newfile}" : "{user} movió {oldfile} a {newfile}", - "A file has been added to or removed from your <strong>favorites</strong>" : "Un archivo fue agregado o borrado de tus <strong>favoritos</strong>", + "A file has been added to or removed from your <strong>favorites</strong>" : "Un archivo se ha añadido o borrado de tus <strong>favoritos</strong>", "Files" : "Archivos", "A file or folder has been <strong>changed</strong>" : "Se ha <strong>modificado</strong> un archivo o carpeta", - "A favorite file or folder has been <strong>changed</strong>" : "Un archivo o carpeta favorito ha sido <strong>cambiado</strong>", + "A favorite file or folder has been <strong>changed</strong>" : "Un archivo favorito o carpeta se ha <strong>cambiado</strong>", "Failed to authorize" : "Fallo al autorizar", "Invalid folder path" : "Ruta de carpeta inválida", "Folder not found" : "Carpeta no encontrada", @@ -127,6 +127,7 @@ "Search globally by filename …" : "Búsqueda global por nombre de archivo …", "Search here by filename …" : "Buscar aquí por nombre de archivo …", "Search scope options" : "Opciones de alcance de la búsqueda", + "Filter and search from this location" : "Filtrar y buscar en esta ubicación", "Search globally" : "Buscar globalmente", "{usedQuotaByte} used" : "{usedQuotaByte} utilizados", "{used} of {quota} used" : "{used} usados de {quota}", @@ -139,6 +140,7 @@ "Create new folder" : "Crear carpeta nueva", "This name is already in use." : "Este nombre ya está en uso.", "Create" : "Crear", + "Files starting with a dot are hidden by default" : "Los archivos que comienzan con un punto son ocultados por defecto", "Fill template fields" : "Rellenar los campos de la plantilla", "Submitting fields …" : "Enviando campos …", "Submit" : "Enviar", @@ -180,7 +182,7 @@ "Loading current folder" : "Cargando carpeta actual", "Retry" : "Reintentar", "No files in here" : "Aquí no hay archivos", - "Upload some content or sync with your devices!" : "¡Suba contenidos o sincronice sus dispositivos!", + "Upload some content or sync with your devices!" : "¡Sube contenido o sincroniza tus dispositivos!", "Go back" : "Ir atrás", "Views" : "Vistas", "Files settings" : "Configuración de archivos", @@ -198,16 +200,21 @@ "Sort favorites first" : "Ordenar los favoritos primero", "Sort folders before files" : "Ordenar carpetas antes que archivos", "Enable folder tree" : "Habilitar el árbol de carpetas", + "Visual settings" : "Ajustes visuales", "Show hidden files" : "Mostrar archivos ocultos", "Show file type column" : "Mostrar la columna de tipo de archivo", "Crop image previews" : "Recortar la previsualización de las imágenes", + "Show files extensions" : "Mostrar extensiones de archivos", "Additional settings" : "Ajustes adicionales", "WebDAV" : "WebDAV", - "WebDAV URL" : "WebDAV URL", + "WebDAV URL" : "URL de WebDAV", "Copy to clipboard" : "Copiar al portapapeles", + "Use this address to access your Files via WebDAV." : "Usa esta dirección para acceder a tus Archivos a través de WebDAV.", + "Two-Factor Authentication is enabled for your account, and therefore you need to use an app password to connect an external WebDAV client." : "La autenticación en dos pasos está habilitada para su cuenta y, por lo tanto, debe usar una contraseña de aplicación para conectarse a un cliente WebDAV externo.", "Warnings" : "Advertencias", "Prevent warning dialogs from open or reenable them." : "Evitar que se abran los diálogos de advertencia o volver a habilitarlos.", "Show a warning dialog when changing a file extension." : "Mostrar un diálogo de advertencia cuando se cambia la extensión de un archivo.", + "Show a warning dialog when deleting files." : "Mostrar un cuadro de aviso cuando se borren archivos.", "Keyboard shortcuts" : "Atajos de teclado", "Speed up your Files experience with these quick shortcuts." : "Acelere su experiencia con Archivos con esos rápidos atajos de teclado.", "Open the actions menu for a file" : "Abrir el menú de acciones para un archivo", @@ -325,6 +332,7 @@ "Templates" : "Plantillas", "New template folder" : "Nueva carpeta de plantillas", "In folder" : "En carpeta", + "Search in all files" : "Buscar en todos los archivos", "Search in folder: {folder}" : "Buscar en carpeta: {folder}", "One of the dropped files could not be processed" : "Uno de los archivos arrastrados no puede ser procesado", "Your browser does not support the Filesystem API. Directories will not be uploaded" : "Su navegador no soporta la API de Sistema de archivos. Los directorios no se subirán", @@ -358,6 +366,7 @@ "No favorites yet" : "Aún no hay favoritos", "Files and folders you mark as favorite will show up here" : "Aquí aparecerán los archivos y carpetas que has marcado como favoritos", "List of your files and folders." : "Lista de sus archivos y carpetas.", + "Folder tree" : "Árbol de carpetas", "List of your files and folders that are not shared." : "Lista de sus archivos y carpetas que no están compartidos.", "No personal files found" : "No se encontraron archivos personales", "Files that are not shared will show up here." : "Los archivos y carpetas que no ha compartido aparecerán aquí.", @@ -370,12 +379,12 @@ "No entries found in this folder" : "No hay entradas en esta carpeta", "Select all" : "Seleccionar todo", "Upload too large" : "Subida demasido grande", - "The files you are trying to upload exceed the maximum size for file uploads on this server." : "Los archivos que está intentando subir sobrepasan el tamaño máximo permitido en este servidor.", + "The files you are trying to upload exceed the maximum size for file uploads on this server." : "Los archivos que estás intentando subir sobrepasan el tamaño máximo permitido en este servidor.", "File could not be found" : "El archivo no se ha encontrado", "Show list view" : "Mostrar vista de lista", "Show grid view" : "Mostrar vista de cuadrícula", "Close" : "Cerrar", - "Could not create folder \"{dir}\"" : "No se pudo crear la carpeta \"{dir}\"", + "Could not create folder \"{dir}\"" : "No se ha podido crear la carpeta \"{dir}\"", "This will stop your current uploads." : "Esto detendrá las subidas en curso.", "Upload cancelled." : "Subida cancelada.", "Processing files …" : "Procesando archivos …", @@ -383,7 +392,7 @@ "Unable to upload {filename} as it is a directory or has 0 bytes" : "No ha sido posible subir {filename} porque es un directorio o tiene 0 bytes", "Not enough free space, you are uploading {size1} but only {size2} is left" : "No hay suficiente espacio libre. Quiere subir {size1} pero solo quedan {size2}", "Target folder \"{dir}\" does not exist any more" : "La carpeta de destino \"{dir}\" ya no existe", - "An unknown error has occurred" : "Ha ocurrido un error desconocido", + "An unknown error has occurred" : "Se ha producido un error desconocido", "File could not be uploaded" : "No se ha podido subir el archivo", "Uploading …" : "Subiendo …", "{remainingTime} ({currentNumber}/{total})" : "{remainingTime} ({currentNumber}/{total})", @@ -402,18 +411,18 @@ "Select directory \"{dirName}\"" : "Seleccione la carpeta \"{dirName}\"", "Select file \"{fileName}\"" : "Seleccione el archivo \"{fileName}\"", "Unable to determine date" : "No se ha podido determinar la fecha", - "Could not move \"{file}\", target exists" : "No se pudo mover \"{file}\", ya existe", - "Could not move \"{file}\"" : "No se pudo mover \"{file}\"", + "Could not move \"{file}\", target exists" : "No se ha podido mover \"{file}\", ya existe", + "Could not move \"{file}\"" : "No se ha podido mover \"{file}\"", "copy" : "copiar", "Could not copy \"{file}\", target exists" : "No se ha podido copiar \"{file}\", ya existe el destino", "Could not copy \"{file}\"" : "No se ha podido copiar \"{file}\"", "Copied {origin} inside {destination}" : "Se ha copiado {origin} dentro de {destination}", "Copied {origin} and {nbfiles} other files inside {destination}" : "Se han copiado {origin} y {nbfiles} otros archivos dentro de {destination}", "{newName} already exists" : "{newName} ya existe", - "Could not create file \"{file}\"" : "No se pudo crear archivo \"{file}\"", + "Could not create file \"{file}\"" : "No se ha podido crear el archivo \"{file}\"", "Could not create file \"{file}\" because it already exists" : "No se pudo crear archivo \"{file}\" porque ya existe", "Could not create folder \"{dir}\" because it already exists" : "No se ha podido crear la carpeta \"{dir}\" porque ya existe", - "Could not fetch file details \"{file}\"" : "No se pudieron obtener los detalles de \"{file}\"", + "Could not fetch file details \"{file}\"" : "No se han podido obtener los detalles de \"{file}\"", "Error deleting file \"{fileName}\"." : "Error al borrar el archivo \"{fileName}\".", "No search results in other folders for {tag}{filter}{endtag}" : "No hay resultados de búsqueda en otras carpetas para {tag}{filter}{endtag}", "Enter more than two characters to search in other folders" : "Escriba más de dos caracteres para buscar en otras carpetas", @@ -426,7 +435,7 @@ "Select file range" : "Seleccionar el rango de archivos", "{used}%" : "{used}%", "{used} used" : "{used} usados", - "\"{name}\" is an invalid file name." : "\"{name}\" es un nombre de archivo inválido.", + "\"{name}\" is an invalid file name." : "\"{name}\" es un nombre de archivo no válido.", "File name cannot be empty." : "El nombre de archivo no puede estar vacío.", "\"/\" is not allowed inside a file name." : "\"/\" no se permite en un nombre de archivo.", "\"{name}\" is not an allowed filetype" : "\"{name}\" no es un tipo de archivo permitido", @@ -444,10 +453,10 @@ "_%n byte_::_%n bytes_" : ["%n byte","%n bytes","%n bytes"], "Favored" : "Favorecido", "Favor" : "Favorecer", - "Copy direct link (only works for people who have access to this file/folder)" : "El enlace directo fue copiado (solo funciona para usuarios que tienen acceso a este archivo/carpeta)", + "Copy direct link (only works for people who have access to this file/folder)" : "Copiar enlace directo (solo funciona para usuarios que tienen acceso a este archivo/carpeta)", "Upload file" : "Subir archivo", "Not favored" : "No favorecido", - "An error occurred while trying to update the tags" : "Se produjo un error al tratar de actualizar las etiquetas", + "An error occurred while trying to update the tags" : "Se ha producido un error al tratar de actualizar las etiquetas", "Upload (max. %s)" : "Subida (máx. %s)", "Submitting fields…" : "Enviando campos…", "Filter filenames…" : "Filtrar nombres de archivo…", diff --git a/apps/files/l10n/mk.js b/apps/files/l10n/mk.js index 8d9883c4dd5..80f697aad04 100644 --- a/apps/files/l10n/mk.js +++ b/apps/files/l10n/mk.js @@ -277,15 +277,24 @@ OC.L10N.register( "This year ({year})" : "Оваа година ({year})", "Last year ({year})" : "Минатата година ({year})", "Documents" : "Документи", + "Spreadsheets" : "Табели", + "Presentations" : "Презентации", + "PDFs" : "PDF-и", "Folders" : "Папки", "Audio" : "Аудио", + "Photos and images" : "Фотографии и слики", "Videos" : "Видеа", "Created new folder \"{name}\"" : "Креирана нова папка \"{name}\"", "Unable to initialize the templates directory" : "Не може да се иницијализира папка за шаблони", "Create templates folder" : "Креирај папка за шаблони", "Templates" : "Шаблони", "New template folder" : "Нова папка за шаблони", + "In folder" : "Во папка", + "Search in folder: {folder}" : "Барај во папка: {folder}", "One of the dropped files could not be processed" : "Една од испуштените датотеки неможе да се процесоира", + "Unable to create the directory {directory}" : "Неможе да се креира папка {directory}", + "Some files could not be uploaded" : "Некој датотеки неможат да се прикачат", + "Files uploaded successfully" : "Успешно прикачени датотеки", "Some files could not be moved" : "Некои датотеки не можат да се преместат", "Could not rename \"{oldName}\", it does not exist any more" : "Неможе да се преименува \"{oldName}\", не постои повеќе", "The name \"{newName}\" is already used in the folder \"{dir}\". Please choose a different name." : "Името \"{newName}\" веќе се користи во папката \"{dir}\". Ве молиме изберете друго име.", @@ -293,9 +302,15 @@ OC.L10N.register( "This operation is forbidden" : "Операцијата не е дозволена", "This directory is unavailable, please check the logs or contact the administrator" : "Овој директориум е недостапен, ве молиме проверете ги логовите или контактирајте со администраторот", "Storage is temporarily not available" : "Складиштето моментално не е достапно", + "Unexpected error: {error}" : "Неочекувана грешка: {error}", "_%n file_::_%n files_" : ["%n датотека","%n датотеки"], "_%n folder_::_%n folders_" : ["%n папка","%n папки"], "Filename must not be empty." : "Името на датотеката не може да биде празно.", + "\"{char}\" is not allowed inside a filename." : "\"{char}\" не е дозволен во името на датотеката.", + "\"{segment}\" is a reserved name and not allowed for filenames." : "\"{segment}\" е резервирано име и не е дозволено во името на датотеката.", + "\"{extension}\" is not an allowed filetype." : "\"{extension}\" не е дозволен вид на датотека.", + "Filenames must not end with \"{extension}\"." : "Името неможе да завршува со \"{extension}\".", + "List of favorite files and folders." : "Листа на омилени датотеки и папки.", "No favorites yet" : "Сеуште нема фаворити", "Files and folders you mark as favorite will show up here" : "Датотеките и папките кои ќе ги означите како чести, ќе се појават тука", "List of your files and folders." : "Листа на вашите датотеки и папки.", @@ -397,6 +412,8 @@ OC.L10N.register( "Personal Files" : "Лични датотеки", "Text file" : "Текстуална датотека", "New text file.txt" : "Нова текстуална датотека file.txt", + "%1$s (renamed)" : "%1$s (преименувано)", + "renamed file" : "преименувана датотека", "Filter file names …" : "Филтрирај имиња на датотеки ..." }, "nplurals=2; plural=(n % 10 == 1 && n % 100 != 11) ? 0 : 1;"); diff --git a/apps/files/l10n/mk.json b/apps/files/l10n/mk.json index fca2b8d3b5c..bc76f3e5e86 100644 --- a/apps/files/l10n/mk.json +++ b/apps/files/l10n/mk.json @@ -275,15 +275,24 @@ "This year ({year})" : "Оваа година ({year})", "Last year ({year})" : "Минатата година ({year})", "Documents" : "Документи", + "Spreadsheets" : "Табели", + "Presentations" : "Презентации", + "PDFs" : "PDF-и", "Folders" : "Папки", "Audio" : "Аудио", + "Photos and images" : "Фотографии и слики", "Videos" : "Видеа", "Created new folder \"{name}\"" : "Креирана нова папка \"{name}\"", "Unable to initialize the templates directory" : "Не може да се иницијализира папка за шаблони", "Create templates folder" : "Креирај папка за шаблони", "Templates" : "Шаблони", "New template folder" : "Нова папка за шаблони", + "In folder" : "Во папка", + "Search in folder: {folder}" : "Барај во папка: {folder}", "One of the dropped files could not be processed" : "Една од испуштените датотеки неможе да се процесоира", + "Unable to create the directory {directory}" : "Неможе да се креира папка {directory}", + "Some files could not be uploaded" : "Некој датотеки неможат да се прикачат", + "Files uploaded successfully" : "Успешно прикачени датотеки", "Some files could not be moved" : "Некои датотеки не можат да се преместат", "Could not rename \"{oldName}\", it does not exist any more" : "Неможе да се преименува \"{oldName}\", не постои повеќе", "The name \"{newName}\" is already used in the folder \"{dir}\". Please choose a different name." : "Името \"{newName}\" веќе се користи во папката \"{dir}\". Ве молиме изберете друго име.", @@ -291,9 +300,15 @@ "This operation is forbidden" : "Операцијата не е дозволена", "This directory is unavailable, please check the logs or contact the administrator" : "Овој директориум е недостапен, ве молиме проверете ги логовите или контактирајте со администраторот", "Storage is temporarily not available" : "Складиштето моментално не е достапно", + "Unexpected error: {error}" : "Неочекувана грешка: {error}", "_%n file_::_%n files_" : ["%n датотека","%n датотеки"], "_%n folder_::_%n folders_" : ["%n папка","%n папки"], "Filename must not be empty." : "Името на датотеката не може да биде празно.", + "\"{char}\" is not allowed inside a filename." : "\"{char}\" не е дозволен во името на датотеката.", + "\"{segment}\" is a reserved name and not allowed for filenames." : "\"{segment}\" е резервирано име и не е дозволено во името на датотеката.", + "\"{extension}\" is not an allowed filetype." : "\"{extension}\" не е дозволен вид на датотека.", + "Filenames must not end with \"{extension}\"." : "Името неможе да завршува со \"{extension}\".", + "List of favorite files and folders." : "Листа на омилени датотеки и папки.", "No favorites yet" : "Сеуште нема фаворити", "Files and folders you mark as favorite will show up here" : "Датотеките и папките кои ќе ги означите како чести, ќе се појават тука", "List of your files and folders." : "Листа на вашите датотеки и папки.", @@ -395,6 +410,8 @@ "Personal Files" : "Лични датотеки", "Text file" : "Текстуална датотека", "New text file.txt" : "Нова текстуална датотека file.txt", + "%1$s (renamed)" : "%1$s (преименувано)", + "renamed file" : "преименувана датотека", "Filter file names …" : "Филтрирај имиња на датотеки ..." },"pluralForm" :"nplurals=2; plural=(n % 10 == 1 && n % 100 != 11) ? 0 : 1;" }
\ No newline at end of file diff --git a/apps/files/l10n/zh_CN.js b/apps/files/l10n/zh_CN.js index 2644bffadf7..4903a18b8f8 100644 --- a/apps/files/l10n/zh_CN.js +++ b/apps/files/l10n/zh_CN.js @@ -142,6 +142,7 @@ OC.L10N.register( "Create new folder" : "创建新文件夹", "This name is already in use." : "此名称已被使用。", "Create" : "创建", + "Files starting with a dot are hidden by default" : "以点开头的文件默认是隐藏的", "Fill template fields" : "填写模板字段", "Submitting fields …" : "正在提交字段…", "Submit" : "使用", @@ -201,16 +202,21 @@ OC.L10N.register( "Sort favorites first" : "收藏排序优先", "Sort folders before files" : "将文件夹排在文件前面", "Enable folder tree" : "启用文件夹树", + "Visual settings" : "视觉设置", "Show hidden files" : "显示隐藏文件", "Show file type column" : "显示文件类型列", "Crop image previews" : "裁剪图片预览", + "Show files extensions" : "显示文件扩展名", "Additional settings" : "其他设置", "WebDAV" : "WebDAV", "WebDAV URL" : "WebDAV URL", "Copy to clipboard" : "复制到剪贴板", + "Use this address to access your Files via WebDAV." : "使用此地址通过 WebDAV 访问您的文件。", + "Two-Factor Authentication is enabled for your account, and therefore you need to use an app password to connect an external WebDAV client." : "您的账号已启用双因素身份验证,因此您需要使用应用密码来连接外部 WebDAV 客户端。", "Warnings" : "警告", "Prevent warning dialogs from open or reenable them." : "防止打开或重新启用警告对话框。", "Show a warning dialog when changing a file extension." : "更改文件扩展名时显示警告对话框。", + "Show a warning dialog when deleting files." : "删除文件时显示警告对话框。", "Keyboard shortcuts" : "键盘快捷键", "Speed up your Files experience with these quick shortcuts." : "这些快捷键可以加快你的“文件”体验。", "Open the actions menu for a file" : "打开文件的操作菜单", @@ -328,6 +334,7 @@ OC.L10N.register( "Templates" : "模板", "New template folder" : "新建模板文件夹", "In folder" : "文件夹内", + "Search in all files" : "在所有文件中搜索", "Search in folder: {folder}" : "在文件夹内搜索:{folder}", "One of the dropped files could not be processed" : "无法处理其中一个已删除的文件", "Your browser does not support the Filesystem API. Directories will not be uploaded" : "你的浏览器不支持文件系统API。目录不会被上传", @@ -361,6 +368,7 @@ OC.L10N.register( "No favorites yet" : "暂无收藏", "Files and folders you mark as favorite will show up here" : "收藏的文件和文件夹会在这里显示", "List of your files and folders." : "您的文件与文件件列表。", + "Folder tree" : "文件夹树", "List of your files and folders that are not shared." : "尚未分享的文件与文件夹", "No personal files found" : "找不到个人文件", "Files that are not shared will show up here." : "尚未分享的文件会显示在此处", diff --git a/apps/files/l10n/zh_CN.json b/apps/files/l10n/zh_CN.json index e519c6853c0..ef788c7c4e5 100644 --- a/apps/files/l10n/zh_CN.json +++ b/apps/files/l10n/zh_CN.json @@ -140,6 +140,7 @@ "Create new folder" : "创建新文件夹", "This name is already in use." : "此名称已被使用。", "Create" : "创建", + "Files starting with a dot are hidden by default" : "以点开头的文件默认是隐藏的", "Fill template fields" : "填写模板字段", "Submitting fields …" : "正在提交字段…", "Submit" : "使用", @@ -199,16 +200,21 @@ "Sort favorites first" : "收藏排序优先", "Sort folders before files" : "将文件夹排在文件前面", "Enable folder tree" : "启用文件夹树", + "Visual settings" : "视觉设置", "Show hidden files" : "显示隐藏文件", "Show file type column" : "显示文件类型列", "Crop image previews" : "裁剪图片预览", + "Show files extensions" : "显示文件扩展名", "Additional settings" : "其他设置", "WebDAV" : "WebDAV", "WebDAV URL" : "WebDAV URL", "Copy to clipboard" : "复制到剪贴板", + "Use this address to access your Files via WebDAV." : "使用此地址通过 WebDAV 访问您的文件。", + "Two-Factor Authentication is enabled for your account, and therefore you need to use an app password to connect an external WebDAV client." : "您的账号已启用双因素身份验证,因此您需要使用应用密码来连接外部 WebDAV 客户端。", "Warnings" : "警告", "Prevent warning dialogs from open or reenable them." : "防止打开或重新启用警告对话框。", "Show a warning dialog when changing a file extension." : "更改文件扩展名时显示警告对话框。", + "Show a warning dialog when deleting files." : "删除文件时显示警告对话框。", "Keyboard shortcuts" : "键盘快捷键", "Speed up your Files experience with these quick shortcuts." : "这些快捷键可以加快你的“文件”体验。", "Open the actions menu for a file" : "打开文件的操作菜单", @@ -326,6 +332,7 @@ "Templates" : "模板", "New template folder" : "新建模板文件夹", "In folder" : "文件夹内", + "Search in all files" : "在所有文件中搜索", "Search in folder: {folder}" : "在文件夹内搜索:{folder}", "One of the dropped files could not be processed" : "无法处理其中一个已删除的文件", "Your browser does not support the Filesystem API. Directories will not be uploaded" : "你的浏览器不支持文件系统API。目录不会被上传", @@ -359,6 +366,7 @@ "No favorites yet" : "暂无收藏", "Files and folders you mark as favorite will show up here" : "收藏的文件和文件夹会在这里显示", "List of your files and folders." : "您的文件与文件件列表。", + "Folder tree" : "文件夹树", "List of your files and folders that are not shared." : "尚未分享的文件与文件夹", "No personal files found" : "找不到个人文件", "Files that are not shared will show up here." : "尚未分享的文件会显示在此处", diff --git a/apps/files/src/FilesApp.vue b/apps/files/src/FilesApp.vue index 54821a03457..6fc02113162 100644 --- a/apps/files/src/FilesApp.vue +++ b/apps/files/src/FilesApp.vue @@ -12,11 +12,10 @@ <script lang="ts"> import { isPublicShare } from '@nextcloud/sharing/public' import { defineComponent } from 'vue' - import NcContent from '@nextcloud/vue/components/NcContent' - import Navigation from './views/Navigation.vue' import FilesList from './views/FilesList.vue' +import { useHotKeys } from './composables/useHotKeys' export default defineComponent({ name: 'FilesApp', @@ -28,6 +27,9 @@ export default defineComponent({ }, setup() { + // Register global hotkeys + useHotKeys() + const isPublic = isPublicShare() return { diff --git a/apps/files/src/services/HotKeysService.spec.ts b/apps/files/src/composables/useHotKeys.spec.ts index 92430c8e6ad..9c001e8b5ff 100644 --- a/apps/files/src/services/HotKeysService.spec.ts +++ b/apps/files/src/composables/useHotKeys.spec.ts @@ -1,10 +1,14 @@ -/** +/* * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ + +import type { Location } from 'vue-router' + import { File, Folder, Permission, View } from '@nextcloud/files' -import { describe, it, vi, expect, beforeEach, beforeAll, afterEach } from 'vitest' -import { nextTick } from 'vue' +import { enableAutoDestroy, mount } from '@vue/test-utils' +import { describe, it, vi, expect, beforeEach, afterEach } from 'vitest' +import { defineComponent, nextTick } from 'vue' import axios from '@nextcloud/axios' import { getPinia } from '../store/index.ts' @@ -15,38 +19,64 @@ import { action as deleteAction } from '../actions/deleteAction.ts' import { action as favoriteAction } from '../actions/favoriteAction.ts' import { action as renameAction } from '../actions/renameAction.ts' import { action as sidebarAction } from '../actions/sidebarAction.ts' -import { registerHotkeys } from './HotKeysService.ts' +import { useHotKeys } from './useHotKeys.ts' import { useUserConfigStore } from '../store/userconfig.ts' +// this is the mocked current route +const route = vi.hoisted(() => ({ + name: 'test', + params: { + fileId: 123, + }, + query: { + openFile: 'false', + dir: '/parent/dir', + }, +})) + +// mocked router +const router = vi.hoisted(() => ({ + push: vi.fn<(route: Location) => void>(), +})) + +vi.mock('../actions/sidebarAction.ts', { spy: true }) +vi.mock('../actions/deleteAction.ts', { spy: true }) +vi.mock('../actions/favoriteAction.ts', { spy: true }) +vi.mock('../actions/renameAction.ts', { spy: true }) + +vi.mock('vue-router/composables', () => ({ + useRoute: vi.fn(() => route), + useRouter: vi.fn(() => router), +})) + let file: File const view = { id: 'files', name: 'Files', } as View -vi.mock('../actions/sidebarAction.ts', { spy: true }) -vi.mock('../actions/deleteAction.ts', { spy: true }) -vi.mock('../actions/favoriteAction.ts', { spy: true }) -vi.mock('../actions/renameAction.ts', { spy: true }) +const TestComponent = defineComponent({ + name: 'test', + setup() { + useHotKeys() + }, + template: '<div />', +}) describe('HotKeysService testing', () => { const activeStore = useActiveStore(getPinia()) - const goToRouteMock = vi.fn() - let initialState: HTMLInputElement + enableAutoDestroy(afterEach) + afterEach(() => { document.body.removeChild(initialState) }) - beforeAll(() => { - registerHotkeys() - }) - beforeEach(() => { // Make sure the router is reset before each test - goToRouteMock.mockClear() + router.push.mockClear() // Make sure the file is reset before each test file = new File({ @@ -66,9 +96,6 @@ describe('HotKeysService testing', () => { activeStore.activeNode = file window.OCA = { Files: { Sidebar: { open: () => {}, setActiveTab: () => {} } } } - // We only mock what needed, we do not need Files.Router.goTo or Files.Navigation - window.OCP = { Files: { Router: { goToRoute: goToRouteMock, params: {}, query: {} } } } - initialState = document.createElement('input') initialState.setAttribute('type', 'hidden') initialState.setAttribute('id', 'initial-state-files_trashbin-config') @@ -76,6 +103,8 @@ describe('HotKeysService testing', () => { allow_delete: true, }))) document.body.appendChild(initialState) + + mount(TestComponent) }) it('Pressing d should open the sidebar once', () => { @@ -135,13 +164,11 @@ describe('HotKeysService testing', () => { }) it('Pressing alt+up should go to parent directory', () => { - expect(goToRouteMock).toHaveBeenCalledTimes(0) - window.OCP.Files.Router.query = { dir: '/foo/bar' } - + expect(router.push).toHaveBeenCalledTimes(0) dispatchEvent({ key: 'ArrowUp', code: 'ArrowUp', altKey: true }) - expect(goToRouteMock).toHaveBeenCalledOnce() - expect(goToRouteMock.mock.calls[0][2].dir).toBe('/foo') + expect(router.push).toHaveBeenCalledOnce() + expect(router.push.mock.calls[0][0].query?.dir).toBe('/parent') }) it('Pressing v should toggle grid view', async () => { diff --git a/apps/files/src/services/HotKeysService.ts b/apps/files/src/composables/useHotKeys.ts index 1ed369b061b..ff56627b2f9 100644 --- a/apps/files/src/services/HotKeysService.ts +++ b/apps/files/src/composables/useHotKeys.ts @@ -4,13 +4,15 @@ */ import { useHotKey } from '@nextcloud/vue/composables/useHotKey' import { dirname } from 'path' +import { useRoute, useRouter } from 'vue-router/composables' import { action as deleteAction } from '../actions/deleteAction.ts' import { action as favoriteAction } from '../actions/favoriteAction.ts' import { action as renameAction } from '../actions/renameAction.ts' import { action as sidebarAction } from '../actions/sidebarAction.ts' -import { executeAction } from '../utils/actionUtils.ts' import { useUserConfigStore } from '../store/userconfig.ts' +import { useRouteParameters } from './useRouteParameters.ts' +import { executeAction } from '../utils/actionUtils.ts' import logger from '../logger.ts' /** @@ -18,7 +20,12 @@ import logger from '../logger.ts' * As much as possible, we try to have all the hotkeys in one place. * Please make sure to add tests for the hotkeys after adding a new one. */ -export const registerHotkeys = function() { +export function useHotKeys(): void { + const userConfigStore = useUserConfigStore() + const { directory } = useRouteParameters() + const router = useRouter() + const route = useRoute() + // d opens the sidebar useHotKey('d', () => executeAction(sidebarAction), { stop: true, @@ -57,26 +64,23 @@ export const registerHotkeys = function() { }) logger.debug('Hotkeys registered') -} - -const goToParentDir = function() { - const params = window.OCP.Files.Router?.params || {} - const query = window.OCP.Files.Router?.query || {} - const currentDir = (query?.dir || '/') as string - const parentDir = dirname(currentDir) + /** + * Use the router to go to the parent directory + */ + function goToParentDir() { + const dir = dirname(directory.value) - logger.debug('Navigating to parent directory', { parentDir }) - window.OCP.Files.Router.goToRoute( - null, - { ...params }, - { ...query, dir: parentDir }, - ) -} + logger.debug('Navigating to parent directory', { dir }) + router.push({ params: { ...route.params }, query: { ...route.query, dir } }) + } -const toggleGridView = function() { - const userConfigStore = useUserConfigStore() - const value = userConfigStore?.userConfig?.grid_view - logger.debug('Toggling grid view', { old: value, new: !value }) - userConfigStore.update('grid_view', !value) + /** + * Toggle the grid view + */ + function toggleGridView() { + const value = userConfigStore.userConfig.grid_view + logger.debug('Toggling grid view', { old: value, new: !value }) + userConfigStore.update('grid_view', !value) + } } diff --git a/apps/files/src/main.ts b/apps/files/src/main.ts index 4b8aca9efd4..463ecaf6239 100644 --- a/apps/files/src/main.ts +++ b/apps/files/src/main.ts @@ -8,7 +8,6 @@ import { PiniaVuePlugin } from 'pinia' import Vue from 'vue' import { getPinia } from './store/index.ts' -import { registerHotkeys } from './services/HotKeysService.ts' import FilesApp from './FilesApp.vue' import router from './router/router' import RouterService from './services/RouterService' @@ -40,9 +39,6 @@ if (!window.OCP.Files.Router) { // Init Pinia store Vue.use(PiniaVuePlugin) -// Init HotKeys AFTER pinia is set up -registerHotkeys() - // Init Files App Settings Service const Settings = new SettingsService() Object.assign(window.OCA.Files, { Settings }) diff --git a/apps/files_external/lib/Lib/Storage/SMB.php b/apps/files_external/lib/Lib/Storage/SMB.php index 0899d2ac093..8f8750864e1 100644 --- a/apps/files_external/lib/Lib/Storage/SMB.php +++ b/apps/files_external/lib/Lib/Storage/SMB.php @@ -336,7 +336,7 @@ class SMB extends Common implements INotifyStorage { if ($retry) { return $this->stat($path, false); } else { - throw $e; + throw new StorageNotAvailableException($e->getMessage(), $e->getCode(), $e); } } if ($this->remoteIsShare() && $this->isRootDir($path)) { diff --git a/apps/files_sharing/src/components/NewFileRequestDialog/NewFileRequestDialogDatePassword.vue b/apps/files_sharing/src/components/NewFileRequestDialog/NewFileRequestDialogDatePassword.vue index 4c14b21e1d5..7e6d56e8794 100644 --- a/apps/files_sharing/src/components/NewFileRequestDialog/NewFileRequestDialogDatePassword.vue +++ b/apps/files_sharing/src/components/NewFileRequestDialog/NewFileRequestDialogDatePassword.vue @@ -14,9 +14,9 @@ <fieldset class="file-request-dialog__expiration" data-cy-file-request-dialog-fieldset="expiration"> <!-- Enable expiration --> <legend>{{ t('files_sharing', 'When should the request expire?') }}</legend> - <NcCheckboxRadioSwitch v-show="!defaultExpireDateEnforced" - :checked="defaultExpireDateEnforced || expirationDate !== null" - :disabled="disabled || defaultExpireDateEnforced" + <NcCheckboxRadioSwitch v-show="!isExpirationDateEnforced" + :checked="isExpirationDateEnforced || expirationDate !== null" + :disabled="disabled || isExpirationDateEnforced" @update:checked="onToggleDeadline"> {{ t('files_sharing', 'Set a submission expiration date') }} </NcCheckboxRadioSwitch> @@ -46,9 +46,9 @@ <fieldset class="file-request-dialog__password" data-cy-file-request-dialog-fieldset="password"> <!-- Enable password --> <legend>{{ t('files_sharing', 'What password should be used for the request?') }}</legend> - <NcCheckboxRadioSwitch v-show="!enforcePasswordForPublicLink" - :checked="enforcePasswordForPublicLink || password !== null" - :disabled="disabled || enforcePasswordForPublicLink" + <NcCheckboxRadioSwitch v-show="!isPasswordEnforced" + :checked="isPasswordEnforced || password !== null" + :disabled="disabled || isPasswordEnforced" @update:checked="onTogglePassword"> {{ t('files_sharing', 'Set a password') }} </NcCheckboxRadioSwitch> @@ -59,7 +59,7 @@ :disabled="disabled" :label="t('files_sharing', 'Password')" :placeholder="t('files_sharing', 'Enter a valid password')" - :required="false" + :required="enforcePasswordForPublicLink" :value="password" name="password" @update:value="$emit('update:password', $event)" /> @@ -180,6 +180,18 @@ export default defineComponent({ return '' }, + + isExpirationDateEnforced(): boolean { + // Both fields needs to be enabled in the settings + return this.defaultExpireDateEnabled + && this.defaultExpireDateEnforced + }, + + isPasswordEnforced(): boolean { + // Both fields needs to be enabled in the settings + return this.enableLinkPasswordByDefault + && this.enforcePasswordForPublicLink + }, }, mounted() { @@ -189,12 +201,12 @@ export default defineComponent({ } // If enforced, we cannot set a date before the default expiration days (see admin settings) - if (this.defaultExpireDateEnforced) { + if (this.isExpirationDateEnforced) { this.maxDate = sharingConfig.defaultExpirationDate } // If enabled by default, we generate a valid password - if (this.enableLinkPasswordByDefault) { + if (this.isPasswordEnforced) { this.generatePassword() } }, diff --git a/apps/files_sharing/src/views/SharingTab.vue b/apps/files_sharing/src/views/SharingTab.vue index 262504aca01..dc200c61df4 100644 --- a/apps/files_sharing/src/views/SharingTab.vue +++ b/apps/files_sharing/src/views/SharingTab.vue @@ -243,8 +243,7 @@ export default { * @return {boolean} */ isSharedWithMe() { - return this.sharedWithMe !== null - && this.sharedWithMe !== undefined + return !!this.sharedWithMe?.user }, /** diff --git a/apps/files_trashbin/l10n/pt_BR.js b/apps/files_trashbin/l10n/pt_BR.js index f1e360f82c0..de9b08f8326 100644 --- a/apps/files_trashbin/l10n/pt_BR.js +++ b/apps/files_trashbin/l10n/pt_BR.js @@ -2,7 +2,7 @@ OC.L10N.register( "files_trashbin", { "restored" : "restaurado", - "Deleted files" : "Arquivos exluídos", + "Deleted files" : "Arquivos excluídos", "Deleted files and folders in the trash bin (may expire during export if you are low on storage space)" : "Arquivos e pastas excluídos na lixeira (podem expirar durante a exportação se você estiver com pouco espaço de armazenamento)", "This application enables people to restore files that were deleted from the system." : "Este aplicativo permite que as pessoas restaurem arquivos que foram excluídos do sistema.", "This application enables people to restore files that were deleted from the system. It displays a list of deleted files in the web interface, and has options to restore those deleted files back to the people file directories or remove them permanently from the system. Restoring a file also restores related file versions, if the versions application is enabled. When a file is deleted from a share, it can be restored in the same manner, though it is no longer shared. By default, these files remain in the trash bin for 30 days.\nTo prevent an account from running out of disk space, the Deleted files app will not utilize more than 50% of the currently available free quota for deleted files. If the deleted files exceed this limit, the app deletes the oldest files until it gets below this limit. More information is available in the Deleted Files documentation." : "Este aplicativo permite que as pessoas restaurem arquivos que foram excluídos do sistema. Ele exibe uma lista de arquivos excluídos na interface da web e tem opções para restaurar esses arquivos excluídos de volta aos diretórios de arquivos de pessoas ou removê-los permanentemente do sistema. A restauração de um arquivo também restaura versões de arquivos relacionadas, se o aplicativo de versões estiver ativado. Quando um arquivo é excluído de um compartilhamento, ele pode ser restaurado da mesma maneira, embora não seja mais compartilhado. Por padrão, esses arquivos permanecem na lixeira por 30 dias. \nPara evitar que uma conta fique sem espaço em disco, o aplicativo Arquivos excluídos não utilizará mais de 50% da cota livre atualmente disponível para arquivos excluídos. Se os arquivos excluídos excederem esse limite, o aplicativo excluirá os arquivos mais antigos até ficarem abaixo desse limite. Mais informações estão disponíveis na documentação Arquivos Excluídos.", diff --git a/apps/files_trashbin/l10n/pt_BR.json b/apps/files_trashbin/l10n/pt_BR.json index da1f67d35ed..8e8c5a3ec3d 100644 --- a/apps/files_trashbin/l10n/pt_BR.json +++ b/apps/files_trashbin/l10n/pt_BR.json @@ -1,6 +1,6 @@ { "translations": { "restored" : "restaurado", - "Deleted files" : "Arquivos exluídos", + "Deleted files" : "Arquivos excluídos", "Deleted files and folders in the trash bin (may expire during export if you are low on storage space)" : "Arquivos e pastas excluídos na lixeira (podem expirar durante a exportação se você estiver com pouco espaço de armazenamento)", "This application enables people to restore files that were deleted from the system." : "Este aplicativo permite que as pessoas restaurem arquivos que foram excluídos do sistema.", "This application enables people to restore files that were deleted from the system. It displays a list of deleted files in the web interface, and has options to restore those deleted files back to the people file directories or remove them permanently from the system. Restoring a file also restores related file versions, if the versions application is enabled. When a file is deleted from a share, it can be restored in the same manner, though it is no longer shared. By default, these files remain in the trash bin for 30 days.\nTo prevent an account from running out of disk space, the Deleted files app will not utilize more than 50% of the currently available free quota for deleted files. If the deleted files exceed this limit, the app deletes the oldest files until it gets below this limit. More information is available in the Deleted Files documentation." : "Este aplicativo permite que as pessoas restaurem arquivos que foram excluídos do sistema. Ele exibe uma lista de arquivos excluídos na interface da web e tem opções para restaurar esses arquivos excluídos de volta aos diretórios de arquivos de pessoas ou removê-los permanentemente do sistema. A restauração de um arquivo também restaura versões de arquivos relacionadas, se o aplicativo de versões estiver ativado. Quando um arquivo é excluído de um compartilhamento, ele pode ser restaurado da mesma maneira, embora não seja mais compartilhado. Por padrão, esses arquivos permanecem na lixeira por 30 dias. \nPara evitar que uma conta fique sem espaço em disco, o aplicativo Arquivos excluídos não utilizará mais de 50% da cota livre atualmente disponível para arquivos excluídos. Se os arquivos excluídos excederem esse limite, o aplicativo excluirá os arquivos mais antigos até ficarem abaixo desse limite. Mais informações estão disponíveis na documentação Arquivos Excluídos.", diff --git a/apps/provisioning_api/l10n/zh_CN.js b/apps/provisioning_api/l10n/zh_CN.js index 2bbc7a444c9..2bb24cb01ae 100644 --- a/apps/provisioning_api/l10n/zh_CN.js +++ b/apps/provisioning_api/l10n/zh_CN.js @@ -13,6 +13,7 @@ OC.L10N.register( "Invalid password value" : "密码值无效", "An email address is required, to send a password link to the user." : "需要电子邮件地址,以将密码链接发送给用户。", "Required email address was not provided" : "未提供所需的电子邮件地址", + "User creation failed" : "用户创建失败", "Invalid quota value: %1$s" : "配额值无效:%1$s", "Invalid quota value. %1$s is exceeding the maximum quota" : "配额值无效。%1$s 超过了最大配额", "Unlimited quota is forbidden on this instance" : "此实例上禁止无限配额", diff --git a/apps/provisioning_api/l10n/zh_CN.json b/apps/provisioning_api/l10n/zh_CN.json index b38c49f3867..03efc200979 100644 --- a/apps/provisioning_api/l10n/zh_CN.json +++ b/apps/provisioning_api/l10n/zh_CN.json @@ -11,6 +11,7 @@ "Invalid password value" : "密码值无效", "An email address is required, to send a password link to the user." : "需要电子邮件地址,以将密码链接发送给用户。", "Required email address was not provided" : "未提供所需的电子邮件地址", + "User creation failed" : "用户创建失败", "Invalid quota value: %1$s" : "配额值无效:%1$s", "Invalid quota value. %1$s is exceeding the maximum quota" : "配额值无效。%1$s 超过了最大配额", "Unlimited quota is forbidden on this instance" : "此实例上禁止无限配额", diff --git a/apps/settings/l10n/de.js b/apps/settings/l10n/de.js index 3b525970801..267617fce18 100644 --- a/apps/settings/l10n/de.js +++ b/apps/settings/l10n/de.js @@ -395,6 +395,7 @@ OC.L10N.register( "Default expiration time of remote shares in days" : "Standardablaufzeit für Remote-Freigaben in Tagen", "Expire remote shares after x days" : "Remote-Freigaben laufen nach x Tagen ab", "Set default expiration date for shares via link or mail" : "Standardablaufzeit für Link- oder E-Mail-Freigaben festlegen", + "Enforce expiration date for link or mail shares" : "Ablaufdatum für Link- oder E-Mail-Freigaben erzwingen", "Default expiration time of shares in days" : "Standardablaufzeit für Freigaben in Tagen", "Privacy settings for sharing" : "Datenschutzeinstellungen bezüglich des Teilens", "Allow account name autocompletion in share dialog and allow access to the system address book" : "Automatische Vervollständigung des Kontonamens im Freigabedialog und Zugriff auf das Systemadressbuch zulassen", diff --git a/apps/settings/l10n/de.json b/apps/settings/l10n/de.json index 0f484bf25f0..1c6ac9e8e14 100644 --- a/apps/settings/l10n/de.json +++ b/apps/settings/l10n/de.json @@ -393,6 +393,7 @@ "Default expiration time of remote shares in days" : "Standardablaufzeit für Remote-Freigaben in Tagen", "Expire remote shares after x days" : "Remote-Freigaben laufen nach x Tagen ab", "Set default expiration date for shares via link or mail" : "Standardablaufzeit für Link- oder E-Mail-Freigaben festlegen", + "Enforce expiration date for link or mail shares" : "Ablaufdatum für Link- oder E-Mail-Freigaben erzwingen", "Default expiration time of shares in days" : "Standardablaufzeit für Freigaben in Tagen", "Privacy settings for sharing" : "Datenschutzeinstellungen bezüglich des Teilens", "Allow account name autocompletion in share dialog and allow access to the system address book" : "Automatische Vervollständigung des Kontonamens im Freigabedialog und Zugriff auf das Systemadressbuch zulassen", diff --git a/apps/settings/l10n/de_DE.js b/apps/settings/l10n/de_DE.js index 9d9d37ffdff..a9917558321 100644 --- a/apps/settings/l10n/de_DE.js +++ b/apps/settings/l10n/de_DE.js @@ -395,6 +395,7 @@ OC.L10N.register( "Default expiration time of remote shares in days" : "Standardablaufzeit für Remote-Freigaben in Tagen", "Expire remote shares after x days" : "Remote-Freigaben laufen nach x Tagen ab", "Set default expiration date for shares via link or mail" : "Standardablaufzeit für Link- oder E-Mail-Freigaben festlegen", + "Enforce expiration date for link or mail shares" : "Ablaufdatum für Link- oder E-Mail-Freigaben erzwingen", "Default expiration time of shares in days" : "Standardablaufzeit für Freigaben in Tagen", "Privacy settings for sharing" : "Datenschutzeinstellungen bezüglich des Teilens", "Allow account name autocompletion in share dialog and allow access to the system address book" : "Automatische Vervollständigung des Kontonamens im Freigabedialog und Zugriff auf das Systemadressbuch zulassen", diff --git a/apps/settings/l10n/de_DE.json b/apps/settings/l10n/de_DE.json index 5136fbbdf30..1a633f6f50a 100644 --- a/apps/settings/l10n/de_DE.json +++ b/apps/settings/l10n/de_DE.json @@ -393,6 +393,7 @@ "Default expiration time of remote shares in days" : "Standardablaufzeit für Remote-Freigaben in Tagen", "Expire remote shares after x days" : "Remote-Freigaben laufen nach x Tagen ab", "Set default expiration date for shares via link or mail" : "Standardablaufzeit für Link- oder E-Mail-Freigaben festlegen", + "Enforce expiration date for link or mail shares" : "Ablaufdatum für Link- oder E-Mail-Freigaben erzwingen", "Default expiration time of shares in days" : "Standardablaufzeit für Freigaben in Tagen", "Privacy settings for sharing" : "Datenschutzeinstellungen bezüglich des Teilens", "Allow account name autocompletion in share dialog and allow access to the system address book" : "Automatische Vervollständigung des Kontonamens im Freigabedialog und Zugriff auf das Systemadressbuch zulassen", diff --git a/apps/settings/l10n/et_EE.js b/apps/settings/l10n/et_EE.js index dfff7899eb5..5f705138e2e 100644 --- a/apps/settings/l10n/et_EE.js +++ b/apps/settings/l10n/et_EE.js @@ -261,6 +261,7 @@ OC.L10N.register( "Default expiration time of remote shares in days" : "Kaugserveris asuva jaosmeedia vaikimisi aegumine päevades", "Expire remote shares after x days" : "Jaosmeedia aegub x päeva möödudes", "Set default expiration date for shares via link or mail" : "Määra lingi või e-kirjaga jagatava jaosmeedia vaikimisi aegumiskuupäev", + "Enforce expiration date for link or mail shares" : "Jõusta lingiga või e-postiga jagamise aegumiskuupäev", "Default expiration time of shares in days" : "Jaosmeedia vaikimisi aegumine päevades", "Privacy settings for sharing" : "Jagamise privaatsusseadistused", "Show disclaimer text on the public link upload page (only shown when the file list is hidden)" : "Kuva avaliku lingiga üleslaadimise lehel lahtiütluste tekst (vaid siis, kui failide loend on peidetud)", diff --git a/apps/settings/l10n/et_EE.json b/apps/settings/l10n/et_EE.json index e642931fd77..82c4767af7e 100644 --- a/apps/settings/l10n/et_EE.json +++ b/apps/settings/l10n/et_EE.json @@ -259,6 +259,7 @@ "Default expiration time of remote shares in days" : "Kaugserveris asuva jaosmeedia vaikimisi aegumine päevades", "Expire remote shares after x days" : "Jaosmeedia aegub x päeva möödudes", "Set default expiration date for shares via link or mail" : "Määra lingi või e-kirjaga jagatava jaosmeedia vaikimisi aegumiskuupäev", + "Enforce expiration date for link or mail shares" : "Jõusta lingiga või e-postiga jagamise aegumiskuupäev", "Default expiration time of shares in days" : "Jaosmeedia vaikimisi aegumine päevades", "Privacy settings for sharing" : "Jagamise privaatsusseadistused", "Show disclaimer text on the public link upload page (only shown when the file list is hidden)" : "Kuva avaliku lingiga üleslaadimise lehel lahtiütluste tekst (vaid siis, kui failide loend on peidetud)", diff --git a/apps/settings/l10n/lv.js b/apps/settings/l10n/lv.js index b3bba6519de..72737da6f8f 100644 --- a/apps/settings/l10n/lv.js +++ b/apps/settings/l10n/lv.js @@ -91,6 +91,7 @@ OC.L10N.register( "Featured" : "Izcelta", "Featured apps are developed by and within the community. They offer central functionality and are ready for production use." : "Izceltās lietotnes ir kopienas izstrādātas. Tās sniedz centrālu funkcionalitāte un ir gatavas izmantošanai produkcijā.", "Download and enable all" : "Lejupielādēt un iespējot visu", + "All apps are up-to-date." : "Visas lietotnes ir atjauninātas.", "Icon" : "Ikona", "Name" : "Nosaukums", "Version" : "Versija", @@ -144,7 +145,7 @@ OC.L10N.register( "Password confirmation is required" : "Nepieciešams paroles apstiprinājums", "Server-side encryption" : "Servera šifrēšana", "Enable server-side encryption" : "Ieslēgt servera šifrēšanu", - "No encryption module loaded, please enable an encryption module in the app menu." : "Nav ielādēts šifrēšanas moduļis, lūdzu, aktivizējiet šifrēšanas moduli lietotņu izvēlnē.", + "No encryption module loaded, please enable an encryption module in the app menu." : "Nav ielādēts neviens šifrēšanas modulis, lūgums iespējot šifrēšanas moduli lietotņu izvēlnē.", "Select default encryption module:" : "Atlasīt noklusējuma šifrēšanas moduli:", "Enable encryption" : "Ieslēgt šifrēšanu", "Encryption alone does not guarantee security of the system. Please see documentation for more information about how the encryption app works, and the supported use cases." : "Šifrēšana vien negarantē sistēmas drošību. Skatiet dokumentāciju, lai iegūtu papildinformāciju par šifrēšanas lietotnes izmantošanu un atbalstītajiem izmantošanas veidiem.", @@ -163,7 +164,7 @@ OC.L10N.register( "You are using {s}{usage}{/s} of {s}{totalSpace}{/s} ({s}{usageRelative}%{/s})" : "Tu izmanto {s}{usage}{/s} no {s}{totalSpace}{/s} ({s}{usageRelative}%{/s})", "You are a member of the following groups:" : "Tu esi zemāk uzskaitīto kopu dalībnieks:", "This address is not confirmed" : "Šī adrese nav apstiprināta", - "Primary email for password reset and notifications" : "Primārā e-pasta adrese paroles atjaunošanai un paziņojumiem", + "Primary email for password reset and notifications" : "Galvenā e-pasta adrese paroles atiestatīšanai un paziņojumiem", "No email address set" : "Nav norādīts e-pasts", "Help translate" : "Palīdzi tulkot", "Unable to update locale" : "Nevarēja atjaunināt lokalizāciju", @@ -224,6 +225,7 @@ OC.L10N.register( "Hide" : "Paslēpt", "Download and enable" : "Lejupielādēt un iespējot", "Disable" : "Deaktivēt", + "Allow untested app" : "Atļaut nepārbaudītu lietotni", "Unknown" : "Nezināms", "Never" : "Nekad", "Do you really want to wipe your data from this device?" : "Vai tiešām izdzēst datus šajā ierīcē?", diff --git a/apps/settings/l10n/lv.json b/apps/settings/l10n/lv.json index 5452dd137a8..a12bc0c0cc2 100644 --- a/apps/settings/l10n/lv.json +++ b/apps/settings/l10n/lv.json @@ -89,6 +89,7 @@ "Featured" : "Izcelta", "Featured apps are developed by and within the community. They offer central functionality and are ready for production use." : "Izceltās lietotnes ir kopienas izstrādātas. Tās sniedz centrālu funkcionalitāte un ir gatavas izmantošanai produkcijā.", "Download and enable all" : "Lejupielādēt un iespējot visu", + "All apps are up-to-date." : "Visas lietotnes ir atjauninātas.", "Icon" : "Ikona", "Name" : "Nosaukums", "Version" : "Versija", @@ -142,7 +143,7 @@ "Password confirmation is required" : "Nepieciešams paroles apstiprinājums", "Server-side encryption" : "Servera šifrēšana", "Enable server-side encryption" : "Ieslēgt servera šifrēšanu", - "No encryption module loaded, please enable an encryption module in the app menu." : "Nav ielādēts šifrēšanas moduļis, lūdzu, aktivizējiet šifrēšanas moduli lietotņu izvēlnē.", + "No encryption module loaded, please enable an encryption module in the app menu." : "Nav ielādēts neviens šifrēšanas modulis, lūgums iespējot šifrēšanas moduli lietotņu izvēlnē.", "Select default encryption module:" : "Atlasīt noklusējuma šifrēšanas moduli:", "Enable encryption" : "Ieslēgt šifrēšanu", "Encryption alone does not guarantee security of the system. Please see documentation for more information about how the encryption app works, and the supported use cases." : "Šifrēšana vien negarantē sistēmas drošību. Skatiet dokumentāciju, lai iegūtu papildinformāciju par šifrēšanas lietotnes izmantošanu un atbalstītajiem izmantošanas veidiem.", @@ -161,7 +162,7 @@ "You are using {s}{usage}{/s} of {s}{totalSpace}{/s} ({s}{usageRelative}%{/s})" : "Tu izmanto {s}{usage}{/s} no {s}{totalSpace}{/s} ({s}{usageRelative}%{/s})", "You are a member of the following groups:" : "Tu esi zemāk uzskaitīto kopu dalībnieks:", "This address is not confirmed" : "Šī adrese nav apstiprināta", - "Primary email for password reset and notifications" : "Primārā e-pasta adrese paroles atjaunošanai un paziņojumiem", + "Primary email for password reset and notifications" : "Galvenā e-pasta adrese paroles atiestatīšanai un paziņojumiem", "No email address set" : "Nav norādīts e-pasts", "Help translate" : "Palīdzi tulkot", "Unable to update locale" : "Nevarēja atjaunināt lokalizāciju", @@ -222,6 +223,7 @@ "Hide" : "Paslēpt", "Download and enable" : "Lejupielādēt un iespējot", "Disable" : "Deaktivēt", + "Allow untested app" : "Atļaut nepārbaudītu lietotni", "Unknown" : "Nezināms", "Never" : "Nekad", "Do you really want to wipe your data from this device?" : "Vai tiešām izdzēst datus šajā ierīcē?", diff --git a/apps/settings/l10n/pt_BR.js b/apps/settings/l10n/pt_BR.js index 848734a4dbb..b676cd4e35b 100644 --- a/apps/settings/l10n/pt_BR.js +++ b/apps/settings/l10n/pt_BR.js @@ -395,6 +395,7 @@ OC.L10N.register( "Default expiration time of remote shares in days" : "Tempo de expiração padrão de compartilhamentos remotos em dias", "Expire remote shares after x days" : "Expiração de compartilhamentos remotos após x dias", "Set default expiration date for shares via link or mail" : "Definir a data de expiração padrão para compartilhamentos via link ou e-mail", + "Enforce expiration date for link or mail shares" : "Impor data de expiração para compartilhamentos de link ou e-mail", "Default expiration time of shares in days" : "Tempo de expiração padrão dos compartilhamentos em dias", "Privacy settings for sharing" : "Configurações de privacidade para compartilhamento", "Allow account name autocompletion in share dialog and allow access to the system address book" : "Permitir o preenchimento automático de nomes das contas na caixa de diálogo de compartilhamento e permitir o acesso ao catálogo de endereços do sistema", diff --git a/apps/settings/l10n/pt_BR.json b/apps/settings/l10n/pt_BR.json index 9ed68c369c2..891f8fa6d5f 100644 --- a/apps/settings/l10n/pt_BR.json +++ b/apps/settings/l10n/pt_BR.json @@ -393,6 +393,7 @@ "Default expiration time of remote shares in days" : "Tempo de expiração padrão de compartilhamentos remotos em dias", "Expire remote shares after x days" : "Expiração de compartilhamentos remotos após x dias", "Set default expiration date for shares via link or mail" : "Definir a data de expiração padrão para compartilhamentos via link ou e-mail", + "Enforce expiration date for link or mail shares" : "Impor data de expiração para compartilhamentos de link ou e-mail", "Default expiration time of shares in days" : "Tempo de expiração padrão dos compartilhamentos em dias", "Privacy settings for sharing" : "Configurações de privacidade para compartilhamento", "Allow account name autocompletion in share dialog and allow access to the system address book" : "Permitir o preenchimento automático de nomes das contas na caixa de diálogo de compartilhamento e permitir o acesso ao catálogo de endereços do sistema", diff --git a/apps/settings/l10n/uk.js b/apps/settings/l10n/uk.js index 92fdadf224b..856aa098e8a 100644 --- a/apps/settings/l10n/uk.js +++ b/apps/settings/l10n/uk.js @@ -395,6 +395,7 @@ OC.L10N.register( "Default expiration time of remote shares in days" : "Типовий термін дії спільних ресурсів у днях", "Expire remote shares after x days" : "Термін дії спільних ресурсів завершується через x днів", "Set default expiration date for shares via link or mail" : "Встановити типовий термін дії спільних ресурсів за посиланням або електронною поштою", + "Enforce expiration date for link or mail shares" : "Застосовувати термін дії для посилань або спільного доступу до пошти", "Default expiration time of shares in days" : "Типовий термін дії спільних ресурсів у днях", "Privacy settings for sharing" : "Налаштування конфіденційності для спільного доступу", "Allow account name autocompletion in share dialog and allow access to the system address book" : "Дозволити автозаповнення імени користувача та доступ до системної адресної книги", diff --git a/apps/settings/l10n/uk.json b/apps/settings/l10n/uk.json index 6ff8ab9eeaa..b405793fb1e 100644 --- a/apps/settings/l10n/uk.json +++ b/apps/settings/l10n/uk.json @@ -393,6 +393,7 @@ "Default expiration time of remote shares in days" : "Типовий термін дії спільних ресурсів у днях", "Expire remote shares after x days" : "Термін дії спільних ресурсів завершується через x днів", "Set default expiration date for shares via link or mail" : "Встановити типовий термін дії спільних ресурсів за посиланням або електронною поштою", + "Enforce expiration date for link or mail shares" : "Застосовувати термін дії для посилань або спільного доступу до пошти", "Default expiration time of shares in days" : "Типовий термін дії спільних ресурсів у днях", "Privacy settings for sharing" : "Налаштування конфіденційності для спільного доступу", "Allow account name autocompletion in share dialog and allow access to the system address book" : "Дозволити автозаповнення імени користувача та доступ до системної адресної книги", diff --git a/apps/settings/l10n/zh_CN.js b/apps/settings/l10n/zh_CN.js index 6b9f224f47f..ae8f90baf91 100644 --- a/apps/settings/l10n/zh_CN.js +++ b/apps/settings/l10n/zh_CN.js @@ -370,6 +370,8 @@ OC.L10N.register( "Users will still be able to screenshot or record the screen. This does not provide any definitive protection." : "用户仍然可以屏幕截图或录制屏幕。这并不能提供任何明确的保护。", "Allow users to share via link and emails" : "允许用户通过链接和电子邮件共享", "Allow public uploads" : "允许公开上传", + "Allow public shares to be added to other clouds by federation." : "允许通过联邦方式将公开共享添加到其他云", + "This will add share permissions to all newly created link shares." : "这将为所有新创建的链接共享添加共享权限。", "Always ask for a password" : "始终要求输入密码", "Enforce password protection" : "强制密码保护", "Exclude groups from password requirements" : "不对指定的组执行密码要求", @@ -393,6 +395,7 @@ OC.L10N.register( "Default expiration time of remote shares in days" : "远程共享的默认过期时间(天)", "Expire remote shares after x days" : "远程共享在X天后过期", "Set default expiration date for shares via link or mail" : "设置通过链接或电子邮件分享的默认过期时间", + "Enforce expiration date for link or mail shares" : "强制设置链接或邮件共享的到期日期", "Default expiration time of shares in days" : "默认的共享过期时间(天)", "Privacy settings for sharing" : "共享的隐私设置", "Allow account name autocompletion in share dialog and allow access to the system address book" : "允许在共享对话框中自动完成帐户名称并允许访问系统地址簿", @@ -667,8 +670,8 @@ OC.L10N.register( "Unable to update {property}" : "无法更新 {property}", "No {property} set" : "没有设置 {property}", "Change scope level of {property}, current scope is {scope}" : "更改 {property} 的范围级别,当前范围为 {scope}", - "Unable to update federation scope of the primary {property}" : "无法更新主要 {property} 的联盟范围", - "Unable to update federation scope of additional {property}" : "无法更新额外 {property} 的联盟范围", + "Unable to update federation scope of the primary {property}" : "无法更新主 {property} 的联邦范围", + "Unable to update federation scope of additional {property}" : "无法更新附加 {property} 的联邦范围", "Add additional email" : "添加额外的电子邮箱", "Add" : "添加", "Create" : "创建", @@ -812,7 +815,7 @@ OC.L10N.register( "Locale" : "地区语系", "First day of week" : "每周的第一天", "Not available as this property is required for core functionality including file sharing and calendar invitations" : "不可用,因为包括文件共享和日历邀请在内的核心功能需要此属性", - "Not available as federation has been disabled for your account, contact your system administration if you have any questions" : "不可用,因为您的帐号已禁用联合,如果您有任何疑问,请联系您的系统管理员", + "Not available as federation has been disabled for your account, contact your system administration if you have any questions" : "由于您的账号已禁用联合,因此不可用,如有任何疑问,请联系您的系统管理员", "Not available as publishing account specific data to the lookup server is not allowed, contact your system administration if you have any questions" : "不可用,因为不允许将帐户特定数据发布到查找服务器,如果您有任何疑问,请联系您的系统管理员", "Discover" : "发现", "Your apps" : "你的应用", diff --git a/apps/settings/l10n/zh_CN.json b/apps/settings/l10n/zh_CN.json index 997db288cc4..46b523ec1a6 100644 --- a/apps/settings/l10n/zh_CN.json +++ b/apps/settings/l10n/zh_CN.json @@ -368,6 +368,8 @@ "Users will still be able to screenshot or record the screen. This does not provide any definitive protection." : "用户仍然可以屏幕截图或录制屏幕。这并不能提供任何明确的保护。", "Allow users to share via link and emails" : "允许用户通过链接和电子邮件共享", "Allow public uploads" : "允许公开上传", + "Allow public shares to be added to other clouds by federation." : "允许通过联邦方式将公开共享添加到其他云", + "This will add share permissions to all newly created link shares." : "这将为所有新创建的链接共享添加共享权限。", "Always ask for a password" : "始终要求输入密码", "Enforce password protection" : "强制密码保护", "Exclude groups from password requirements" : "不对指定的组执行密码要求", @@ -391,6 +393,7 @@ "Default expiration time of remote shares in days" : "远程共享的默认过期时间(天)", "Expire remote shares after x days" : "远程共享在X天后过期", "Set default expiration date for shares via link or mail" : "设置通过链接或电子邮件分享的默认过期时间", + "Enforce expiration date for link or mail shares" : "强制设置链接或邮件共享的到期日期", "Default expiration time of shares in days" : "默认的共享过期时间(天)", "Privacy settings for sharing" : "共享的隐私设置", "Allow account name autocompletion in share dialog and allow access to the system address book" : "允许在共享对话框中自动完成帐户名称并允许访问系统地址簿", @@ -665,8 +668,8 @@ "Unable to update {property}" : "无法更新 {property}", "No {property} set" : "没有设置 {property}", "Change scope level of {property}, current scope is {scope}" : "更改 {property} 的范围级别,当前范围为 {scope}", - "Unable to update federation scope of the primary {property}" : "无法更新主要 {property} 的联盟范围", - "Unable to update federation scope of additional {property}" : "无法更新额外 {property} 的联盟范围", + "Unable to update federation scope of the primary {property}" : "无法更新主 {property} 的联邦范围", + "Unable to update federation scope of additional {property}" : "无法更新附加 {property} 的联邦范围", "Add additional email" : "添加额外的电子邮箱", "Add" : "添加", "Create" : "创建", @@ -810,7 +813,7 @@ "Locale" : "地区语系", "First day of week" : "每周的第一天", "Not available as this property is required for core functionality including file sharing and calendar invitations" : "不可用,因为包括文件共享和日历邀请在内的核心功能需要此属性", - "Not available as federation has been disabled for your account, contact your system administration if you have any questions" : "不可用,因为您的帐号已禁用联合,如果您有任何疑问,请联系您的系统管理员", + "Not available as federation has been disabled for your account, contact your system administration if you have any questions" : "由于您的账号已禁用联合,因此不可用,如有任何疑问,请联系您的系统管理员", "Not available as publishing account specific data to the lookup server is not allowed, contact your system administration if you have any questions" : "不可用,因为不允许将帐户特定数据发布到查找服务器,如果您有任何疑问,请联系您的系统管理员", "Discover" : "发现", "Your apps" : "你的应用", diff --git a/apps/settings/l10n/zh_TW.js b/apps/settings/l10n/zh_TW.js index 503ec672c1e..c9c99e8ab97 100644 --- a/apps/settings/l10n/zh_TW.js +++ b/apps/settings/l10n/zh_TW.js @@ -395,6 +395,7 @@ OC.L10N.register( "Default expiration time of remote shares in days" : "遠端分享預設到期時間(天)", "Expire remote shares after x days" : "x 天後遠端分享過期", "Set default expiration date for shares via link or mail" : "設定透過連結或郵件分享的預設過期日", + "Enforce expiration date for link or mail shares" : "強制設定連結或郵件分享的到期日", "Default expiration time of shares in days" : "分享預設到期時間(天)", "Privacy settings for sharing" : "分享的隱私設定", "Allow account name autocompletion in share dialog and allow access to the system address book" : "允許在分享對話方塊中自動補齊帳號名稱並允許存取系統通訊錄", diff --git a/apps/settings/l10n/zh_TW.json b/apps/settings/l10n/zh_TW.json index 23d6a3cc553..697673f8a55 100644 --- a/apps/settings/l10n/zh_TW.json +++ b/apps/settings/l10n/zh_TW.json @@ -393,6 +393,7 @@ "Default expiration time of remote shares in days" : "遠端分享預設到期時間(天)", "Expire remote shares after x days" : "x 天後遠端分享過期", "Set default expiration date for shares via link or mail" : "設定透過連結或郵件分享的預設過期日", + "Enforce expiration date for link or mail shares" : "強制設定連結或郵件分享的到期日", "Default expiration time of shares in days" : "分享預設到期時間(天)", "Privacy settings for sharing" : "分享的隱私設定", "Allow account name autocompletion in share dialog and allow access to the system address book" : "允許在分享對話方塊中自動補齊帳號名稱並允許存取系統通訊錄", diff --git a/apps/settings/lib/Controller/AISettingsController.php b/apps/settings/lib/Controller/AISettingsController.php index a3b0a874987..114cbf61514 100644 --- a/apps/settings/lib/Controller/AISettingsController.php +++ b/apps/settings/lib/Controller/AISettingsController.php @@ -12,20 +12,15 @@ use OCA\Settings\Settings\Admin\ArtificialIntelligence; use OCP\AppFramework\Controller; use OCP\AppFramework\Http\Attribute\AuthorizedAdminSetting; use OCP\AppFramework\Http\DataResponse; -use OCP\IConfig; +use OCP\IAppConfig; use OCP\IRequest; class AISettingsController extends Controller { - /** - * @param string $appName - * @param IRequest $request - * @param IConfig $config - */ public function __construct( $appName, IRequest $request, - private IConfig $config, + private IAppConfig $appConfig, ) { parent::__construct($appName, $request); } @@ -43,7 +38,7 @@ class AISettingsController extends Controller { if (!isset($settings[$key])) { continue; } - $this->config->setAppValue('core', $key, json_encode($settings[$key])); + $this->appConfig->setValueString('core', $key, json_encode($settings[$key]), lazy: in_array($key, \OC\TaskProcessing\Manager::LAZY_CONFIG_KEYS, true)); } return new DataResponse(); diff --git a/apps/settings/lib/Settings/Admin/ArtificialIntelligence.php b/apps/settings/lib/Settings/Admin/ArtificialIntelligence.php index 78bec73382d..aaec0049b20 100644 --- a/apps/settings/lib/Settings/Admin/ArtificialIntelligence.php +++ b/apps/settings/lib/Settings/Admin/ArtificialIntelligence.php @@ -10,7 +10,7 @@ namespace OCA\Settings\Settings\Admin; use OCP\AppFramework\Http\TemplateResponse; use OCP\AppFramework\Services\IInitialState; -use OCP\IConfig; +use OCP\IAppConfig; use OCP\IL10N; use OCP\Settings\IDelegatedSettings; use OCP\SpeechToText\ISpeechToTextManager; @@ -28,7 +28,7 @@ use Psr\Log\LoggerInterface; class ArtificialIntelligence implements IDelegatedSettings { public function __construct( - private IConfig $config, + private IAppConfig $appConfig, private IL10N $l, private IInitialState $initialState, private ITranslationManager $translationManager, @@ -145,7 +145,7 @@ class ArtificialIntelligence implements IDelegatedSettings { ]; foreach ($settings as $key => $defaultValue) { $value = $defaultValue; - $json = $this->config->getAppValue('core', $key, ''); + $json = $this->appConfig->getValueString('core', $key, '', lazy: in_array($key, \OC\TaskProcessing\Manager::LAZY_CONFIG_KEYS, true)); if ($json !== '') { try { $value = json_decode($json, true, flags: JSON_THROW_ON_ERROR); diff --git a/apps/settings/src/components/AdminSettingsSharingForm.vue b/apps/settings/src/components/AdminSettingsSharingForm.vue index c582e9febee..b0e142d8480 100644 --- a/apps/settings/src/components/AdminSettingsSharingForm.vue +++ b/apps/settings/src/components/AdminSettingsSharingForm.vue @@ -164,7 +164,7 @@ </NcCheckboxRadioSwitch> <fieldset v-show="settings.allowLinks && settings.defaultExpireDate" id="settings-sharing-api-api-expiration" class="sharing__sub-section"> <NcCheckboxRadioSwitch :checked.sync="settings.enforceExpireDate"> - {{ t('settings', 'Enforce expiration date for remote shares') }} + {{ t('settings', 'Enforce expiration date for link or mail shares') }} </NcCheckboxRadioSwitch> <NcTextField type="number" class="sharing__input" diff --git a/apps/settings/tests/Mailer/NewUserMailHelperTest.php b/apps/settings/tests/Mailer/NewUserMailHelperTest.php index 55520184fc9..f352a2b733d 100644 --- a/apps/settings/tests/Mailer/NewUserMailHelperTest.php +++ b/apps/settings/tests/Mailer/NewUserMailHelperTest.php @@ -256,32 +256,46 @@ class NewUserMailHelperTest extends TestCase { <tr style="padding:0;text-align:left;vertical-align:top"> <th style="Margin:0;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;line-height:1.3;margin:0;padding:0;text-align:left"> <center data-parsed="" style="min-width:490px;width:100%"> - <table class="button btn default primary float-center" style="Margin:0 0 30px 0;border-collapse:collapse;border-spacing:0;display:inline-block;float:none;margin:0 0 30px 0;margin-right:15px;border-radius:8px;max-width:300px;padding:0;text-align:center;vertical-align:top;width:auto;background:#00679e;background-color:#00679e;color:#fefefe;"> - <tr style="padding:0;text-align:left;vertical-align:top"> - <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border-collapse:collapse!important;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:normal;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word"> - <table style="border-collapse:collapse;border-spacing:0;padding:0;text-align:left;vertical-align:top;width:100%"> + <!--[if (gte mso 9)|(IE)]> + <table> + <tr> + <td> + <![endif]--> + <table class="button btn default primary float-center" style="Margin:0 0 30px 0;border-collapse:collapse;border-spacing:0;display:inline-block;float:none;margin:0 0 30px 0;margin-right:15px;border-radius:8px;max-width:300px;padding:0;text-align:center;vertical-align:top;width:auto;background:#00679e;background-color:#00679e;color:#fefefe;"> <tr style="padding:0;text-align:left;vertical-align:top"> - <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border:0 solid #00679e;border-collapse:collapse!important;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:normal;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word"> - <a href="https://example.com/resetPassword/MySuperLongSecureRandomToken" style="Margin:0;border:0 solid #00679e;color:#ffffff;display:inline-block;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:regular;line-height:normal;margin:0;padding:8px;text-align:left;outline:1px solid #ffffff;text-decoration:none">Set your password</a> + <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border-collapse:collapse!important;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:normal;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word"> + <table style="border-collapse:collapse;border-spacing:0;padding:0;text-align:left;vertical-align:top;width:100%"> + <tr style="padding:0;text-align:left;vertical-align:top"> + <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border:0 solid #00679e;border-collapse:collapse!important;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:normal;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word"> + <a href="https://example.com/resetPassword/MySuperLongSecureRandomToken" style="Margin:0;border:0 solid #00679e;color:#ffffff;display:inline-block;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:regular;line-height:normal;margin:0;padding:8px;text-align:left;outline:1px solid #ffffff;text-decoration:none">Set your password</a> + </td> + </tr> + </table> </td> </tr> </table> + <!--[if (gte mso 9)|(IE)]> </td> - </tr> - </table> - <table class="button btn default secondary float-center" style="Margin:0 0 30px 0;border-collapse:collapse;border-spacing:0;display:inline-block;float:none;background-color: #ccc;margin:0 0 30px 0;max-height:40px;max-width:300px;padding:1px;border-radius:8px;text-align:center;vertical-align:top;width:auto"> - <tr style="padding:0;text-align:left;vertical-align:top"> - <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border-collapse:collapse!important;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:normal;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word"> - <table style="border-collapse:collapse;border-spacing:0;padding:0;text-align:left;vertical-align:top;width:100%"> + <td> + <![endif]--> + <table class="button btn default secondary float-center" style="Margin:0 0 30px 0;border-collapse:collapse;border-spacing:0;display:inline-block;float:none;background-color: #ccc;margin:0 0 30px 0;max-height:40px;max-width:300px;padding:1px;border-radius:8px;text-align:center;vertical-align:top;width:auto"> <tr style="padding:0;text-align:left;vertical-align:top"> - <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border:0 solid #777;border-collapse:collapse!important;color:#fefefe;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:normal;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word"> - <a href="https://nextcloud.com/install/#install-clients" style="Margin:0;background-color:#fff;border:0 solid #777;color:#6C6C6C!important;display:inline-block;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:regular;line-height:normal;margin:0;border-radius: 7px;padding:8px;text-align:left;text-decoration:none">Install Client</a> + <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border-collapse:collapse!important;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:normal;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word"> + <table style="border-collapse:collapse;border-spacing:0;padding:0;text-align:left;vertical-align:top;width:100%"> + <tr style="padding:0;text-align:left;vertical-align:top"> + <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border:0 solid #777;border-collapse:collapse!important;color:#fefefe;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:normal;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word"> + <a href="https://nextcloud.com/install/#install-clients" style="Margin:0;background-color:#fff;border:0 solid #777;color:#6C6C6C!important;display:inline-block;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:regular;line-height:normal;margin:0;border-radius: 7px;padding:8px;text-align:left;text-decoration:none">Install Client</a> + </td> + </tr> + </table> </td> </tr> </table> + <!--[if (gte mso 9)|(IE)]> </td> </tr> </table> + <![endif]--> </center> </th> <th class="expander" style="Margin:0;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;line-height:1.3;margin:0;padding:0!important;text-align:left;visibility:hidden;width:0"></th> @@ -496,32 +510,46 @@ EOF; <tr style="padding:0;text-align:left;vertical-align:top"> <th style="Margin:0;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;line-height:1.3;margin:0;padding:0;text-align:left"> <center data-parsed="" style="min-width:490px;width:100%"> - <table class="button btn default primary float-center" style="Margin:0 0 30px 0;border-collapse:collapse;border-spacing:0;display:inline-block;float:none;margin:0 0 30px 0;margin-right:15px;border-radius:8px;max-width:300px;padding:0;text-align:center;vertical-align:top;width:auto;background:#00679e;background-color:#00679e;color:#fefefe;"> - <tr style="padding:0;text-align:left;vertical-align:top"> - <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border-collapse:collapse!important;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:normal;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word"> - <table style="border-collapse:collapse;border-spacing:0;padding:0;text-align:left;vertical-align:top;width:100%"> + <!--[if (gte mso 9)|(IE)]> + <table> + <tr> + <td> + <![endif]--> + <table class="button btn default primary float-center" style="Margin:0 0 30px 0;border-collapse:collapse;border-spacing:0;display:inline-block;float:none;margin:0 0 30px 0;margin-right:15px;border-radius:8px;max-width:300px;padding:0;text-align:center;vertical-align:top;width:auto;background:#00679e;background-color:#00679e;color:#fefefe;"> <tr style="padding:0;text-align:left;vertical-align:top"> - <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border:0 solid #00679e;border-collapse:collapse!important;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:normal;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word"> - <a href="https://example.com/" style="Margin:0;border:0 solid #00679e;color:#ffffff;display:inline-block;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:regular;line-height:normal;margin:0;padding:8px;text-align:left;outline:1px solid #ffffff;text-decoration:none">Go to TestCloud</a> + <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border-collapse:collapse!important;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:normal;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word"> + <table style="border-collapse:collapse;border-spacing:0;padding:0;text-align:left;vertical-align:top;width:100%"> + <tr style="padding:0;text-align:left;vertical-align:top"> + <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border:0 solid #00679e;border-collapse:collapse!important;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:normal;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word"> + <a href="https://example.com/" style="Margin:0;border:0 solid #00679e;color:#ffffff;display:inline-block;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:regular;line-height:normal;margin:0;padding:8px;text-align:left;outline:1px solid #ffffff;text-decoration:none">Go to TestCloud</a> + </td> + </tr> + </table> </td> </tr> </table> + <!--[if (gte mso 9)|(IE)]> </td> - </tr> - </table> - <table class="button btn default secondary float-center" style="Margin:0 0 30px 0;border-collapse:collapse;border-spacing:0;display:inline-block;float:none;background-color: #ccc;margin:0 0 30px 0;max-height:40px;max-width:300px;padding:1px;border-radius:8px;text-align:center;vertical-align:top;width:auto"> - <tr style="padding:0;text-align:left;vertical-align:top"> - <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border-collapse:collapse!important;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:normal;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word"> - <table style="border-collapse:collapse;border-spacing:0;padding:0;text-align:left;vertical-align:top;width:100%"> + <td> + <![endif]--> + <table class="button btn default secondary float-center" style="Margin:0 0 30px 0;border-collapse:collapse;border-spacing:0;display:inline-block;float:none;background-color: #ccc;margin:0 0 30px 0;max-height:40px;max-width:300px;padding:1px;border-radius:8px;text-align:center;vertical-align:top;width:auto"> <tr style="padding:0;text-align:left;vertical-align:top"> - <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border:0 solid #777;border-collapse:collapse!important;color:#fefefe;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:normal;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word"> - <a href="https://nextcloud.com/install/#install-clients" style="Margin:0;background-color:#fff;border:0 solid #777;color:#6C6C6C!important;display:inline-block;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:regular;line-height:normal;margin:0;border-radius: 7px;padding:8px;text-align:left;text-decoration:none">Install Client</a> + <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border-collapse:collapse!important;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:normal;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word"> + <table style="border-collapse:collapse;border-spacing:0;padding:0;text-align:left;vertical-align:top;width:100%"> + <tr style="padding:0;text-align:left;vertical-align:top"> + <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border:0 solid #777;border-collapse:collapse!important;color:#fefefe;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:normal;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word"> + <a href="https://nextcloud.com/install/#install-clients" style="Margin:0;background-color:#fff;border:0 solid #777;color:#6C6C6C!important;display:inline-block;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:regular;line-height:normal;margin:0;border-radius: 7px;padding:8px;text-align:left;text-decoration:none">Install Client</a> + </td> + </tr> + </table> </td> </tr> </table> + <!--[if (gte mso 9)|(IE)]> </td> </tr> </table> + <![endif]--> </center> </th> <th class="expander" style="Margin:0;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;line-height:1.3;margin:0;padding:0!important;text-align:left;visibility:hidden;width:0"></th> @@ -725,32 +753,46 @@ EOF; <tr style="padding:0;text-align:left;vertical-align:top"> <th style="Margin:0;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;line-height:1.3;margin:0;padding:0;text-align:left"> <center data-parsed="" style="min-width:490px;width:100%"> - <table class="button btn default primary float-center" style="Margin:0 0 30px 0;border-collapse:collapse;border-spacing:0;display:inline-block;float:none;margin:0 0 30px 0;margin-right:15px;border-radius:8px;max-width:300px;padding:0;text-align:center;vertical-align:top;width:auto;background:#00679e;background-color:#00679e;color:#fefefe;"> - <tr style="padding:0;text-align:left;vertical-align:top"> - <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border-collapse:collapse!important;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:normal;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word"> - <table style="border-collapse:collapse;border-spacing:0;padding:0;text-align:left;vertical-align:top;width:100%"> + <!--[if (gte mso 9)|(IE)]> + <table> + <tr> + <td> + <![endif]--> + <table class="button btn default primary float-center" style="Margin:0 0 30px 0;border-collapse:collapse;border-spacing:0;display:inline-block;float:none;margin:0 0 30px 0;margin-right:15px;border-radius:8px;max-width:300px;padding:0;text-align:center;vertical-align:top;width:auto;background:#00679e;background-color:#00679e;color:#fefefe;"> <tr style="padding:0;text-align:left;vertical-align:top"> - <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border:0 solid #00679e;border-collapse:collapse!important;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:normal;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word"> - <a href="https://example.com/" style="Margin:0;border:0 solid #00679e;color:#ffffff;display:inline-block;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:regular;line-height:normal;margin:0;padding:8px;text-align:left;outline:1px solid #ffffff;text-decoration:none">Go to TestCloud</a> + <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border-collapse:collapse!important;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:normal;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word"> + <table style="border-collapse:collapse;border-spacing:0;padding:0;text-align:left;vertical-align:top;width:100%"> + <tr style="padding:0;text-align:left;vertical-align:top"> + <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border:0 solid #00679e;border-collapse:collapse!important;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:normal;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word"> + <a href="https://example.com/" style="Margin:0;border:0 solid #00679e;color:#ffffff;display:inline-block;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:regular;line-height:normal;margin:0;padding:8px;text-align:left;outline:1px solid #ffffff;text-decoration:none">Go to TestCloud</a> + </td> + </tr> + </table> </td> </tr> </table> + <!--[if (gte mso 9)|(IE)]> </td> - </tr> - </table> - <table class="button btn default secondary float-center" style="Margin:0 0 30px 0;border-collapse:collapse;border-spacing:0;display:inline-block;float:none;background-color: #ccc;margin:0 0 30px 0;max-height:40px;max-width:300px;padding:1px;border-radius:8px;text-align:center;vertical-align:top;width:auto"> - <tr style="padding:0;text-align:left;vertical-align:top"> - <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border-collapse:collapse!important;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:normal;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word"> - <table style="border-collapse:collapse;border-spacing:0;padding:0;text-align:left;vertical-align:top;width:100%"> + <td> + <![endif]--> + <table class="button btn default secondary float-center" style="Margin:0 0 30px 0;border-collapse:collapse;border-spacing:0;display:inline-block;float:none;background-color: #ccc;margin:0 0 30px 0;max-height:40px;max-width:300px;padding:1px;border-radius:8px;text-align:center;vertical-align:top;width:auto"> <tr style="padding:0;text-align:left;vertical-align:top"> - <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border:0 solid #777;border-collapse:collapse!important;color:#fefefe;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:normal;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word"> - <a href="https://nextcloud.com/install/#install-clients" style="Margin:0;background-color:#fff;border:0 solid #777;color:#6C6C6C!important;display:inline-block;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:regular;line-height:normal;margin:0;border-radius: 7px;padding:8px;text-align:left;text-decoration:none">Install Client</a> + <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border-collapse:collapse!important;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:normal;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word"> + <table style="border-collapse:collapse;border-spacing:0;padding:0;text-align:left;vertical-align:top;width:100%"> + <tr style="padding:0;text-align:left;vertical-align:top"> + <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border:0 solid #777;border-collapse:collapse!important;color:#fefefe;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:normal;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word"> + <a href="https://nextcloud.com/install/#install-clients" style="Margin:0;background-color:#fff;border:0 solid #777;color:#6C6C6C!important;display:inline-block;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:regular;line-height:normal;margin:0;border-radius: 7px;padding:8px;text-align:left;text-decoration:none">Install Client</a> + </td> + </tr> + </table> </td> </tr> </table> + <!--[if (gte mso 9)|(IE)]> </td> </tr> </table> + <![endif]--> </center> </th> <th class="expander" style="Margin:0;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;line-height:1.3;margin:0;padding:0!important;text-align:left;visibility:hidden;width:0"></th> diff --git a/apps/user_status/l10n/zh_CN.js b/apps/user_status/l10n/zh_CN.js index 1bf11fddfff..c36ad38c713 100644 --- a/apps/user_status/l10n/zh_CN.js +++ b/apps/user_status/l10n/zh_CN.js @@ -10,26 +10,27 @@ OC.L10N.register( "Out of office" : "不在办公室", "Working remotely" : "远程办公中", "In a call" : "通话中", + "Be right back" : "马上回来", "User status" : "用户状态", "Clear status after" : "清除状态于", - "Emoji for your status message" : "表示状态信息的表情符号", - "What is your status?" : "你什么状态?", - "Predefined statuses" : "预定义的状态", - "Previously set" : "先前设定", + "Emoji for your status message" : "状态消息的表情符号", + "What is your status?" : "您的状态如何?", + "Predefined statuses" : "预定义状态", + "Previously set" : "先前设置", "Reset status" : "重置状态", - "Reset status to \"{icon} {message}\"" : "重置状态为 {icon} {message}", - "Reset status to \"{message}\"" : "重置状态为 {message}", - "Reset status to \"{icon}\"" : "重置状态为 {icon}", + "Reset status to \"{icon} {message}\"" : "将状态重置为“{icon} {message}”", + "Reset status to \"{message}\"" : "将状态重置为“{message}”", + "Reset status to \"{icon}\"" : "将状态重置为“{icon}”", "There was an error saving the status" : "保存状态时出错", "There was an error clearing the status" : "清除状态时出错", - "There was an error reverting the status" : "还原状态时发生错误", + "There was an error reverting the status" : "恢复状态时出错", "Online status" : "在线状态", - "Status message" : "状态信息", + "Status message" : "状态消息", "Set absence period" : "设置缺勤时段", - "Set absence period and replacement" : "设置缺勤时段和替代人员", - "Your status was set automatically" : "您的状态已自动设定", - "Clear status message" : "清除状态信息", - "Set status message" : "设定状态信息", + "Set absence period and replacement" : "设置缺勤时段和接替者", + "Your status was set automatically" : "您的状态已自动设置", + "Clear status message" : "清除状态消息", + "Set status message" : "设置状态消息", "Don't clear" : "不要清除", "Today" : "今天", "This week" : "本周", @@ -38,11 +39,11 @@ OC.L10N.register( "Do not disturb" : "勿扰", "Invisible" : "隐身", "Offline" : "离线", - "Set status" : "设定状态", + "Set status" : "设置状态", "There was an error saving the new status" : "保存新状态时出错", "30 minutes" : "30 分钟", - "1 hour" : "1小时", - "4 hours" : "4小时", + "1 hour" : "1 小时", + "4 hours" : "4 小时", "Busy" : "忙碌", "Mute all notifications" : "静音所有通知", "Appear offline" : "显示为离线" diff --git a/apps/user_status/l10n/zh_CN.json b/apps/user_status/l10n/zh_CN.json index 7157edb0884..8546482d238 100644 --- a/apps/user_status/l10n/zh_CN.json +++ b/apps/user_status/l10n/zh_CN.json @@ -8,26 +8,27 @@ "Out of office" : "不在办公室", "Working remotely" : "远程办公中", "In a call" : "通话中", + "Be right back" : "马上回来", "User status" : "用户状态", "Clear status after" : "清除状态于", - "Emoji for your status message" : "表示状态信息的表情符号", - "What is your status?" : "你什么状态?", - "Predefined statuses" : "预定义的状态", - "Previously set" : "先前设定", + "Emoji for your status message" : "状态消息的表情符号", + "What is your status?" : "您的状态如何?", + "Predefined statuses" : "预定义状态", + "Previously set" : "先前设置", "Reset status" : "重置状态", - "Reset status to \"{icon} {message}\"" : "重置状态为 {icon} {message}", - "Reset status to \"{message}\"" : "重置状态为 {message}", - "Reset status to \"{icon}\"" : "重置状态为 {icon}", + "Reset status to \"{icon} {message}\"" : "将状态重置为“{icon} {message}”", + "Reset status to \"{message}\"" : "将状态重置为“{message}”", + "Reset status to \"{icon}\"" : "将状态重置为“{icon}”", "There was an error saving the status" : "保存状态时出错", "There was an error clearing the status" : "清除状态时出错", - "There was an error reverting the status" : "还原状态时发生错误", + "There was an error reverting the status" : "恢复状态时出错", "Online status" : "在线状态", - "Status message" : "状态信息", + "Status message" : "状态消息", "Set absence period" : "设置缺勤时段", - "Set absence period and replacement" : "设置缺勤时段和替代人员", - "Your status was set automatically" : "您的状态已自动设定", - "Clear status message" : "清除状态信息", - "Set status message" : "设定状态信息", + "Set absence period and replacement" : "设置缺勤时段和接替者", + "Your status was set automatically" : "您的状态已自动设置", + "Clear status message" : "清除状态消息", + "Set status message" : "设置状态消息", "Don't clear" : "不要清除", "Today" : "今天", "This week" : "本周", @@ -36,11 +37,11 @@ "Do not disturb" : "勿扰", "Invisible" : "隐身", "Offline" : "离线", - "Set status" : "设定状态", + "Set status" : "设置状态", "There was an error saving the new status" : "保存新状态时出错", "30 minutes" : "30 分钟", - "1 hour" : "1小时", - "4 hours" : "4小时", + "1 hour" : "1 小时", + "4 hours" : "4 小时", "Busy" : "忙碌", "Mute all notifications" : "静音所有通知", "Appear offline" : "显示为离线" diff --git a/apps/user_status/l10n/zh_TW.js b/apps/user_status/l10n/zh_TW.js index dbb1627765f..c4cd18345a5 100644 --- a/apps/user_status/l10n/zh_TW.js +++ b/apps/user_status/l10n/zh_TW.js @@ -10,6 +10,7 @@ OC.L10N.register( "Out of office" : "不在辦公室", "Working remotely" : "遠端工作", "In a call" : "通話中", + "Be right back" : "馬上回來", "User status" : "使用者狀態", "Clear status after" : "多久後清除狀態", "Emoji for your status message" : "狀態訊息的表情符號", diff --git a/apps/user_status/l10n/zh_TW.json b/apps/user_status/l10n/zh_TW.json index e42a675045d..9e99204b682 100644 --- a/apps/user_status/l10n/zh_TW.json +++ b/apps/user_status/l10n/zh_TW.json @@ -8,6 +8,7 @@ "Out of office" : "不在辦公室", "Working remotely" : "遠端工作", "In a call" : "通話中", + "Be right back" : "馬上回來", "User status" : "使用者狀態", "Clear status after" : "多久後清除狀態", "Emoji for your status message" : "狀態訊息的表情符號", diff --git a/apps/user_status/lib/Capabilities.php b/apps/user_status/lib/Capabilities.php index 0c5dc4e03d2..c3edbc032d6 100644 --- a/apps/user_status/lib/Capabilities.php +++ b/apps/user_status/lib/Capabilities.php @@ -23,7 +23,7 @@ class Capabilities implements ICapability { } /** - * @return array{user_status: array{enabled: bool, restore: bool, supports_emoji: bool}} + * @return array{user_status: array{enabled: bool, restore: bool, supports_emoji: bool, supports_busy: bool}} */ public function getCapabilities() { return [ @@ -31,6 +31,7 @@ class Capabilities implements ICapability { 'enabled' => true, 'restore' => true, 'supports_emoji' => $this->emojiHelper->doesPlatformSupportEmoji(), + 'supports_busy' => true, ], ]; } diff --git a/apps/user_status/openapi.json b/apps/user_status/openapi.json index d1018fa26e6..e48d4970b96 100644 --- a/apps/user_status/openapi.json +++ b/apps/user_status/openapi.json @@ -31,7 +31,8 @@ "required": [ "enabled", "restore", - "supports_emoji" + "supports_emoji", + "supports_busy" ], "properties": { "enabled": { @@ -42,6 +43,9 @@ }, "supports_emoji": { "type": "boolean" + }, + "supports_busy": { + "type": "boolean" } } } diff --git a/apps/user_status/tests/Unit/CapabilitiesTest.php b/apps/user_status/tests/Unit/CapabilitiesTest.php index f07892ff3fd..601fb207df4 100644 --- a/apps/user_status/tests/Unit/CapabilitiesTest.php +++ b/apps/user_status/tests/Unit/CapabilitiesTest.php @@ -35,6 +35,7 @@ class CapabilitiesTest extends TestCase { 'enabled' => true, 'restore' => true, 'supports_emoji' => $supportsEmojis, + 'supports_busy' => true, ] ], $this->capabilities->getCapabilities()); } diff --git a/apps/workflowengine/composer/composer/autoload_classmap.php b/apps/workflowengine/composer/composer/autoload_classmap.php index 52d221fb767..0444cce13e7 100644 --- a/apps/workflowengine/composer/composer/autoload_classmap.php +++ b/apps/workflowengine/composer/composer/autoload_classmap.php @@ -10,7 +10,6 @@ return array( 'OCA\\WorkflowEngine\\AppInfo\\Application' => $baseDir . '/../lib/AppInfo/Application.php', 'OCA\\WorkflowEngine\\BackgroundJobs\\Rotate' => $baseDir . '/../lib/BackgroundJobs/Rotate.php', 'OCA\\WorkflowEngine\\Check\\AbstractStringCheck' => $baseDir . '/../lib/Check/AbstractStringCheck.php', - 'OCA\\WorkflowEngine\\Check\\Directory' => $baseDir . '/../lib/Check/Directory.php', 'OCA\\WorkflowEngine\\Check\\FileMimeType' => $baseDir . '/../lib/Check/FileMimeType.php', 'OCA\\WorkflowEngine\\Check\\FileName' => $baseDir . '/../lib/Check/FileName.php', 'OCA\\WorkflowEngine\\Check\\FileSize' => $baseDir . '/../lib/Check/FileSize.php', diff --git a/apps/workflowengine/composer/composer/autoload_static.php b/apps/workflowengine/composer/composer/autoload_static.php index 57b569dbc10..0b9ac89ae30 100644 --- a/apps/workflowengine/composer/composer/autoload_static.php +++ b/apps/workflowengine/composer/composer/autoload_static.php @@ -25,7 +25,6 @@ class ComposerStaticInitWorkflowEngine 'OCA\\WorkflowEngine\\AppInfo\\Application' => __DIR__ . '/..' . '/../lib/AppInfo/Application.php', 'OCA\\WorkflowEngine\\BackgroundJobs\\Rotate' => __DIR__ . '/..' . '/../lib/BackgroundJobs/Rotate.php', 'OCA\\WorkflowEngine\\Check\\AbstractStringCheck' => __DIR__ . '/..' . '/../lib/Check/AbstractStringCheck.php', - 'OCA\\WorkflowEngine\\Check\\Directory' => __DIR__ . '/..' . '/../lib/Check/Directory.php', 'OCA\\WorkflowEngine\\Check\\FileMimeType' => __DIR__ . '/..' . '/../lib/Check/FileMimeType.php', 'OCA\\WorkflowEngine\\Check\\FileName' => __DIR__ . '/..' . '/../lib/Check/FileName.php', 'OCA\\WorkflowEngine\\Check\\FileSize' => __DIR__ . '/..' . '/../lib/Check/FileSize.php', diff --git a/apps/workflowengine/l10n/de.js b/apps/workflowengine/l10n/de.js index 8feec57e910..761beaa28d9 100644 --- a/apps/workflowengine/l10n/de.js +++ b/apps/workflowengine/l10n/de.js @@ -97,7 +97,6 @@ OC.L10N.register( "is" : "ist", "is not" : "ist nicht", "File name" : "Dateiname", - "Directory" : "Verzeichnis", "File MIME type" : "Datei MIME-Typ", "File size (upload)" : "Dateigröße (beim Hochladen)", "less" : "weniger", diff --git a/apps/workflowengine/l10n/de.json b/apps/workflowengine/l10n/de.json index 9ded7338f81..eca78d35353 100644 --- a/apps/workflowengine/l10n/de.json +++ b/apps/workflowengine/l10n/de.json @@ -95,7 +95,6 @@ "is" : "ist", "is not" : "ist nicht", "File name" : "Dateiname", - "Directory" : "Verzeichnis", "File MIME type" : "Datei MIME-Typ", "File size (upload)" : "Dateigröße (beim Hochladen)", "less" : "weniger", diff --git a/apps/workflowengine/l10n/de_DE.js b/apps/workflowengine/l10n/de_DE.js index 8cf48150dad..882d975e932 100644 --- a/apps/workflowengine/l10n/de_DE.js +++ b/apps/workflowengine/l10n/de_DE.js @@ -97,7 +97,6 @@ OC.L10N.register( "is" : "ist", "is not" : "ist nicht", "File name" : "Dateiname", - "Directory" : "Verzeichnis", "File MIME type" : "Datei MIME-Typ", "File size (upload)" : "Dateigröße (beim Hochladen)", "less" : "weniger", diff --git a/apps/workflowengine/l10n/de_DE.json b/apps/workflowengine/l10n/de_DE.json index 253ed3df8d2..26b952586ba 100644 --- a/apps/workflowengine/l10n/de_DE.json +++ b/apps/workflowengine/l10n/de_DE.json @@ -95,7 +95,6 @@ "is" : "ist", "is not" : "ist nicht", "File name" : "Dateiname", - "Directory" : "Verzeichnis", "File MIME type" : "Datei MIME-Typ", "File size (upload)" : "Dateigröße (beim Hochladen)", "less" : "weniger", diff --git a/apps/workflowengine/l10n/en_GB.js b/apps/workflowengine/l10n/en_GB.js index dc97bed2037..971aa8d6415 100644 --- a/apps/workflowengine/l10n/en_GB.js +++ b/apps/workflowengine/l10n/en_GB.js @@ -97,7 +97,6 @@ OC.L10N.register( "is" : "is", "is not" : "is not", "File name" : "File name", - "Directory" : "Directory", "File MIME type" : "File MIME type", "File size (upload)" : "File size (upload)", "less" : "less", diff --git a/apps/workflowengine/l10n/en_GB.json b/apps/workflowengine/l10n/en_GB.json index f6597a1f36a..b4591846e96 100644 --- a/apps/workflowengine/l10n/en_GB.json +++ b/apps/workflowengine/l10n/en_GB.json @@ -95,7 +95,6 @@ "is" : "is", "is not" : "is not", "File name" : "File name", - "Directory" : "Directory", "File MIME type" : "File MIME type", "File size (upload)" : "File size (upload)", "less" : "less", diff --git a/apps/workflowengine/l10n/et_EE.js b/apps/workflowengine/l10n/et_EE.js index 89960752f17..a6dd9bd897e 100644 --- a/apps/workflowengine/l10n/et_EE.js +++ b/apps/workflowengine/l10n/et_EE.js @@ -97,7 +97,6 @@ OC.L10N.register( "is" : "on", "is not" : "ei ole", "File name" : "Failinimi", - "Directory" : "Kaust", "File MIME type" : "Faili MIME-tüüp", "File size (upload)" : "Faili suurus (üleslaadimine)", "less" : "väiksem", diff --git a/apps/workflowengine/l10n/et_EE.json b/apps/workflowengine/l10n/et_EE.json index 5a86411bdb3..3da17c5756d 100644 --- a/apps/workflowengine/l10n/et_EE.json +++ b/apps/workflowengine/l10n/et_EE.json @@ -95,7 +95,6 @@ "is" : "on", "is not" : "ei ole", "File name" : "Failinimi", - "Directory" : "Kaust", "File MIME type" : "Faili MIME-tüüp", "File size (upload)" : "Faili suurus (üleslaadimine)", "less" : "väiksem", diff --git a/apps/workflowengine/l10n/ga.js b/apps/workflowengine/l10n/ga.js index c6f9b29a57d..0c61ee9b09d 100644 --- a/apps/workflowengine/l10n/ga.js +++ b/apps/workflowengine/l10n/ga.js @@ -97,7 +97,6 @@ OC.L10N.register( "is" : "tá", "is not" : "níl", "File name" : "Ainm comhaid", - "Directory" : "Eolaire", "File MIME type" : "Cineál comhaid MIME", "File size (upload)" : "Méid comhaid (uaslódáil)", "less" : "níos lú", diff --git a/apps/workflowengine/l10n/ga.json b/apps/workflowengine/l10n/ga.json index d5f47ae7f97..46ee7bd06a9 100644 --- a/apps/workflowengine/l10n/ga.json +++ b/apps/workflowengine/l10n/ga.json @@ -95,7 +95,6 @@ "is" : "tá", "is not" : "níl", "File name" : "Ainm comhaid", - "Directory" : "Eolaire", "File MIME type" : "Cineál comhaid MIME", "File size (upload)" : "Méid comhaid (uaslódáil)", "less" : "níos lú", diff --git a/apps/workflowengine/l10n/pt_BR.js b/apps/workflowengine/l10n/pt_BR.js index 1404b310f47..8d40914252d 100644 --- a/apps/workflowengine/l10n/pt_BR.js +++ b/apps/workflowengine/l10n/pt_BR.js @@ -97,7 +97,6 @@ OC.L10N.register( "is" : "é", "is not" : "não é", "File name" : "Nome do arquivo", - "Directory" : "Diretório", "File MIME type" : "Tipo de arquivo MIME", "File size (upload)" : "Tamanho do arquivo (upload)", "less" : "menor que", diff --git a/apps/workflowengine/l10n/pt_BR.json b/apps/workflowengine/l10n/pt_BR.json index 1604f631e11..2cbde62dc24 100644 --- a/apps/workflowengine/l10n/pt_BR.json +++ b/apps/workflowengine/l10n/pt_BR.json @@ -95,7 +95,6 @@ "is" : "é", "is not" : "não é", "File name" : "Nome do arquivo", - "Directory" : "Diretório", "File MIME type" : "Tipo de arquivo MIME", "File size (upload)" : "Tamanho do arquivo (upload)", "less" : "menor que", diff --git a/apps/workflowengine/l10n/ru.js b/apps/workflowengine/l10n/ru.js index c00da93134c..6d24bbea78e 100644 --- a/apps/workflowengine/l10n/ru.js +++ b/apps/workflowengine/l10n/ru.js @@ -97,7 +97,6 @@ OC.L10N.register( "is" : "равняется", "is not" : "не равняется", "File name" : "Имя файла", - "Directory" : "Директория", "File MIME type" : "MIME-тип файла", "File size (upload)" : "Размер передаваемого на сервер файла", "less" : "меньше", diff --git a/apps/workflowengine/l10n/ru.json b/apps/workflowengine/l10n/ru.json index c23b9d020b0..228ad6a71ce 100644 --- a/apps/workflowengine/l10n/ru.json +++ b/apps/workflowengine/l10n/ru.json @@ -95,7 +95,6 @@ "is" : "равняется", "is not" : "не равняется", "File name" : "Имя файла", - "Directory" : "Директория", "File MIME type" : "MIME-тип файла", "File size (upload)" : "Размер передаваемого на сервер файла", "less" : "меньше", diff --git a/apps/workflowengine/l10n/uk.js b/apps/workflowengine/l10n/uk.js index f335db33985..a9583382441 100644 --- a/apps/workflowengine/l10n/uk.js +++ b/apps/workflowengine/l10n/uk.js @@ -97,7 +97,6 @@ OC.L10N.register( "is" : "є", "is not" : "не", "File name" : "Ім'я файлу", - "Directory" : "Каталог", "File MIME type" : "Тип MIME файлу", "File size (upload)" : "Розмір файлу (завантаження)", "less" : "менше", diff --git a/apps/workflowengine/l10n/uk.json b/apps/workflowengine/l10n/uk.json index 13674a3b207..93c18f047c3 100644 --- a/apps/workflowengine/l10n/uk.json +++ b/apps/workflowengine/l10n/uk.json @@ -95,7 +95,6 @@ "is" : "є", "is not" : "не", "File name" : "Ім'я файлу", - "Directory" : "Каталог", "File MIME type" : "Тип MIME файлу", "File size (upload)" : "Розмір файлу (завантаження)", "less" : "менше", diff --git a/apps/workflowengine/l10n/zh_HK.js b/apps/workflowengine/l10n/zh_HK.js index bca42dd024f..edd352c1880 100644 --- a/apps/workflowengine/l10n/zh_HK.js +++ b/apps/workflowengine/l10n/zh_HK.js @@ -97,7 +97,6 @@ OC.L10N.register( "is" : "是", "is not" : "不是", "File name" : "檔案名稱", - "Directory" : "目錄", "File MIME type" : "檔案MIME類型", "File size (upload)" : "檔案大小(上傳)", "less" : "更少", diff --git a/apps/workflowengine/l10n/zh_HK.json b/apps/workflowengine/l10n/zh_HK.json index d606da3c02e..9b249cd1af4 100644 --- a/apps/workflowengine/l10n/zh_HK.json +++ b/apps/workflowengine/l10n/zh_HK.json @@ -95,7 +95,6 @@ "is" : "是", "is not" : "不是", "File name" : "檔案名稱", - "Directory" : "目錄", "File MIME type" : "檔案MIME類型", "File size (upload)" : "檔案大小(上傳)", "less" : "更少", diff --git a/apps/workflowengine/lib/Check/Directory.php b/apps/workflowengine/lib/Check/Directory.php deleted file mode 100644 index f7b856a95fe..00000000000 --- a/apps/workflowengine/lib/Check/Directory.php +++ /dev/null @@ -1,58 +0,0 @@ -<?php - -declare(strict_types=1); - -/** - * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ -namespace OCA\WorkflowEngine\Check; - -use OCA\WorkflowEngine\Entity\File; -use OCP\IL10N; -use OCP\WorkflowEngine\IFileCheck; - -class Directory extends AbstractStringCheck implements IFileCheck { - use TFileCheck; - - /** - * @param IL10N $l - */ - public function __construct( - IL10N $l, - ) { - parent::__construct($l); - } - - /** - * @return string - */ - protected function getActualValue(): string { - if ($this->path === null) { - return ''; - } - // files/some/path -> some/path - return preg_replace('/^files\//', '', pathinfo($this->path, PATHINFO_DIRNAME)); - } - - /** - * @param string $operator - * @param string $checkValue - * @param string $actualValue - * @return bool - */ - protected function executeStringCheck($operator, $checkValue, $actualValue) { - if ($operator === 'is' || $operator === '!is') { - $checkValue = ltrim(rtrim($checkValue, '/'), '/'); - } - return parent::executeStringCheck($operator, $checkValue, $actualValue); - } - - public function supportedEntities(): array { - return [ File::class ]; - } - - public function isAvailableForScope(int $scope): bool { - return true; - } -} diff --git a/apps/workflowengine/lib/Manager.php b/apps/workflowengine/lib/Manager.php index 27b25a2e752..0f41679789d 100644 --- a/apps/workflowengine/lib/Manager.php +++ b/apps/workflowengine/lib/Manager.php @@ -8,7 +8,6 @@ namespace OCA\WorkflowEngine; use Doctrine\DBAL\Exception; use OCA\WorkflowEngine\AppInfo\Application; -use OCA\WorkflowEngine\Check\Directory; use OCA\WorkflowEngine\Check\FileMimeType; use OCA\WorkflowEngine\Check\FileName; use OCA\WorkflowEngine\Check\FileSize; @@ -693,7 +692,6 @@ class Manager implements IManager { protected function getBuildInChecks(): array { try { return [ - $this->container->query(Directory::class), $this->container->query(FileMimeType::class), $this->container->query(FileName::class), $this->container->query(FileSize::class), diff --git a/apps/workflowengine/src/components/Checks/file.js b/apps/workflowengine/src/components/Checks/file.js index b2d348e4712..568efc81cd3 100644 --- a/apps/workflowengine/src/components/Checks/file.js +++ b/apps/workflowengine/src/components/Checks/file.js @@ -32,19 +32,6 @@ const FileChecks = [ }, { - class: 'OCA\\WorkflowEngine\\Check\\Directory', - name: t('workflowengine', 'Directory'), - operators: stringOrRegexOperators, - placeholder: (check) => { - if (check.operator === 'matches' || check.operator === '!matches') { - return '/^myfolder/.+$/i' - } - return 'myfolder/subfolder' - }, - validate: stringValidator, - }, - - { class: 'OCA\\WorkflowEngine\\Check\\FileMimeType', name: t('workflowengine', 'File MIME type'), operators: stringOrRegexOperators, diff --git a/apps/workflowengine/tests/Check/DirectoryTest.php b/apps/workflowengine/tests/Check/DirectoryTest.php deleted file mode 100644 index 6eef082b5e5..00000000000 --- a/apps/workflowengine/tests/Check/DirectoryTest.php +++ /dev/null @@ -1,68 +0,0 @@ -<?php - -declare(strict_types=1); -/** - * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -namespace OCA\WorkflowEngine\Tests\Check; - -use OCA\WorkflowEngine\Check\Directory; -use OCA\WorkflowEngine\Entity\File; -use OCP\Files\Storage\IStorage; -use OCP\IL10N; -use Test\TestCase; - -class DirectoryTest extends TestCase { - /** @var IL10N */ - private $l10n; - - /** @var IStorage */ - private $storage; - - /** @var Directory */ - private $directory; - - protected function setUp(): void { - parent::setUp(); - $this->l10n = $this->createMock(IL10N::class); - $this->storage = $this->createMock(IStorage::class); - $this->directory = new Directory($this->l10n); - } - - /** - * @dataProvider dataProviderCheck - */ - public function testExecuteStringCheck(string $operator, string $configuredDirectoryPath, string $filePath, bool $expectedResult): void { - $this->directory->setFileInfo($this->storage, $filePath); - - $result = $this->directory->executeCheck($operator, $configuredDirectoryPath); - - $this->assertEquals($expectedResult, $result); - } - - public function testSupportedEntities(): void { - $this->assertSame([File::class], $this->directory->supportedEntities()); - } - - public function testIsAvailableForScope(): void { - $this->assertTrue($this->directory->isAvailableForScope(1)); - } - - public function dataProviderCheck(): array { - return [ - ['is', 'some/path', 'files/some/path/file.txt', true], - ['is', '/some/path/', 'files/some/path/file.txt', true], - - ['!is', 'some/path', 'files/some/path/file.txt', false], - ['!is', 'some/path/', 'files/someother/path/file.txt', true], - - ['matches', '/^some\/path\/.+$/i', 'files/SomE/PATH/subfolder/file.txt', true], - ['matches', '/some\/path\/.*\/sub2/', 'files/some/path/subfolder1/sub2/anotherfile.pdf', true], - - ['!matches', '/some\/path/', 'files/some/path/file.txt', false], - ['!matches', '/some\/path/', 'files/another/path/file.txt', true], - ]; - } -} |