From 9fb44e34affe0d4d58ecaa137e2f33d884139f11 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Fri, 20 May 2016 15:10:18 +0200 Subject: [PATCH 01/30] add propagator batching --- lib/private/Files/Cache/Propagator.php | 86 ++++++++++++++++++++++++++ lib/public/Files/Cache/IPropagator.php | 19 ++++++ 2 files changed, 105 insertions(+) diff --git a/lib/private/Files/Cache/Propagator.php b/lib/private/Files/Cache/Propagator.php index b998c6bcfa..6be0818f62 100644 --- a/lib/private/Files/Cache/Propagator.php +++ b/lib/private/Files/Cache/Propagator.php @@ -29,6 +29,10 @@ use OCP\IDBConnection; * Propagate etags and mtimes within the storage */ class Propagator implements IPropagator { + private $inBatch = false; + + private $batch = []; + /** * @var \OC\Files\Storage\Storage */ @@ -59,6 +63,13 @@ class Propagator implements IPropagator { $parents = $this->getParents($internalPath); + if ($this->inBatch) { + foreach ($parents as $parent) { + $this->addToBatch($parent, $time, $sizeDifference); + } + return; + } + $parentHashes = array_map('md5', $parents); $etag = uniqid(); // since we give all folders the same etag we don't ask the storage for the etag @@ -98,4 +109,79 @@ class Propagator implements IPropagator { } return $parents; } + + /** + * Mark the beginning of a propagation batch + * + * Note that not all cache setups support propagation in which case this will be a noop + * + * Batching for cache setups that do support it has to be explicit since the cache state is not fully consistent + * before the batch is committed. + */ + public function beginBatch() { + $this->inBatch = true; + } + + private function addToBatch($internalPath, $time, $sizeDifference) { + if (!isset($this->batch[$internalPath])) { + $this->batch[$internalPath] = [ + 'hash' => md5($internalPath), + 'time' => $time, + 'size' => $sizeDifference + ]; + } else { + $this->batch[$internalPath]['size'] += $sizeDifference; + if ($time > $this->batch[$internalPath]['time']) { + $this->batch[$internalPath]['time'] = $time; + } + } + } + + /** + * Commit the active propagation batch + */ + public function commitBatch() { + if (!$this->inBatch) { + throw new \BadMethodCallException('Not in batch'); + } + $this->inBatch = false; + + $this->connection->beginTransaction(); + + $query = $this->connection->getQueryBuilder(); + $storageId = (int)$this->storage->getStorageCache()->getNumericId(); + + $query->update('filecache') + ->set('mtime', $query->createFunction('GREATEST(`mtime`, ' . $query->createParameter('time') . ')')) + ->set('etag', $query->expr()->literal(uniqid())) + ->where($query->expr()->eq('storage', $query->expr()->literal($storageId, IQueryBuilder::PARAM_INT))) + ->andWhere($query->expr()->eq('path_hash', $query->createParameter('hash'))); + + $sizeQuery = $this->connection->getQueryBuilder(); + $sizeQuery->update('filecache') + ->set('size', $sizeQuery->createFunction('`size` + ' . $sizeQuery->createParameter('size'))) + ->where($query->expr()->eq('storage', $query->expr()->literal($storageId, IQueryBuilder::PARAM_INT))) + ->andWhere($query->expr()->eq('path_hash', $query->createParameter('hash'))) + ->andWhere($sizeQuery->expr()->gt('size', $sizeQuery->expr()->literal(-1, IQueryBuilder::PARAM_INT))); + + foreach ($this->batch as $item) { + $query->setParameter('time', $item['time']); + $query->setParameter('hash', $item['hash']); + + $query->execute(); + + if ($item['size']) { + $sizeQuery->setParameter('size', $item['size']); + $sizeQuery->setParameter('hash', $item['hash']); + + $sizeQuery->execute(); + } + } + + $this->batch = []; + + $this->connection->commit(); + } + + } diff --git a/lib/public/Files/Cache/IPropagator.php b/lib/public/Files/Cache/IPropagator.php index 5494ec9a54..541135b9e6 100644 --- a/lib/public/Files/Cache/IPropagator.php +++ b/lib/public/Files/Cache/IPropagator.php @@ -27,6 +27,25 @@ namespace OCP\Files\Cache; * @since 9.0.0 */ interface IPropagator { + /** + * Mark the beginning of a propagation batch + * + * Note that not all cache setups support propagation in which case this will be a noop + * + * Batching for cache setups that do support it has to be explicit since the cache state is not fully consistent + * before the batch is committed. + * + * @since 9.1.0 + */ + public function beginBatch(); + + /** + * Commit the active propagation batch + * + * @since 9.1.0 + */ + public function commitBatch(); + /** * @param string $internalPath * @param int $time From cc67ad4dda201b13109f378da31e66ec5fae0546 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Thu, 26 May 2016 16:04:50 +0200 Subject: [PATCH 02/30] use propagator batching in the scanner --- lib/private/Files/Utils/Scanner.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/private/Files/Utils/Scanner.php b/lib/private/Files/Utils/Scanner.php index 9b55c312e2..be35910471 100644 --- a/lib/private/Files/Utils/Scanner.php +++ b/lib/private/Files/Utils/Scanner.php @@ -138,7 +138,9 @@ class Scanner extends PublicEmitter { $this->triggerPropagator($storage, $path); }); + $storage->getPropagator()->beginBatch(); $scanner->backgroundScan(); + $storage->getPropagator()->commitBatch(); } } @@ -182,12 +184,14 @@ class Scanner extends PublicEmitter { $this->db->beginTransaction(); } try { + $storage->getPropagator()->beginBatch(); $scanner->scan($relativePath, \OC\Files\Cache\Scanner::SCAN_RECURSIVE, \OC\Files\Cache\Scanner::REUSE_ETAG | \OC\Files\Cache\Scanner::REUSE_SIZE); $cache = $storage->getCache(); if ($cache instanceof Cache) { // only re-calculate for the root folder we scanned, anything below that is taken care of by the scanner $cache->correctFolderSize($relativePath); } + $storage->getPropagator()->commitBatch(); } catch (StorageNotAvailableException $e) { $this->logger->error('Storage ' . $storage->getId() . ' not available'); $this->logger->logException($e); From 11900baaf37e827be95687c392fb5461fe95a3d1 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Thu, 2 Jun 2016 14:40:42 +0200 Subject: [PATCH 03/30] add tests for propagator batching --- tests/lib/Files/Cache/PropagatorTest.php | 125 +++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 tests/lib/Files/Cache/PropagatorTest.php diff --git a/tests/lib/Files/Cache/PropagatorTest.php b/tests/lib/Files/Cache/PropagatorTest.php new file mode 100644 index 0000000000..402b29c8c3 --- /dev/null +++ b/tests/lib/Files/Cache/PropagatorTest.php @@ -0,0 +1,125 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +namespace Test\Files\Cache; + +use OC\Files\Storage\Temporary; +use OCP\Files\Cache\ICacheEntry; +use OCP\Files\Storage\IStorage; +use Test\TestCase; + +/** + * @group DB + */ +class PropagatorTest extends TestCase { + /** @var IStorage */ + private $storage; + + public function setUp() { + parent::setUp(); + $this->storage = new Temporary(); + $this->storage->mkdir('foo/bar'); + $this->storage->file_put_contents('foo/bar/file.txt', 'bar'); + $this->storage->getScanner()->scan(''); + } + + /** + * @param $paths + * @return ICacheEntry[] + */ + private function getFileInfos($paths) { + $values = array_map(function ($path) { + return $this->storage->getCache()->get($path); + }, $paths); + return array_combine($paths, $values); + } + + public function testEtagPropagation() { + $paths = ['', 'foo', 'foo/bar']; + $oldInfos = $this->getFileInfos($paths); + $this->storage->getPropagator()->propagateChange('foo/bar/file.txt', time()); + $newInfos = $this->getFileInfos($paths); + + foreach ($oldInfos as $i => $oldInfo) { + $this->assertNotEquals($oldInfo->getEtag(), $newInfos[$i]->getEtag()); + } + } + + public function testTimePropagation() { + $paths = ['', 'foo', 'foo/bar']; + $oldTime = time() - 200; + $targetTime = time() - 100; + $now = time(); + $cache = $this->storage->getCache(); + $cache->put('', ['mtime' => $now]); + $cache->put('foo', ['mtime' => $now]); + $cache->put('foo/bar', ['mtime' => $oldTime]); + $cache->put('foo/bar/file.txt', ['mtime' => $oldTime]); + $this->storage->getPropagator()->propagateChange('foo/bar/file.txt', $targetTime); + $newInfos = $this->getFileInfos($paths); + + $this->assertEquals($targetTime, $newInfos['foo/bar']->getMTime()); + + // dont lower mtimes + $this->assertEquals($now, $newInfos['foo']->getMTime()); + $this->assertEquals($now, $newInfos['']->getMTime()); + } + + public function testSizePropagation() { + $paths = ['', 'foo', 'foo/bar']; + $oldInfos = $this->getFileInfos($paths); + $this->storage->getPropagator()->propagateChange('foo/bar/file.txt', time(), 10); + $newInfos = $this->getFileInfos($paths); + + foreach ($oldInfos as $i => $oldInfo) { + $this->assertEquals($oldInfo->getSize() + 10, $newInfos[$i]->getSize()); + } + } + + public function testBatchedPropagation() { + $this->storage->mkdir('foo/baz'); + $this->storage->mkdir('asd'); + $this->storage->file_put_contents('asd/file.txt', 'bar'); + $this->storage->file_put_contents('foo/baz/file.txt', 'bar'); + $this->storage->getScanner()->scan(''); + + $paths = ['', 'foo', 'foo/bar', 'asd', 'foo/baz']; + + $oldInfos = $this->getFileInfos($paths); + $propagator = $this->storage->getPropagator(); + + $propagator->beginBatch(); + $propagator->propagateChange('asd/file.txt', time(), 10); + $propagator->propagateChange('foo/bar/file.txt', time(), 2); + + $newInfos = $this->getFileInfos($paths); + + // no changes until we finish the batch + foreach ($oldInfos as $i => $oldInfo) { + $this->assertEquals($oldInfo->getSize(), $newInfos[$i]->getSize()); + $this->assertEquals($oldInfo->getEtag(), $newInfos[$i]->getEtag()); + $this->assertEquals($oldInfo->getMTime(), $newInfos[$i]->getMTime()); + } + + $propagator->commitBatch(); + + $newInfos = $this->getFileInfos($paths); + + foreach ($oldInfos as $i => $oldInfo) { + if ($oldInfo->getPath() !== 'foo/baz') { + $this->assertNotEquals($oldInfo->getEtag(), $newInfos[$i]->getEtag()); + } + } + + $this->assertEquals($oldInfos['']->getSize() + 12, $newInfos['']->getSize()); + $this->assertEquals($oldInfos['asd']->getSize() + 10, $newInfos['asd']->getSize()); + $this->assertEquals($oldInfos['foo']->getSize() + 2, $newInfos['foo']->getSize()); + $this->assertEquals($oldInfos['foo/bar']->getSize() + 2, $newInfos['foo/bar']->getSize()); + $this->assertEquals($oldInfos['foo/baz']->getSize(), $newInfos['foo/baz']->getSize()); + } +} From fce19d22d93d8c36066f5fe67efb86bf257a37c7 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Thu, 2 Jun 2016 15:43:43 +0200 Subject: [PATCH 04/30] fix mtime propagation on sqlite --- lib/private/Files/Cache/Propagator.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/private/Files/Cache/Propagator.php b/lib/private/Files/Cache/Propagator.php index 6be0818f62..52bb4dfdfc 100644 --- a/lib/private/Files/Cache/Propagator.php +++ b/lib/private/Files/Cache/Propagator.php @@ -79,7 +79,7 @@ class Propagator implements IPropagator { }, $parentHashes); $builder->update('filecache') - ->set('mtime', $builder->createFunction('GREATEST(`mtime`, ' . $builder->createNamedParameter($time) . ')')) + ->set('mtime', $builder->createFunction('GREATEST(`mtime`, ' . $builder->createNamedParameter($time, IQueryBuilder::PARAM_INT) . ')')) ->set('etag', $builder->createNamedParameter($etag, IQueryBuilder::PARAM_STR)) ->where($builder->expr()->eq('storage', $builder->createNamedParameter($storageId, IQueryBuilder::PARAM_INT))) ->andWhere($builder->expr()->in('path_hash', $hashParams)); @@ -165,13 +165,13 @@ class Propagator implements IPropagator { ->andWhere($sizeQuery->expr()->gt('size', $sizeQuery->expr()->literal(-1, IQueryBuilder::PARAM_INT))); foreach ($this->batch as $item) { - $query->setParameter('time', $item['time']); + $query->setParameter('time', $item['time'], IQueryBuilder::PARAM_INT); $query->setParameter('hash', $item['hash']); $query->execute(); if ($item['size']) { - $sizeQuery->setParameter('size', $item['size']); + $sizeQuery->setParameter('size', $item['size'], IQueryBuilder::PARAM_INT); $sizeQuery->setParameter('hash', $item['hash']); $sizeQuery->execute(); From 1b5368bbaf3a57d6c12fbace78ff7ba0a4c4024b Mon Sep 17 00:00:00 2001 From: Roeland Jago Douma Date: Mon, 6 Jun 2016 14:39:02 +0200 Subject: [PATCH 05/30] Wrap publicwebdav in sharePermission mask Fixes #24868 The writable mask was a bit misleading. We should wrap with the sharepermissions (as they are used everywhere else). The PERMISSIONS_SHARE are added since that is required for the public link check plugin. --- apps/dav/appinfo/v1/publicwebdav.php | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/apps/dav/appinfo/v1/publicwebdav.php b/apps/dav/appinfo/v1/publicwebdav.php index c6aaab2712..07004f43bd 100644 --- a/apps/dav/appinfo/v1/publicwebdav.php +++ b/apps/dav/appinfo/v1/publicwebdav.php @@ -66,14 +66,11 @@ $server = $serverFactory->createServer($baseuri, $requestUri, $authBackend, func $share = $authBackend->getShare(); $owner = $share->getShareOwner(); - $isWritable = $share->getPermissions() & (\OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_CREATE); $fileId = $share->getNodeId(); - if (!$isWritable) { - \OC\Files\Filesystem::addStorageWrapper('readonly', function ($mountPoint, $storage) { - return new \OC\Files\Storage\Wrapper\PermissionsMask(array('storage' => $storage, 'mask' => \OCP\Constants::PERMISSION_READ + \OCP\Constants::PERMISSION_SHARE)); - }); - } + \OC\Files\Filesystem::addStorageWrapper('sharePermissions', function ($mountPoint, $storage) use ($share) { + return new \OC\Files\Storage\Wrapper\PermissionsMask(array('storage' => $storage, 'mask' => $share->getPermissions() | \OCP\Constants::PERMISSION_SHARE)); + }); OC_Util::setupFS($owner); $ownerView = \OC\Files\Filesystem::getView(); From 7c040c0bf97c2d3a63051a819dc480403d3b96fb Mon Sep 17 00:00:00 2001 From: Roeland Jago Douma Date: Tue, 7 Jun 2016 12:50:12 +0200 Subject: [PATCH 06/30] Show the path relative to the requesting user A share can only be requested by 3 'types' of people * owner * initiator * recipient So we have to get the path as the current user. Since that is the only path that has any meaning to the user. --- apps/files_sharing/lib/API/Share20OCS.php | 24 +++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/apps/files_sharing/lib/API/Share20OCS.php b/apps/files_sharing/lib/API/Share20OCS.php index 90e1f19130..53b27aae0b 100644 --- a/apps/files_sharing/lib/API/Share20OCS.php +++ b/apps/files_sharing/lib/API/Share20OCS.php @@ -100,15 +100,8 @@ class Share20OCS { */ protected function formatShare(\OCP\Share\IShare $share) { $sharedBy = $this->userManager->get($share->getSharedBy()); - // for federated shares the owner can be a remote user, in this - // case we use the initiator - if ($this->userManager->userExists($share->getShareOwner())) { - $shareOwner = $this->userManager->get($share->getShareOwner()); - $localUser = $share->getShareOwner(); - } else { - $shareOwner = $this->userManager->get($share->getSharedBy()); - $localUser = $share->getSharedBy(); - } + $shareOwner = $this->userManager->get($share->getShareOwner()); + $result = [ 'id' => $share->getId(), 'share_type' => $share->getShareType(), @@ -123,8 +116,16 @@ class Share20OCS { 'displayname_file_owner' => $shareOwner !== null ? $shareOwner->getDisplayName() : $share->getShareOwner(), ]; - $node = $share->getNode(); - $result['path'] = $this->rootFolder->getUserFolder($localUser)->getRelativePath($node->getPath()); + $userFolder = $this->rootFolder->getUserFolder($this->currentUser->getUID()); + $nodes = $userFolder->getById($share->getNodeId()); + + if (empty($nodes)) { + throw new NotFoundException(); + } + + $node = $nodes[0]; + + $result['path'] = $userFolder->getRelativePath($node->getPath()); if ($node instanceOf \OCP\Files\Folder) { $result['item_type'] = 'folder'; } else { @@ -536,7 +537,6 @@ class Share20OCS { $shares = array_merge($shares, $federatedShares); } - $formatted = []; foreach ($shares as $share) { try { From 2e2ece753f4474d4ba0edbf22e4984335eec51ff Mon Sep 17 00:00:00 2001 From: Roeland Jago Douma Date: Tue, 7 Jun 2016 14:17:22 +0200 Subject: [PATCH 07/30] Fix unit tests --- .../tests/API/Share20OCSTest.php | 21 ++++++++++++++++--- apps/files_sharing/tests/ApiTest.php | 9 +++----- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/apps/files_sharing/tests/API/Share20OCSTest.php b/apps/files_sharing/tests/API/Share20OCSTest.php index 02b16d7bf8..b760a0f47a 100644 --- a/apps/files_sharing/tests/API/Share20OCSTest.php +++ b/apps/files_sharing/tests/API/Share20OCSTest.php @@ -433,8 +433,12 @@ class Share20OCSTest extends \Test\TestCase { ->method('getRelativePath') ->will($this->returnArgument(0)); + $userFolder->method('getById') + ->with($share->getNodeId()) + ->willReturn([$share->getNode()]); + $this->rootFolder->method('getUserFolder') - ->with($share->getShareOwner()) + ->with($this->currentUser->getUID()) ->willReturn($userFolder); $this->urlGenerator @@ -2006,8 +2010,19 @@ class Share20OCSTest extends \Test\TestCase { ->willReturn('myLink'); - $this->rootFolder->method('getUserFolder')->with($share->getShareOwner())->will($this->returnSelf()); - $this->rootFolder->method('getRelativePath')->will($this->returnArgument(0)); + $this->rootFolder->method('getUserFolder') + ->with($this->currentUser->getUID()) + ->will($this->returnSelf()); + + if (!$exception) { + $this->rootFolder->method('getById') + ->with($share->getNodeId()) + ->willReturn([$share->getNode()]); + + $this->rootFolder->method('getRelativePath') + ->with($share->getNode()->getPath()) + ->will($this->returnArgument(0)); + } try { $result = $this->invokePrivate($this->ocs, 'formatShare', [$share]); diff --git a/apps/files_sharing/tests/ApiTest.php b/apps/files_sharing/tests/ApiTest.php index f44c346236..b1a9e0dfbf 100644 --- a/apps/files_sharing/tests/ApiTest.php +++ b/apps/files_sharing/tests/ApiTest.php @@ -764,8 +764,7 @@ class ApiTest extends TestCase { // we should get exactly one result $this->assertCount(1, $data); - $expectedPath = $this->folder . $this->subfolder; - $this->assertEquals($expectedPath, $data[0]['path']); + $this->assertEquals($this->subfolder, $data[0]['path']); $this->shareManager->deleteShare($share2); $this->shareManager->deleteShare($share1); @@ -812,8 +811,7 @@ class ApiTest extends TestCase { // we should get exactly one result $this->assertCount(1, $data); - $expectedPath = $this->folder . $this->subfolder . $this->subsubfolder; - $this->assertEquals($expectedPath, $data[0]['path']); + $this->assertEquals($this->subsubfolder, $data[0]['path']); $this->shareManager->deleteShare($share1); $this->shareManager->deleteShare($share2); @@ -922,8 +920,7 @@ class ApiTest extends TestCase { // we should get exactly one result $this->assertCount(1, $data); - $expectedPath = $this->folder.$this->subfolder.$this->filename; - $this->assertEquals($expectedPath, $data[0]['path']); + $this->assertEquals($this->filename, $data[0]['path']); $this->shareManager->deleteShare($share1); $this->shareManager->deleteShare($share2); From 72c5535492f10024f25f4b641c4c481f203a2872 Mon Sep 17 00:00:00 2001 From: Roeland Jago Douma Date: Tue, 7 Jun 2016 14:21:48 +0200 Subject: [PATCH 08/30] Extend unit tests --- apps/files_sharing/tests/ApiTest.php | 34 +++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/apps/files_sharing/tests/ApiTest.php b/apps/files_sharing/tests/ApiTest.php index b1a9e0dfbf..058b0c4758 100644 --- a/apps/files_sharing/tests/ApiTest.php +++ b/apps/files_sharing/tests/ApiTest.php @@ -800,6 +800,9 @@ class ApiTest extends TestCase { ->setPermissions(1); $share3 = $this->shareManager->createShare($share3); + /* + * Test as recipient + */ $request = $this->createRequest(['path' => '/', 'subfiles' => 'true']); $ocs = $this->createOCS($request, self::TEST_FILES_SHARING_API_USER3); $result = $ocs->getShares(); @@ -810,9 +813,38 @@ class ApiTest extends TestCase { // we should get exactly one result $this->assertCount(1, $data); - $this->assertEquals($this->subsubfolder, $data[0]['path']); + /* + * Test for first owner/initiator + */ + $request = $this->createRequest([]); + $ocs = $this->createOCS($request, self::TEST_FILES_SHARING_API_USER1); + $result = $ocs->getShares(); + $this->assertTrue($result->succeeded()); + + // test should return one share within $this->folder + $data = $result->getData(); + + // we should get exactly one result + $this->assertCount(1, $data); + $this->assertEquals($this->folder . $this->subfolder, $data[0]['path']); + + /* + * Test for second initiator + */ + $request = $this->createRequest([]); + $ocs = $this->createOCS($request, self::TEST_FILES_SHARING_API_USER2); + $result = $ocs->getShares(); + $this->assertTrue($result->succeeded()); + + // test should return one share within $this->folder + $data = $result->getData(); + + // we should get exactly one result + $this->assertCount(1, $data); + $this->assertEquals($this->subfolder . $this->subsubfolder, $data[0]['path']); + $this->shareManager->deleteShare($share1); $this->shareManager->deleteShare($share2); $this->shareManager->deleteShare($share3); From c5a6c8b70bbe15de631a472370c783885a943f72 Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Wed, 8 Jun 2016 11:22:01 +0200 Subject: [PATCH 09/30] Use array_merge when reading cached groups members --- apps/user_ldap/lib/Group_LDAP.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/user_ldap/lib/Group_LDAP.php b/apps/user_ldap/lib/Group_LDAP.php index 7c12613f34..14d86fb061 100644 --- a/apps/user_ldap/lib/Group_LDAP.php +++ b/apps/user_ldap/lib/Group_LDAP.php @@ -535,7 +535,7 @@ class Group_LDAP extends BackendUtility implements \OCP\GroupInterface { } if(isset($this->cachedGroupsByMember[$uid])) { - $groups[] = $this->cachedGroupsByMember[$uid]; + $groups = array_merge($groups, $this->cachedGroupsByMember[$uid]); } else { $groupsByMember = array_values($this->getGroupsByMember($uid)); $groupsByMember = $this->access->ownCloudGroupNames($groupsByMember); From fbdec59f22a23a84b36248001bc8680d3746bb7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Tue, 19 Apr 2016 11:33:37 +0200 Subject: [PATCH 10/30] Extract CLASS property from calendar object and store it in the database --- apps/dav/appinfo/database.xml | 6 ++ apps/dav/lib/CalDAV/CalDavBackend.php | 74 +++++++++++++------ .../tests/unit/CalDAV/CalDavBackendTest.php | 15 ++-- 3 files changed, 69 insertions(+), 26 deletions(-) diff --git a/apps/dav/appinfo/database.xml b/apps/dav/appinfo/database.xml index f79ea07ae7..5687158b09 100644 --- a/apps/dav/appinfo/database.xml +++ b/apps/dav/appinfo/database.xml @@ -272,6 +272,12 @@ CREATE TABLE calendarobjects ( text 255 + + 0 - public, 1 - private, 2 - confidential + classification + int + 0 + calobjects_index true diff --git a/apps/dav/lib/CalDAV/CalDavBackend.php b/apps/dav/lib/CalDAV/CalDavBackend.php index 64fdf0f7eb..541a616f6d 100644 --- a/apps/dav/lib/CalDAV/CalDavBackend.php +++ b/apps/dav/lib/CalDAV/CalDavBackend.php @@ -37,10 +37,11 @@ use Sabre\CalDAV\Xml\Property\ScheduleCalendarTransp; use Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet; use Sabre\DAV; use Sabre\DAV\Exception\Forbidden; +use Sabre\DAV\PropPatch; use Sabre\HTTP\URLUtil; use Sabre\VObject\DateTimeParser; use Sabre\VObject\Reader; -use Sabre\VObject\RecurrenceIterator; +use Sabre\VObject\Recur\EventIterator; /** * Class CalDavBackend @@ -61,6 +62,10 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription */ const MAX_DATE = '2038-01-01'; + const CLASSIFICATION_PUBLIC = 0; + const CLASSIFICATION_PRIVATE = 1; + const CLASSIFICATION_CONFIDENTIAL = 2; + /** * List of CalDAV properties, and how they map to database field names * Add your own properties by simply adding on to this array. @@ -395,10 +400,10 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription * * Read the PropPatch documentation for more info and examples. * - * @param \Sabre\DAV\PropPatch $propPatch + * @param PropPatch $propPatch * @return void */ - function updateCalendar($calendarId, \Sabre\DAV\PropPatch $propPatch) { + function updateCalendar($calendarId, PropPatch $propPatch) { $supportedProperties = array_keys($this->propertyMap); $supportedProperties[] = '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp'; @@ -618,6 +623,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription 'componenttype' => $query->createNamedParameter($extraData['componentType']), 'firstoccurence' => $query->createNamedParameter($extraData['firstOccurence']), 'lastoccurence' => $query->createNamedParameter($extraData['lastOccurence']), + 'classification' => $query->createNamedParameter($extraData['classification']), 'uid' => $query->createNamedParameter($extraData['uid']), ]) ->execute(); @@ -657,6 +663,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription ->set('componenttype', $query->createNamedParameter($extraData['componentType'])) ->set('firstoccurence', $query->createNamedParameter($extraData['firstOccurence'])) ->set('lastoccurence', $query->createNamedParameter($extraData['lastOccurence'])) + ->set('classification', $query->createNamedParameter($extraData['classification'])) ->set('uid', $query->createNamedParameter($extraData['uid'])) ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId))) ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri))) @@ -667,6 +674,18 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription return '"' . $extraData['etag'] . '"'; } + /** + * @param int $calendarObjectId + * @param int $classification + */ + public function setClassification($calendarObjectId, $classification) { + $query = $this->db->getQueryBuilder(); + $query->update('calendarobjects') + ->set('classification', $query->createNamedParameter($classification)) + ->where($query->expr()->eq('id', $query->createNamedParameter($calendarObjectId))) + ->execute(); + } + /** * Deletes an existing calendar object. * @@ -1086,10 +1105,10 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription * Read the PropPatch documentation for more info and examples. * * @param mixed $subscriptionId - * @param \Sabre\DAV\PropPatch $propPatch + * @param PropPatch $propPatch * @return void */ - function updateSubscription($subscriptionId, DAV\PropPatch $propPatch) { + function updateSubscription($subscriptionId, PropPatch $propPatch) { $supportedProperties = array_keys($this->subscriptionPropertyMap); $supportedProperties[] = '{http://calendarserver.org/ns/}source'; @@ -1280,14 +1299,15 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription * @param string $calendarData * @return array */ - protected function getDenormalizedData($calendarData) { + public function getDenormalizedData($calendarData) { $vObject = Reader::read($calendarData); $componentType = null; $component = null; - $firstOccurence = null; - $lastOccurence = null; + $firstOccurrence = null; + $lastOccurrence = null; $uid = null; + $classification = self::CLASSIFICATION_PUBLIC; foreach($vObject->getComponents() as $component) { if ($component->name!=='VTIMEZONE') { $componentType = $component->name; @@ -1303,23 +1323,23 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription // Finding the last occurrence is a bit harder if (!isset($component->RRULE)) { if (isset($component->DTEND)) { - $lastOccurence = $component->DTEND->getDateTime()->getTimeStamp(); + $lastOccurrence = $component->DTEND->getDateTime()->getTimeStamp(); } elseif (isset($component->DURATION)) { $endDate = clone $component->DTSTART->getDateTime(); $endDate->add(DateTimeParser::parse($component->DURATION->getValue())); - $lastOccurence = $endDate->getTimeStamp(); + $lastOccurrence = $endDate->getTimeStamp(); } elseif (!$component->DTSTART->hasTime()) { $endDate = clone $component->DTSTART->getDateTime(); $endDate->modify('+1 day'); - $lastOccurence = $endDate->getTimeStamp(); + $lastOccurrence = $endDate->getTimeStamp(); } else { - $lastOccurence = $firstOccurence; + $lastOccurrence = $firstOccurence; } } else { - $it = new RecurrenceIterator($vObject, (string)$component->UID); + $it = new EventIterator($vObject, (string)$component->UID); $maxDate = new \DateTime(self::MAX_DATE); if ($it->isInfinite()) { - $lastOccurence = $maxDate->getTimeStamp(); + $lastOccurrence = $maxDate->getTimeStamp(); } else { $end = $it->getDtEnd(); while($it->valid() && $end < $maxDate) { @@ -1327,19 +1347,31 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription $it->next(); } - $lastOccurence = $end->getTimeStamp(); + $lastOccurrence = $end->getTimeStamp(); } } } + if ($component->CLASS) { + $classification = CalDavBackend::CLASSIFICATION_PRIVATE; + switch ($component->CLASS->getValue()) { + case 'PUBLIC': + $classification = CalDavBackend::CLASSIFICATION_PUBLIC; + break; + case 'CONFIDENTIAL': + $classification = CalDavBackend::CLASSIFICATION_CONFIDENTIAL; + break; + } + } return [ - 'etag' => md5($calendarData), - 'size' => strlen($calendarData), - 'componentType' => $componentType, - 'firstOccurence' => is_null($firstOccurence) ? null : max(0, $firstOccurence), - 'lastOccurence' => $lastOccurence, - 'uid' => $uid, + 'etag' => md5($calendarData), + 'size' => strlen($calendarData), + 'componentType' => $componentType, + 'firstOccurence' => is_null($firstOccurrence) ? null : max(0, $firstOccurrence), + 'lastOccurence' => $lastOccurrence, + 'uid' => $uid, + 'classification' => $classification ]; } diff --git a/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php b/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php index c3e32e436d..4281b88be0 100644 --- a/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php +++ b/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php @@ -464,15 +464,20 @@ EOD; /** * @dataProvider providesCalDataForGetDenormalizedData */ - public function testGetDenormalizedData($expectedFirstOccurance, $calData) { - $actual = $this->invokePrivate($this->backend, 'getDenormalizedData', [$calData]); - $this->assertEquals($expectedFirstOccurance, $actual['firstOccurence']); + public function testGetDenormalizedData($expected, $key, $calData) { + $actual = $this->backend->getDenormalizedData($calData); + $this->assertEquals($expected, $actual[$key]); } public function providesCalDataForGetDenormalizedData() { return [ - [0, "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 3.5.0//EN\r\nCALSCALE:GREGORIAN\r\nBEGIN:VEVENT\r\nUID:413F269B-B51B-46B1-AFB6-40055C53A4DC\r\nDTSTAMP:20160309T095056Z\r\nDTSTART;VALUE=DATE:16040222\r\nDTEND;VALUE=DATE:16040223\r\nRRULE:FREQ=YEARLY\r\nSUMMARY:SUMMARY\r\nTRANSP:TRANSPARENT\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"], - [null, "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 3.5.0//EN\r\nCALSCALE:GREGORIAN\r\nBEGIN:VEVENT\r\nUID:413F269B-B51B-46B1-AFB6-40055C53A4DC\r\nDTSTAMP:20160309T095056Z\r\nRRULE:FREQ=YEARLY\r\nSUMMARY:SUMMARY\r\nTRANSP:TRANSPARENT\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"] + 'first occurrence before unix epoch starts' => [0, 'firstOccurence', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 3.5.0//EN\r\nCALSCALE:GREGORIAN\r\nBEGIN:VEVENT\r\nUID:413F269B-B51B-46B1-AFB6-40055C53A4DC\r\nDTSTAMP:20160309T095056Z\r\nDTSTART;VALUE=DATE:16040222\r\nDTEND;VALUE=DATE:16040223\r\nRRULE:FREQ=YEARLY\r\nSUMMARY:SUMMARY\r\nTRANSP:TRANSPARENT\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"], + 'no first occurrence because yearly' => [null, 'firstOccurence', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 3.5.0//EN\r\nCALSCALE:GREGORIAN\r\nBEGIN:VEVENT\r\nUID:413F269B-B51B-46B1-AFB6-40055C53A4DC\r\nDTSTAMP:20160309T095056Z\r\nRRULE:FREQ=YEARLY\r\nSUMMARY:SUMMARY\r\nTRANSP:TRANSPARENT\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n"], + 'CLASS:PRIVATE' => [CalDavBackend::CLASSIFICATION_PRIVATE, 'classification', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//dmfs.org//mimedir.icalendar//EN\r\nBEGIN:VTIMEZONE\r\nTZID:Europe/Berlin\r\nX-LIC-LOCATION:Europe/Berlin\r\nBEGIN:DAYLIGHT\r\nTZOFFSETFROM:+0100\r\nTZOFFSETTO:+0200\r\nTZNAME:CEST\r\nDTSTART:19700329T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETFROM:+0200\r\nTZOFFSETTO:+0100\r\nTZNAME:CET\r\nDTSTART:19701025T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU\r\nEND:STANDARD\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nDTSTART;TZID=Europe/Berlin:20160419T130000\r\nSUMMARY:Test\r\nCLASS:PRIVATE\r\nTRANSP:OPAQUE\r\nSTATUS:CONFIRMED\r\nDTEND;TZID=Europe/Berlin:20160419T140000\r\nLAST-MODIFIED:20160419T074202Z\r\nDTSTAMP:20160419T074202Z\r\nCREATED:20160419T074202Z\r\nUID:2e468c48-7860-492e-bc52-92fa0daeeccf.1461051722310\r\nEND:VEVENT\r\nEND:VCALENDAR"], + 'CLASS:PUBLIC' => [CalDavBackend::CLASSIFICATION_PUBLIC, 'classification', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//dmfs.org//mimedir.icalendar//EN\r\nBEGIN:VTIMEZONE\r\nTZID:Europe/Berlin\r\nX-LIC-LOCATION:Europe/Berlin\r\nBEGIN:DAYLIGHT\r\nTZOFFSETFROM:+0100\r\nTZOFFSETTO:+0200\r\nTZNAME:CEST\r\nDTSTART:19700329T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETFROM:+0200\r\nTZOFFSETTO:+0100\r\nTZNAME:CET\r\nDTSTART:19701025T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU\r\nEND:STANDARD\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nDTSTART;TZID=Europe/Berlin:20160419T130000\r\nSUMMARY:Test\r\nCLASS:PUBLIC\r\nTRANSP:OPAQUE\r\nSTATUS:CONFIRMED\r\nDTEND;TZID=Europe/Berlin:20160419T140000\r\nLAST-MODIFIED:20160419T074202Z\r\nDTSTAMP:20160419T074202Z\r\nCREATED:20160419T074202Z\r\nUID:2e468c48-7860-492e-bc52-92fa0daeeccf.1461051722310\r\nEND:VEVENT\r\nEND:VCALENDAR"], + 'CLASS:CONFIDENTIAL' => [CalDavBackend::CLASSIFICATION_CONFIDENTIAL, 'classification', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//dmfs.org//mimedir.icalendar//EN\r\nBEGIN:VTIMEZONE\r\nTZID:Europe/Berlin\r\nX-LIC-LOCATION:Europe/Berlin\r\nBEGIN:DAYLIGHT\r\nTZOFFSETFROM:+0100\r\nTZOFFSETTO:+0200\r\nTZNAME:CEST\r\nDTSTART:19700329T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETFROM:+0200\r\nTZOFFSETTO:+0100\r\nTZNAME:CET\r\nDTSTART:19701025T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU\r\nEND:STANDARD\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nDTSTART;TZID=Europe/Berlin:20160419T130000\r\nSUMMARY:Test\r\nCLASS:CONFIDENTIAL\r\nTRANSP:OPAQUE\r\nSTATUS:CONFIRMED\r\nDTEND;TZID=Europe/Berlin:20160419T140000\r\nLAST-MODIFIED:20160419T074202Z\r\nDTSTAMP:20160419T074202Z\r\nCREATED:20160419T074202Z\r\nUID:2e468c48-7860-492e-bc52-92fa0daeeccf.1461051722310\r\nEND:VEVENT\r\nEND:VCALENDAR"], + 'no class set -> public' => [CalDavBackend::CLASSIFICATION_PUBLIC, 'classification', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//dmfs.org//mimedir.icalendar//EN\r\nBEGIN:VTIMEZONE\r\nTZID:Europe/Berlin\r\nX-LIC-LOCATION:Europe/Berlin\r\nBEGIN:DAYLIGHT\r\nTZOFFSETFROM:+0100\r\nTZOFFSETTO:+0200\r\nTZNAME:CEST\r\nDTSTART:19700329T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETFROM:+0200\r\nTZOFFSETTO:+0100\r\nTZNAME:CET\r\nDTSTART:19701025T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU\r\nEND:STANDARD\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nDTSTART;TZID=Europe/Berlin:20160419T130000\r\nSUMMARY:Test\r\nTRANSP:OPAQUE\r\nDTEND;TZID=Europe/Berlin:20160419T140000\r\nLAST-MODIFIED:20160419T074202Z\r\nDTSTAMP:20160419T074202Z\r\nCREATED:20160419T074202Z\r\nUID:2e468c48-7860-492e-bc52-92fa0daeeccf.1461051722310\r\nEND:VEVENT\r\nEND:VCALENDAR"], + 'unknown class -> private' => [CalDavBackend::CLASSIFICATION_PRIVATE, 'classification', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//dmfs.org//mimedir.icalendar//EN\r\nBEGIN:VTIMEZONE\r\nTZID:Europe/Berlin\r\nX-LIC-LOCATION:Europe/Berlin\r\nBEGIN:DAYLIGHT\r\nTZOFFSETFROM:+0100\r\nTZOFFSETTO:+0200\r\nTZNAME:CEST\r\nDTSTART:19700329T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETFROM:+0200\r\nTZOFFSETTO:+0100\r\nTZNAME:CET\r\nDTSTART:19701025T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU\r\nEND:STANDARD\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nDTSTART;TZID=Europe/Berlin:20160419T130000\r\nSUMMARY:Test\r\nCLASS:VERTRAULICH\r\nTRANSP:OPAQUE\r\nSTATUS:CONFIRMED\r\nDTEND;TZID=Europe/Berlin:20160419T140000\r\nLAST-MODIFIED:20160419T074202Z\r\nDTSTAMP:20160419T074202Z\r\nCREATED:20160419T074202Z\r\nUID:2e468c48-7860-492e-bc52-92fa0daeeccf.1461051722310\r\nEND:VEVENT\r\nEND:VCALENDAR"], ]; } From f013cfc5309666729ee657b9084ead9e7f93a772 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Tue, 19 Apr 2016 11:47:06 +0200 Subject: [PATCH 11/30] Add migration step --- apps/dav/appinfo/database.xml | 2 +- apps/dav/appinfo/update.php | 1 + apps/dav/lib/AppInfo/Application.php | 18 ++++++++++ apps/dav/lib/migration/classification.php | 41 +++++++++++++++++++++++ 4 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 apps/dav/lib/migration/classification.php diff --git a/apps/dav/appinfo/database.xml b/apps/dav/appinfo/database.xml index 5687158b09..9578526a70 100644 --- a/apps/dav/appinfo/database.xml +++ b/apps/dav/appinfo/database.xml @@ -275,7 +275,7 @@ CREATE TABLE calendarobjects ( 0 - public, 1 - private, 2 - confidential classification - int + integer 0 diff --git a/apps/dav/appinfo/update.php b/apps/dav/appinfo/update.php index d2ee06cc9f..2941725086 100644 --- a/apps/dav/appinfo/update.php +++ b/apps/dav/appinfo/update.php @@ -24,3 +24,4 @@ use OCA\DAV\AppInfo\Application; $app = new Application(); $app->generateBirthdays(); +$app->migrateClassification(); diff --git a/apps/dav/lib/AppInfo/Application.php b/apps/dav/lib/AppInfo/Application.php index ba0ef421f9..0de61b12e2 100644 --- a/apps/dav/lib/AppInfo/Application.php +++ b/apps/dav/lib/AppInfo/Application.php @@ -31,10 +31,12 @@ use OCA\DAV\CardDAV\SyncService; use OCA\DAV\Connector\Sabre\Principal; use OCA\DAV\DAV\GroupPrincipalBackend; use OCA\DAV\HookManager; +use OCA\DAV\Migration\Classification; use \OCP\AppFramework\App; use OCP\AppFramework\IAppContainer; use OCP\Contacts\IManager; use OCP\IUser; +use Sabre\VObject\Reader; use Symfony\Component\EventDispatcher\GenericEvent; class Application extends App { @@ -168,4 +170,20 @@ class Application extends App { $this->getContainer()->getServer()->getLogger()->logException($ex); } } + + public function migrateClassification() { + try { + /** @var CalDavBackend $calDavBackend */ + $calDavBackend = $this->getContainer()->query('CalDavBackend'); + $migration = new Classification($calDavBackend); + $userManager = $this->getContainer()->getServer()->getUserManager(); + + $userManager->callForAllUsers(function($user) use($migration) { + /** @var IUser $user */ + $migration->runForUser($user); + }); + } catch (\Exception $ex) { + $this->getContainer()->getServer()->getLogger()->logException($ex); + } + } } diff --git a/apps/dav/lib/migration/classification.php b/apps/dav/lib/migration/classification.php new file mode 100644 index 0000000000..48e62e6158 --- /dev/null +++ b/apps/dav/lib/migration/classification.php @@ -0,0 +1,41 @@ +calDavBackend = $calDavBackend; + } + + /** + * @param IUser $user + */ + public function runForUser($user) { + $principal = 'principals/users/' . $user->getUID(); + $calendars = $this->calDavBackend->getCalendarsForUser($principal); + foreach ($calendars as $calendar) { + $objects = $this->calDavBackend->getCalendarObjects($calendar['id']); + foreach ($objects as $object) { + $calObject = $this->calDavBackend->getCalendarObject($calendar['id'], $object['id']); + $classification = $this->extractClassification($calObject['calendardata']); + $this->calDavBackend->setClassification($object['id'], $classification); + } + } + } + + /** + * @param $calObject + */ + protected function extractClassification($calendarData) { + return $this->calDavBackend->getDenormalizedData($calendarData)['classification']; + } +} From 287e41732c1727cd34065cc74d83ddd2bb8b3cd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Tue, 31 May 2016 11:13:45 +0200 Subject: [PATCH 12/30] Bump dav app version and fix variable rename --- apps/dav/appinfo/info.xml | 2 +- apps/dav/lib/CalDAV/CalDavBackend.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/dav/appinfo/info.xml b/apps/dav/appinfo/info.xml index ca456b0308..f749645dfe 100644 --- a/apps/dav/appinfo/info.xml +++ b/apps/dav/appinfo/info.xml @@ -5,7 +5,7 @@ ownCloud WebDAV endpoint AGPL owncloud.org - 0.2.4 + 0.2.5 diff --git a/apps/dav/lib/CalDAV/CalDavBackend.php b/apps/dav/lib/CalDAV/CalDavBackend.php index 541a616f6d..30cfae39fa 100644 --- a/apps/dav/lib/CalDAV/CalDavBackend.php +++ b/apps/dav/lib/CalDAV/CalDavBackend.php @@ -1319,7 +1319,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription throw new \Sabre\DAV\Exception\BadRequest('Calendar objects must have a VJOURNAL, VEVENT or VTODO component'); } if ($componentType === 'VEVENT' && $component->DTSTART) { - $firstOccurence = $component->DTSTART->getDateTime()->getTimeStamp(); + $firstOccurrence = $component->DTSTART->getDateTime()->getTimeStamp(); // Finding the last occurrence is a bit harder if (!isset($component->RRULE)) { if (isset($component->DTEND)) { @@ -1333,7 +1333,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription $endDate->modify('+1 day'); $lastOccurrence = $endDate->getTimeStamp(); } else { - $lastOccurrence = $firstOccurence; + $lastOccurrence = $firstOccurrence; } } else { $it = new EventIterator($vObject, (string)$component->UID); From 369c3b5d7e6d3bae4fae87338cfd3d90f0c72810 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Tue, 31 May 2016 11:50:06 +0200 Subject: [PATCH 13/30] Implement classification migration as repair step --- apps/dav/appinfo/info.xml | 5 ++ apps/dav/appinfo/update.php | 1 - apps/dav/lib/AppInfo/Application.php | 24 ++---- apps/dav/lib/Migration/Classification.php | 93 +++++++++++++++++++++++ apps/dav/lib/migration/classification.php | 41 ---------- lib/public/Migration/IRepairStep.php | 3 +- 6 files changed, 108 insertions(+), 59 deletions(-) create mode 100644 apps/dav/lib/Migration/Classification.php delete mode 100644 apps/dav/lib/migration/classification.php diff --git a/apps/dav/appinfo/info.xml b/apps/dav/appinfo/info.xml index f749645dfe..26e37e6bb8 100644 --- a/apps/dav/appinfo/info.xml +++ b/apps/dav/appinfo/info.xml @@ -20,4 +20,9 @@ OCA\DAV\CardDAV\Sync\SyncJob + + + OCA\DAV\Migration\Classification + + diff --git a/apps/dav/appinfo/update.php b/apps/dav/appinfo/update.php index 2941725086..d2ee06cc9f 100644 --- a/apps/dav/appinfo/update.php +++ b/apps/dav/appinfo/update.php @@ -24,4 +24,3 @@ use OCA\DAV\AppInfo\Application; $app = new Application(); $app->generateBirthdays(); -$app->migrateClassification(); diff --git a/apps/dav/lib/AppInfo/Application.php b/apps/dav/lib/AppInfo/Application.php index 0de61b12e2..9e0d2da4e1 100644 --- a/apps/dav/lib/AppInfo/Application.php +++ b/apps/dav/lib/AppInfo/Application.php @@ -108,6 +108,14 @@ class Application extends App { $g ); }); + + $container->registerService('OCA\DAV\Migration\Classification', function ($c) { + /** @var IAppContainer $c */ + return new Classification( + $c->query('CalDavBackend'), + $c->getServer()->getUserManager() + ); + }); } /** @@ -170,20 +178,4 @@ class Application extends App { $this->getContainer()->getServer()->getLogger()->logException($ex); } } - - public function migrateClassification() { - try { - /** @var CalDavBackend $calDavBackend */ - $calDavBackend = $this->getContainer()->query('CalDavBackend'); - $migration = new Classification($calDavBackend); - $userManager = $this->getContainer()->getServer()->getUserManager(); - - $userManager->callForAllUsers(function($user) use($migration) { - /** @var IUser $user */ - $migration->runForUser($user); - }); - } catch (\Exception $ex) { - $this->getContainer()->getServer()->getLogger()->logException($ex); - } - } } diff --git a/apps/dav/lib/Migration/Classification.php b/apps/dav/lib/Migration/Classification.php new file mode 100644 index 0000000000..13ceca6fea --- /dev/null +++ b/apps/dav/lib/Migration/Classification.php @@ -0,0 +1,93 @@ + + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + + +namespace OCA\DAV\Migration; + +use OCA\DAV\CalDAV\CalDavBackend; +use OCP\IUser; +use OCP\IUserManager; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; + +class Classification implements IRepairStep { + + /** @var CalDavBackend */ + private $calDavBackend; + + /** @var IUserManager */ + private $userManager; + + /** + * Classification constructor. + * + * @param CalDavBackend $calDavBackend + */ + public function __construct(CalDavBackend $calDavBackend, IUserManager $userManager) { + $this->calDavBackend = $calDavBackend; + $this->userManager = $userManager; + } + + /** + * @param IUser $user + */ + public function runForUser($user) { + $principal = 'principals/users/' . $user->getUID(); + $calendars = $this->calDavBackend->getCalendarsForUser($principal); + foreach ($calendars as $calendar) { + $objects = $this->calDavBackend->getCalendarObjects($calendar['id']); + foreach ($objects as $object) { + $calObject = $this->calDavBackend->getCalendarObject($calendar['id'], $object['id']); + $classification = $this->extractClassification($calObject['calendardata']); + $this->calDavBackend->setClassification($object['id'], $classification); + } + } + } + + /** + * @param $calendarData + * @return integer + * @throws \Sabre\DAV\Exception\BadRequest + */ + protected function extractClassification($calendarData) { + return $this->calDavBackend->getDenormalizedData($calendarData)['classification']; + } + + /** + * @inheritdoc + */ + public function getName() { + return 'Fix classification for calendar objects'; + } + + /** + * @inheritdoc + */ + public function run(IOutput $output) { + $output->startProgress(); + $this->userManager->callForAllUsers(function($user) use ($output) { + /** @var IUser $user */ + $output->advance(1, $user->getDisplayName()); + $this->runForUser($user); + }); + $output->finishProgress(); + } +} diff --git a/apps/dav/lib/migration/classification.php b/apps/dav/lib/migration/classification.php deleted file mode 100644 index 48e62e6158..0000000000 --- a/apps/dav/lib/migration/classification.php +++ /dev/null @@ -1,41 +0,0 @@ -calDavBackend = $calDavBackend; - } - - /** - * @param IUser $user - */ - public function runForUser($user) { - $principal = 'principals/users/' . $user->getUID(); - $calendars = $this->calDavBackend->getCalendarsForUser($principal); - foreach ($calendars as $calendar) { - $objects = $this->calDavBackend->getCalendarObjects($calendar['id']); - foreach ($objects as $object) { - $calObject = $this->calDavBackend->getCalendarObject($calendar['id'], $object['id']); - $classification = $this->extractClassification($calObject['calendardata']); - $this->calDavBackend->setClassification($object['id'], $classification); - } - } - } - - /** - * @param $calObject - */ - protected function extractClassification($calendarData) { - return $this->calDavBackend->getDenormalizedData($calendarData)['classification']; - } -} diff --git a/lib/public/Migration/IRepairStep.php b/lib/public/Migration/IRepairStep.php index 3394b70a6f..84619128b4 100644 --- a/lib/public/Migration/IRepairStep.php +++ b/lib/public/Migration/IRepairStep.php @@ -39,8 +39,9 @@ interface IRepairStep { * Run repair step. * Must throw exception on error. * - * @since 9.1.0 + * @param IOutput $output * @throws \Exception in case of failure + * @since 9.1.0 */ public function run(IOutput $output); From 082f456b8b835efc3fa37af7e1628da8d5781331 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Tue, 31 May 2016 17:16:28 +0200 Subject: [PATCH 14/30] Added unit testing for the migration step --- .htaccess | 4 + apps/dav/lib/CalDAV/CalDavBackend.php | 14 +- apps/dav/lib/Migration/Classification.php | 2 +- .../unit/CalDAV/AbstractCalDavBackendTest.php | 163 ++++++++++++++++++ .../tests/unit/CalDAV/CalDavBackendTest.php | 126 +------------- .../unit/Migration/ClassificationTest.php | 75 ++++++++ 6 files changed, 258 insertions(+), 126 deletions(-) create mode 100644 apps/dav/tests/unit/CalDAV/AbstractCalDavBackendTest.php create mode 100644 apps/dav/tests/unit/Migration/ClassificationTest.php diff --git a/.htaccess b/.htaccess index bd9f5af3f7..5628af585d 100644 --- a/.htaccess +++ b/.htaccess @@ -72,3 +72,7 @@ Options -Indexes ModPagespeed Off +#### DO NOT CHANGE ANYTHING ABOVE THIS LINE #### + +ErrorDocument 403 /core/templates/403.php +ErrorDocument 404 /core/templates/404.php diff --git a/apps/dav/lib/CalDAV/CalDavBackend.php b/apps/dav/lib/CalDAV/CalDavBackend.php index 30cfae39fa..1950b87df3 100644 --- a/apps/dav/lib/CalDAV/CalDavBackend.php +++ b/apps/dav/lib/CalDAV/CalDavBackend.php @@ -489,7 +489,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription */ function getCalendarObjects($calendarId) { $query = $this->db->getQueryBuilder(); - $query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'componenttype']) + $query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'componenttype', 'classification']) ->from('calendarobjects') ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId))); $stmt = $query->execute(); @@ -504,6 +504,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription 'calendarid' => $row['calendarid'], 'size' => (int)$row['size'], 'component' => strtolower($row['componenttype']), + 'classification'=> $row['classification'] ]; } @@ -529,7 +530,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription function getCalendarObject($calendarId, $objectUri) { $query = $this->db->getQueryBuilder(); - $query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'calendardata', 'componenttype']) + $query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'calendardata', 'componenttype', 'classification']) ->from('calendarobjects') ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId))) ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri))); @@ -547,6 +548,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription 'size' => (int)$row['size'], 'calendardata' => $this->readBlob($row['calendardata']), 'component' => strtolower($row['componenttype']), + 'classification'=> $row['classification'] ]; } @@ -564,7 +566,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription */ function getMultipleCalendarObjects($calendarId, array $uris) { $query = $this->db->getQueryBuilder(); - $query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'calendardata', 'componenttype']) + $query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'calendardata', 'componenttype', 'classification']) ->from('calendarobjects') ->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId))) ->andWhere($query->expr()->in('uri', $query->createParameter('uri'))) @@ -584,6 +586,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription 'size' => (int)$row['size'], 'calendardata' => $this->readBlob($row['calendardata']), 'component' => strtolower($row['componenttype']), + 'classification' => $row['classification'] ]; } @@ -679,6 +682,11 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription * @param int $classification */ public function setClassification($calendarObjectId, $classification) { + if (!in_array($classification, [ + self::CLASSIFICATION_PUBLIC, self::CLASSIFICATION_PRIVATE, self::CLASSIFICATION_CONFIDENTIAL + ])) { + throw new \InvalidArgumentException(); + } $query = $this->db->getQueryBuilder(); $query->update('calendarobjects') ->set('classification', $query->createNamedParameter($classification)) diff --git a/apps/dav/lib/Migration/Classification.php b/apps/dav/lib/Migration/Classification.php index 13ceca6fea..b793f790af 100644 --- a/apps/dav/lib/Migration/Classification.php +++ b/apps/dav/lib/Migration/Classification.php @@ -55,7 +55,7 @@ class Classification implements IRepairStep { foreach ($calendars as $calendar) { $objects = $this->calDavBackend->getCalendarObjects($calendar['id']); foreach ($objects as $object) { - $calObject = $this->calDavBackend->getCalendarObject($calendar['id'], $object['id']); + $calObject = $this->calDavBackend->getCalendarObject($calendar['id'], $object['uri']); $classification = $this->extractClassification($calObject['calendardata']); $this->calDavBackend->setClassification($object['id'], $classification); } diff --git a/apps/dav/tests/unit/CalDAV/AbstractCalDavBackendTest.php b/apps/dav/tests/unit/CalDAV/AbstractCalDavBackendTest.php new file mode 100644 index 0000000000..49e5e5a2bc --- /dev/null +++ b/apps/dav/tests/unit/CalDAV/AbstractCalDavBackendTest.php @@ -0,0 +1,163 @@ + + * @author Thomas Müller + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OCA\DAV\Tests\unit\CalDAV; + +use DateTime; +use DateTimeZone; +use OCA\DAV\CalDAV\CalDavBackend; +use OCA\DAV\CalDAV\Calendar; +use OCA\DAV\Connector\Sabre\Principal; +use OCP\IL10N; +use Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet; +use Sabre\DAV\PropPatch; +use Sabre\DAV\Xml\Property\Href; +use Sabre\DAVACL\IACL; +use Test\TestCase; + +/** + * Class CalDavBackendTest + * + * @group DB + * + * @package OCA\DAV\Tests\unit\CalDAV + */ +abstract class AbstractCalDavBackendTest extends TestCase { + + /** @var CalDavBackend */ + protected $backend; + + /** @var Principal | \PHPUnit_Framework_MockObject_MockObject */ + protected $principal; + + const UNIT_TEST_USER = 'principals/users/caldav-unit-test'; + const UNIT_TEST_USER1 = 'principals/users/caldav-unit-test1'; + const UNIT_TEST_GROUP = 'principals/groups/caldav-unit-test-group'; + + public function setUp() { + parent::setUp(); + + $this->principal = $this->getMockBuilder('OCA\DAV\Connector\Sabre\Principal') + ->disableOriginalConstructor() + ->setMethods(['getPrincipalByPath', 'getGroupMembership']) + ->getMock(); + $this->principal->expects($this->any())->method('getPrincipalByPath') + ->willReturn([ + 'uri' => 'principals/best-friend' + ]); + $this->principal->expects($this->any())->method('getGroupMembership') + ->withAnyParameters() + ->willReturn([self::UNIT_TEST_GROUP]); + + $db = \OC::$server->getDatabaseConnection(); + $this->backend = new CalDavBackend($db, $this->principal); + + $this->tearDown(); + } + + public function tearDown() { + parent::tearDown(); + + if (is_null($this->backend)) { + return; + } + $books = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER); + foreach ($books as $book) { + $this->backend->deleteCalendar($book['id']); + } + $subscriptions = $this->backend->getSubscriptionsForUser(self::UNIT_TEST_USER); + foreach ($subscriptions as $subscription) { + $this->backend->deleteSubscription($subscription['id']); + } + } + + protected function createTestCalendar() { + $this->backend->createCalendar(self::UNIT_TEST_USER, 'Example', [ + '{http://apple.com/ns/ical/}calendar-color' => '#1C4587FF' + ]); + $calendars = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER); + $this->assertEquals(1, count($calendars)); + $this->assertEquals(self::UNIT_TEST_USER, $calendars[0]['principaluri']); + /** @var SupportedCalendarComponentSet $components */ + $components = $calendars[0]['{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set']; + $this->assertEquals(['VEVENT','VTODO'], $components->getValue()); + $color = $calendars[0]['{http://apple.com/ns/ical/}calendar-color']; + $this->assertEquals('#1C4587FF', $color); + $this->assertEquals('Example', $calendars[0]['uri']); + $this->assertEquals('Example', $calendars[0]['{DAV:}displayname']); + $calendarId = $calendars[0]['id']; + + return $calendarId; + } + + protected function createEvent($calendarId, $start = '20130912T130000Z', $end = '20130912T140000Z') { + + $calData = <<getUniqueID('event'); + $this->backend->createCalendarObject($calendarId, $uri0, $calData); + + return $uri0; + } + + protected function assertAcl($principal, $privilege, $acl) { + foreach($acl as $a) { + if ($a['principal'] === $principal && $a['privilege'] === $privilege) { + $this->assertTrue(true); + return; + } + } + $this->fail("ACL does not contain $principal / $privilege"); + } + + protected function assertNotAcl($principal, $privilege, $acl) { + foreach($acl as $a) { + if ($a['principal'] === $principal && $a['privilege'] === $privilege) { + $this->fail("ACL contains $principal / $privilege"); + return; + } + } + $this->assertTrue(true); + } + + protected function assertAccess($shouldHaveAcl, $principal, $privilege, $acl) { + if ($shouldHaveAcl) { + $this->assertAcl($principal, $privilege, $acl); + } else { + $this->assertNotAcl($principal, $privilege, $acl); + } + } +} diff --git a/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php b/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php index 4281b88be0..977bdf15c8 100644 --- a/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php +++ b/apps/dav/tests/unit/CalDAV/CalDavBackendTest.php @@ -26,13 +26,10 @@ use DateTime; use DateTimeZone; use OCA\DAV\CalDAV\CalDavBackend; use OCA\DAV\CalDAV\Calendar; -use OCA\DAV\Connector\Sabre\Principal; use OCP\IL10N; -use Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet; use Sabre\DAV\PropPatch; use Sabre\DAV\Xml\Property\Href; use Sabre\DAVACL\IACL; -use Test\TestCase; /** * Class CalDavBackendTest @@ -41,54 +38,7 @@ use Test\TestCase; * * @package OCA\DAV\Tests\unit\CalDAV */ -class CalDavBackendTest extends TestCase { - - /** @var CalDavBackend */ - private $backend; - - /** @var Principal | \PHPUnit_Framework_MockObject_MockObject */ - private $principal; - - const UNIT_TEST_USER = 'principals/users/caldav-unit-test'; - const UNIT_TEST_USER1 = 'principals/users/caldav-unit-test1'; - const UNIT_TEST_GROUP = 'principals/groups/caldav-unit-test-group'; - - public function setUp() { - parent::setUp(); - - $this->principal = $this->getMockBuilder('OCA\DAV\Connector\Sabre\Principal') - ->disableOriginalConstructor() - ->setMethods(['getPrincipalByPath', 'getGroupMembership']) - ->getMock(); - $this->principal->expects($this->any())->method('getPrincipalByPath') - ->willReturn([ - 'uri' => 'principals/best-friend' - ]); - $this->principal->expects($this->any())->method('getGroupMembership') - ->withAnyParameters() - ->willReturn([self::UNIT_TEST_GROUP]); - - $db = \OC::$server->getDatabaseConnection(); - $this->backend = new CalDavBackend($db, $this->principal); - - $this->tearDown(); - } - - public function tearDown() { - parent::tearDown(); - - if (is_null($this->backend)) { - return; - } - $books = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER); - foreach ($books as $book) { - $this->backend->deleteCalendar($book['id']); - } - $subscriptions = $this->backend->getSubscriptionsForUser(self::UNIT_TEST_USER); - foreach ($subscriptions as $subscription) { - $this->backend->deleteSubscription($subscription['id']); - } - } +class CalDavBackendTest extends AbstractCalDavBackendTest { public function testCalendarOperations() { @@ -232,6 +182,7 @@ EOD; $calendarObjects = $this->backend->getCalendarObjects($calendarId); $this->assertEquals(1, count($calendarObjects)); $this->assertEquals($calendarId, $calendarObjects[0]['calendarid']); + $this->assertArrayHasKey('classification', $calendarObjects[0]); // get the cards $calendarObject = $this->backend->getCalendarObject($calendarId, $uri); @@ -241,6 +192,7 @@ EOD; $this->assertArrayHasKey('lastmodified', $calendarObject); $this->assertArrayHasKey('etag', $calendarObject); $this->assertArrayHasKey('size', $calendarObject); + $this->assertArrayHasKey('classification', $calendarObject); $this->assertEquals($calData, $calendarObject['calendardata']); // update the card @@ -310,6 +262,7 @@ EOD; $this->assertArrayHasKey('lastmodified', $card); $this->assertArrayHasKey('etag', $card); $this->assertArrayHasKey('size', $card); + $this->assertArrayHasKey('classification', $card); $this->assertEquals($calData, $card['calendardata']); } @@ -363,49 +316,6 @@ EOD; ]; } - private function createTestCalendar() { - $this->backend->createCalendar(self::UNIT_TEST_USER, 'Example', [ - '{http://apple.com/ns/ical/}calendar-color' => '#1C4587FF' - ]); - $calendars = $this->backend->getCalendarsForUser(self::UNIT_TEST_USER); - $this->assertEquals(1, count($calendars)); - $this->assertEquals(self::UNIT_TEST_USER, $calendars[0]['principaluri']); - /** @var SupportedCalendarComponentSet $components */ - $components = $calendars[0]['{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set']; - $this->assertEquals(['VEVENT','VTODO'], $components->getValue()); - $color = $calendars[0]['{http://apple.com/ns/ical/}calendar-color']; - $this->assertEquals('#1C4587FF', $color); - $this->assertEquals('Example', $calendars[0]['uri']); - $this->assertEquals('Example', $calendars[0]['{DAV:}displayname']); - $calendarId = $calendars[0]['id']; - - return $calendarId; - } - - private function createEvent($calendarId, $start = '20130912T130000Z', $end = '20130912T140000Z') { - - $calData = <<getUniqueID('event'); - $this->backend->createCalendarObject($calendarId, $uri0, $calData); - - return $uri0; - } - public function testSyncSupport() { $calendarId = $this->createTestCalendar(); @@ -480,32 +390,4 @@ EOD; 'unknown class -> private' => [CalDavBackend::CLASSIFICATION_PRIVATE, 'classification', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//dmfs.org//mimedir.icalendar//EN\r\nBEGIN:VTIMEZONE\r\nTZID:Europe/Berlin\r\nX-LIC-LOCATION:Europe/Berlin\r\nBEGIN:DAYLIGHT\r\nTZOFFSETFROM:+0100\r\nTZOFFSETTO:+0200\r\nTZNAME:CEST\r\nDTSTART:19700329T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETFROM:+0200\r\nTZOFFSETTO:+0100\r\nTZNAME:CET\r\nDTSTART:19701025T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU\r\nEND:STANDARD\r\nEND:VTIMEZONE\r\nBEGIN:VEVENT\r\nDTSTART;TZID=Europe/Berlin:20160419T130000\r\nSUMMARY:Test\r\nCLASS:VERTRAULICH\r\nTRANSP:OPAQUE\r\nSTATUS:CONFIRMED\r\nDTEND;TZID=Europe/Berlin:20160419T140000\r\nLAST-MODIFIED:20160419T074202Z\r\nDTSTAMP:20160419T074202Z\r\nCREATED:20160419T074202Z\r\nUID:2e468c48-7860-492e-bc52-92fa0daeeccf.1461051722310\r\nEND:VEVENT\r\nEND:VCALENDAR"], ]; } - - private function assertAcl($principal, $privilege, $acl) { - foreach($acl as $a) { - if ($a['principal'] === $principal && $a['privilege'] === $privilege) { - $this->assertTrue(true); - return; - } - } - $this->fail("ACL does not contain $principal / $privilege"); - } - - private function assertNotAcl($principal, $privilege, $acl) { - foreach($acl as $a) { - if ($a['principal'] === $principal && $a['privilege'] === $privilege) { - $this->fail("ACL contains $principal / $privilege"); - return; - } - } - $this->assertTrue(true); - } - - private function assertAccess($shouldHaveAcl, $principal, $privilege, $acl) { - if ($shouldHaveAcl) { - $this->assertAcl($principal, $privilege, $acl); - } else { - $this->assertNotAcl($principal, $privilege, $acl); - } - } } diff --git a/apps/dav/tests/unit/Migration/ClassificationTest.php b/apps/dav/tests/unit/Migration/ClassificationTest.php new file mode 100644 index 0000000000..5c7fa62722 --- /dev/null +++ b/apps/dav/tests/unit/Migration/ClassificationTest.php @@ -0,0 +1,75 @@ + + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + +namespace OCA\DAV\Tests\unit\DAV\Migration; + +use OCA\DAV\CalDAV\CalDavBackend; +use OCA\DAV\Migration\Classification; +use OCA\DAV\Tests\unit\CalDAV\AbstractCalDavBackendTest; +use OCP\IUser; + +/** + * Class ClassificationTest + * + * @group DB + * + * @package OCA\DAV\Tests\unit\DAV + */ +class ClassificationTest extends AbstractCalDavBackendTest { + + /** @var \PHPUnit_Framework_MockObject_MockObject | \OCP\IUserManager */ + private $userManager; + + public function setUp() { + parent::setUp(); + + $this->userManager = $this->getMockBuilder('OCP\IUserManager') + ->disableOriginalConstructor()->getMock(); + } + + public function test() { + // setup data + $calendarId = $this->createTestCalendar(); + $eventUri = $this->createEvent($calendarId, '20130912T130000Z', '20130912T140000Z'); + $object = $this->backend->getCalendarObject($calendarId, $eventUri); + + // assert proper classification + $this->assertEquals(CalDavBackend::CLASSIFICATION_PUBLIC, $object['classification']); + $this->backend->setClassification($object['id'], CalDavBackend::CLASSIFICATION_CONFIDENTIAL); + $object = $this->backend->getCalendarObject($calendarId, $eventUri); + $this->assertEquals(CalDavBackend::CLASSIFICATION_CONFIDENTIAL, $object['classification']); + + // run migration + $c = new Classification($this->backend, $this->userManager); + + /** @var IUser | \PHPUnit_Framework_MockObject_MockObject $user */ + $user = $this->getMockBuilder('OCP\IUser') + ->disableOriginalConstructor() + ->getMock(); + $user->expects($this->once())->method('getUID')->willReturn('caldav-unit-test'); + + $c->runForUser($user); + + // assert classification after migration + $object = $this->backend->getCalendarObject($calendarId, $eventUri); + $this->assertEquals(CalDavBackend::CLASSIFICATION_PUBLIC, $object['classification']); + } +} From bfcd1dc49c6fe296559131841ee0096ec1ce89c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Tue, 31 May 2016 18:10:31 +0200 Subject: [PATCH 15/30] Filter confidential calendar objects in shared calendars Filter private calendar objects in shared calendars --- .htaccess | 4 - apps/dav/lib/CalDAV/CalDavBackend.php | 6 +- apps/dav/lib/CalDAV/Calendar.php | 77 ++++++++++ apps/dav/lib/CalDAV/CalendarObject.php | 92 ++++++++++++ apps/dav/tests/unit/CalDAV/CalendarTest.php | 150 ++++++++++++++++++++ 5 files changed, 322 insertions(+), 7 deletions(-) create mode 100644 apps/dav/lib/CalDAV/CalendarObject.php diff --git a/.htaccess b/.htaccess index 5628af585d..bd9f5af3f7 100644 --- a/.htaccess +++ b/.htaccess @@ -72,7 +72,3 @@ Options -Indexes ModPagespeed Off -#### DO NOT CHANGE ANYTHING ABOVE THIS LINE #### - -ErrorDocument 403 /core/templates/403.php -ErrorDocument 404 /core/templates/404.php diff --git a/apps/dav/lib/CalDAV/CalDavBackend.php b/apps/dav/lib/CalDAV/CalDavBackend.php index 1950b87df3..ce49408297 100644 --- a/apps/dav/lib/CalDAV/CalDavBackend.php +++ b/apps/dav/lib/CalDAV/CalDavBackend.php @@ -504,7 +504,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription 'calendarid' => $row['calendarid'], 'size' => (int)$row['size'], 'component' => strtolower($row['componenttype']), - 'classification'=> $row['classification'] + 'classification'=> (int)$row['classification'] ]; } @@ -548,7 +548,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription 'size' => (int)$row['size'], 'calendardata' => $this->readBlob($row['calendardata']), 'component' => strtolower($row['componenttype']), - 'classification'=> $row['classification'] + 'classification'=> (int)$row['classification'] ]; } @@ -586,7 +586,7 @@ class CalDavBackend extends AbstractBackend implements SyncSupport, Subscription 'size' => (int)$row['size'], 'calendardata' => $this->readBlob($row['calendardata']), 'component' => strtolower($row['componenttype']), - 'classification' => $row['classification'] + 'classification' => (int)$row['classification'] ]; } diff --git a/apps/dav/lib/CalDAV/Calendar.php b/apps/dav/lib/CalDAV/Calendar.php index 73b3957a9b..785bb5699e 100644 --- a/apps/dav/lib/CalDAV/Calendar.php +++ b/apps/dav/lib/CalDAV/Calendar.php @@ -26,6 +26,7 @@ use OCA\DAV\DAV\Sharing\IShareable; use OCP\IL10N; use Sabre\CalDAV\Backend\BackendInterface; use Sabre\DAV\Exception\Forbidden; +use Sabre\DAV\Exception\NotFound; use Sabre\DAV\PropPatch; class Calendar extends \Sabre\CalDAV\Calendar implements IShareable { @@ -162,6 +163,78 @@ class Calendar extends \Sabre\CalDAV\Calendar implements IShareable { parent::propPatch($propPatch); } + function getChild($name) { + + $obj = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'], $name); + + if (!$obj) { + throw new NotFound('Calendar object not found'); + } + + if ($this->isShared() && $obj['classification'] === CalDavBackend::CLASSIFICATION_PRIVATE) { + throw new NotFound('Calendar object not found'); + } + + $obj['acl'] = $this->getChildACL(); + + return new CalendarObject($this->caldavBackend, $this->calendarInfo, $obj); + + } + + function getChildren() { + + $objs = $this->caldavBackend->getCalendarObjects($this->calendarInfo['id']); + $children = []; + foreach ($objs as $obj) { + if ($this->isShared() && $obj['classification'] === CalDavBackend::CLASSIFICATION_PRIVATE) { + continue; + } + $obj['acl'] = $this->getChildACL(); + $children[] = new CalendarObject($this->caldavBackend, $this->calendarInfo, $obj); + } + return $children; + + } + + function getMultipleChildren(array $paths) { + + $objs = $this->caldavBackend->getMultipleCalendarObjects($this->calendarInfo['id'], $paths); + $children = []; + foreach ($objs as $obj) { + if ($this->isShared() && $obj['classification'] === CalDavBackend::CLASSIFICATION_PRIVATE) { + continue; + } + $obj['acl'] = $this->getChildACL(); + $children[] = new CalendarObject($this->caldavBackend, $this->calendarInfo, $obj); + } + return $children; + + } + + function childExists($name) { + $obj = $this->caldavBackend->getCalendarObject($this->calendarInfo['id'], $name); + if (!$obj) { + return false; + } + if ($this->isShared() && $obj['classification'] === CalDavBackend::CLASSIFICATION_PRIVATE) { + return false; + } + + return true; + } + + function calendarQuery(array $filters) { + + $uris = $this->caldavBackend->calendarQuery($this->calendarInfo['id'], $filters); + if ($this->isShared()) { + return array_filter($uris, function ($uri) { + return $this->childExists($uri); + }); + } + + return $uris; + } + private function canWrite() { if (isset($this->calendarInfo['{http://owncloud.org/ns}read-only'])) { return !$this->calendarInfo['{http://owncloud.org/ns}read-only']; @@ -169,4 +242,8 @@ class Calendar extends \Sabre\CalDAV\Calendar implements IShareable { return true; } + private function isShared() { + return isset($this->calendarInfo['{http://owncloud.org/ns}owner-principal']); + } + } diff --git a/apps/dav/lib/CalDAV/CalendarObject.php b/apps/dav/lib/CalDAV/CalendarObject.php new file mode 100644 index 0000000000..b4a58b5209 --- /dev/null +++ b/apps/dav/lib/CalDAV/CalendarObject.php @@ -0,0 +1,92 @@ + + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ + + +namespace OCA\DAV\CalDAV; + + +use Sabre\VObject\Component; +use Sabre\VObject\Property; +use Sabre\VObject\Reader; + +class CalendarObject extends \Sabre\CalDAV\CalendarObject { + + /** + * @inheritdoc + */ + function get() { + $data = parent::get(); + if ($this->isShared() && $this->objectData['classification'] === CalDavBackend::CLASSIFICATION_CONFIDENTIAL) { + return $this->createConfidentialObject($data); + } + return $data; + } + + private function isShared() { + return isset($this->calendarInfo['{http://owncloud.org/ns}owner-principal']); + } + + /** + * @param string $calData + * @return string + */ + private static function createConfidentialObject($calData) { + + $vObject = Reader::read($calData); + + /** @var Component $vElement */ + $vElement = null; + if(isset($vObject->VEVENT)) { + $vElement = $vObject->VEVENT; + } + if(isset($vObject->VJOURNAL)) { + $vElement = $vObject->VJOURNAL; + } + if(isset($vObject->VTODO)) { + $vElement = $vObject->VTODO; + } + if(!is_null($vElement)) { + foreach ($vElement->children as &$property) { + /** @var Property $property */ + switch($property->name) { + case 'CREATED': + case 'DTSTART': + case 'RRULE': + case 'DURATION': + case 'DTEND': + case 'CLASS': + case 'UID': + break; + case 'SUMMARY': + $property->setValue('Busy'); + break; + default: + $vElement->__unset($property->name); + unset($property); + break; + } + } + } + + return $vObject->serialize(); + } + +} diff --git a/apps/dav/tests/unit/CalDAV/CalendarTest.php b/apps/dav/tests/unit/CalDAV/CalendarTest.php index 73d85e82bb..56a2d4fcba 100644 --- a/apps/dav/tests/unit/CalDAV/CalendarTest.php +++ b/apps/dav/tests/unit/CalDAV/CalendarTest.php @@ -27,6 +27,7 @@ use OCA\DAV\CalDAV\CalDavBackend; use OCA\DAV\CalDAV\Calendar; use OCP\IL10N; use Sabre\DAV\PropPatch; +use Sabre\VObject\Reader; use Test\TestCase; class CalendarTest extends TestCase { @@ -189,4 +190,153 @@ class CalendarTest extends TestCase { 'birthday calendar' => [false, false, false, BirthdayService::BIRTHDAY_CALENDAR_URI] ]; } + + /** + * @dataProvider providesConfidentialClassificationData + * @param $expectedChildren + * @param $isShared + */ + public function testPrivateClassification($expectedChildren, $isShared) { + + $calObject0 = ['uri' => 'event-0', 'classification' => CalDavBackend::CLASSIFICATION_PUBLIC]; + $calObject1 = ['uri' => 'event-1', 'classification' => CalDavBackend::CLASSIFICATION_CONFIDENTIAL]; + $calObject2 = ['uri' => 'event-2', 'classification' => CalDavBackend::CLASSIFICATION_PRIVATE]; + + /** @var \PHPUnit_Framework_MockObject_MockObject | CalDavBackend $backend */ + $backend = $this->getMockBuilder('OCA\DAV\CalDAV\CalDavBackend')->disableOriginalConstructor()->getMock(); + $backend->expects($this->any())->method('getCalendarObjects')->willReturn([ + $calObject0, $calObject1, $calObject2 + ]); + $backend->expects($this->any())->method('getMultipleCalendarObjects') + ->with(666, ['event-0', 'event-1', 'event-2']) + ->willReturn([ + $calObject0, $calObject1, $calObject2 + ]); + $backend->expects($this->any())->method('getCalendarObject') + ->willReturn($calObject2)->with(666, 'event-2'); + + $calendarInfo = [ + 'principaluri' => 'user2', + 'id' => 666, + 'uri' => 'cal', + ]; + + if ($isShared) { + $calendarInfo['{http://owncloud.org/ns}owner-principal'] = 'user1'; + + } + $c = new Calendar($backend, $calendarInfo, $this->l10n); + $children = $c->getChildren(); + $this->assertEquals($expectedChildren, count($children)); + $children = $c->getMultipleChildren(['event-0', 'event-1', 'event-2']); + $this->assertEquals($expectedChildren, count($children)); + + $this->assertEquals(!$isShared, $c->childExists('event-2')); + } + + /** + * @dataProvider providesConfidentialClassificationData + * @param $expectedChildren + * @param $isShared + */ + public function testConfidentialClassification($expectedChildren, $isShared) { + $start = '20160609'; + $end = '20160610'; + + $calData = << 'event-0', 'classification' => CalDavBackend::CLASSIFICATION_PUBLIC]; + $calObject1 = ['uri' => 'event-1', 'classification' => CalDavBackend::CLASSIFICATION_CONFIDENTIAL, 'calendardata' => $calData]; + $calObject2 = ['uri' => 'event-2', 'classification' => CalDavBackend::CLASSIFICATION_PRIVATE]; + + /** @var \PHPUnit_Framework_MockObject_MockObject | CalDavBackend $backend */ + $backend = $this->getMockBuilder('OCA\DAV\CalDAV\CalDavBackend')->disableOriginalConstructor()->getMock(); + $backend->expects($this->any())->method('getCalendarObjects')->willReturn([ + $calObject0, $calObject1, $calObject2 + ]); + $backend->expects($this->any())->method('getMultipleCalendarObjects') + ->with(666, ['event-0', 'event-1', 'event-2']) + ->willReturn([ + $calObject0, $calObject1, $calObject2 + ]); + $backend->expects($this->any())->method('getCalendarObject') + ->willReturn($calObject1)->with(666, 'event-1'); + + $calendarInfo = [ + 'principaluri' => 'user2', + 'id' => 666, + 'uri' => 'cal', + ]; + + if ($isShared) { + $calendarInfo['{http://owncloud.org/ns}owner-principal'] = 'user1'; + + } + $c = new Calendar($backend, $calendarInfo, $this->l10n); + + // test private event + $privateEvent = $c->getChild('event-1'); + $calData = $privateEvent->get(); + $event = Reader::read($calData); + + $this->assertEquals($start, $event->VEVENT->DTSTART->getValue()); + $this->assertEquals($end, $event->VEVENT->DTEND->getValue()); + + if ($isShared) { + $this->assertEquals('Busy', $event->VEVENT->SUMMARY->getValue()); + $this->assertArrayNotHasKey('ATTENDEE', $event->VEVENT); + $this->assertArrayNotHasKey('LOCATION', $event->VEVENT); + $this->assertArrayNotHasKey('DESCRIPTION', $event->VEVENT); + $this->assertArrayNotHasKey('ORGANIZER', $event->VEVENT); + } else { + $this->assertEquals('Test Event', $event->VEVENT->SUMMARY->getValue()); + } + } + + public function providesConfidentialClassificationData() { + return [ + [3, false], + [2, true] + ]; + } } From cf06b17df15d6de83d2893cbe08880bbd9dd5c2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Thu, 9 Jun 2016 13:53:32 +0200 Subject: [PATCH 16/30] Use the correct realm for basic authentication - fixes #23427 --- apps/dav/lib/Connector/PublicAuth.php | 9 +++++++-- apps/dav/lib/Connector/Sabre/Auth.php | 4 ++++ apps/dav/lib/Connector/Sabre/ServerFactory.php | 3 +-- apps/federation/lib/DAV/FedAuth.php | 4 ++++ 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/apps/dav/lib/Connector/PublicAuth.php b/apps/dav/lib/Connector/PublicAuth.php index 2716ca2910..4e63ca1d29 100644 --- a/apps/dav/lib/Connector/PublicAuth.php +++ b/apps/dav/lib/Connector/PublicAuth.php @@ -31,13 +31,14 @@ use OCP\IRequest; use OCP\ISession; use OCP\Share\Exceptions\ShareNotFound; use OCP\Share\IManager; +use Sabre\DAV\Auth\Backend\AbstractBasic; /** * Class PublicAuth * * @package OCA\DAV\Connector */ -class PublicAuth extends \Sabre\DAV\Auth\Backend\AbstractBasic { +class PublicAuth extends AbstractBasic { /** @var \OCP\Share\IShare */ private $share; @@ -62,6 +63,10 @@ class PublicAuth extends \Sabre\DAV\Auth\Backend\AbstractBasic { $this->request = $request; $this->shareManager = $shareManager; $this->session = $session; + + // setup realm + $defaults = new \OC_Defaults(); + $this->realm = $defaults->getName(); } /** @@ -99,7 +104,7 @@ class PublicAuth extends \Sabre\DAV\Auth\Backend\AbstractBasic { if (in_array('XMLHttpRequest', explode(',', $this->request->getHeader('X-Requested-With')))) { // do not re-authenticate over ajax, use dummy auth name to prevent browser popup http_response_code(401); - header('WWW-Authenticate', 'DummyBasic real="ownCloud"'); + header('WWW-Authenticate','DummyBasic realm="' . $this->realm . '"'); throw new \Sabre\DAV\Exception\NotAuthenticated('Cannot authenticate over ajax calls'); } return false; diff --git a/apps/dav/lib/Connector/Sabre/Auth.php b/apps/dav/lib/Connector/Sabre/Auth.php index 7b959a0d89..a0e4b2231a 100644 --- a/apps/dav/lib/Connector/Sabre/Auth.php +++ b/apps/dav/lib/Connector/Sabre/Auth.php @@ -74,6 +74,10 @@ class Auth extends AbstractBasic { $this->twoFactorManager = $twoFactorManager; $this->request = $request; $this->principalPrefix = $principalPrefix; + + // setup realm + $defaults = new \OC_Defaults(); + $this->realm = $defaults->getName(); } /** diff --git a/apps/dav/lib/Connector/Sabre/ServerFactory.php b/apps/dav/lib/Connector/Sabre/ServerFactory.php index b193bfc76c..330c297d42 100644 --- a/apps/dav/lib/Connector/Sabre/ServerFactory.php +++ b/apps/dav/lib/Connector/Sabre/ServerFactory.php @@ -100,10 +100,9 @@ class ServerFactory { $server->setBaseUri($baseUri); // Load plugins - $defaults = new \OC_Defaults(); $server->addPlugin(new \OCA\DAV\Connector\Sabre\MaintenancePlugin($this->config)); $server->addPlugin(new \OCA\DAV\Connector\Sabre\BlockLegacyClientPlugin($this->config)); - $server->addPlugin(new \Sabre\DAV\Auth\Plugin($authBackend, $defaults->getName())); + $server->addPlugin(new \Sabre\DAV\Auth\Plugin($authBackend)); // FIXME: The following line is a workaround for legacy components relying on being able to send a GET to / $server->addPlugin(new \OCA\DAV\Connector\Sabre\DummyGetResponsePlugin()); $server->addPlugin(new \OCA\DAV\Connector\Sabre\ExceptionLoggerPlugin('webdav', $this->logger)); diff --git a/apps/federation/lib/DAV/FedAuth.php b/apps/federation/lib/DAV/FedAuth.php index bb1041adcd..21c0d61487 100644 --- a/apps/federation/lib/DAV/FedAuth.php +++ b/apps/federation/lib/DAV/FedAuth.php @@ -36,6 +36,10 @@ class FedAuth extends AbstractBasic { public function __construct(DbHandler $db) { $this->db = $db; $this->principalPrefix = 'principals/system/'; + + // setup realm + $defaults = new \OC_Defaults(); + $this->realm = $defaults->getName(); } /** From 7d51fd0310ef74ae9db7358e524b8a679ef2df39 Mon Sep 17 00:00:00 2001 From: Roeland Jago Douma Date: Wed, 8 Jun 2016 11:52:56 +0200 Subject: [PATCH 17/30] Add repair step to clean old calendar/contact shares fixes #21889 --- lib/private/Repair.php | 2 + lib/private/Repair/RemoveOldShares.php | 103 +++++++++++++++ tests/lib/Repair/RemoveOldSharesTest.php | 160 +++++++++++++++++++++++ 3 files changed, 265 insertions(+) create mode 100644 lib/private/Repair/RemoveOldShares.php create mode 100644 tests/lib/Repair/RemoveOldSharesTest.php diff --git a/lib/private/Repair.php b/lib/private/Repair.php index 4869db7749..710bbbbaed 100644 --- a/lib/private/Repair.php +++ b/lib/private/Repair.php @@ -34,6 +34,7 @@ use OC\Repair\Collation; use OC\Repair\DropOldJobs; use OC\Repair\OldGroupMembershipShares; use OC\Repair\RemoveGetETagEntries; +use OC\Repair\RemoveOldShares; use OC\Repair\SharePropagation; use OC\Repair\SqliteAutoincrement; use OC\Repair\DropOldTables; @@ -132,6 +133,7 @@ class Repair implements IOutput{ new UpdateOutdatedOcsIds(\OC::$server->getConfig()), new RepairInvalidShares(\OC::$server->getConfig(), \OC::$server->getDatabaseConnection()), new SharePropagation(\OC::$server->getConfig()), + new RemoveOldShares(\OC::$server->getDatabaseConnection()), ]; } diff --git a/lib/private/Repair/RemoveOldShares.php b/lib/private/Repair/RemoveOldShares.php new file mode 100644 index 0000000000..2c05d97b15 --- /dev/null +++ b/lib/private/Repair/RemoveOldShares.php @@ -0,0 +1,103 @@ + + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ +namespace OC\Repair; + +use OCP\IDBConnection; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; + +/** + * Class RemoveOldShares + * + * @package OC\Repair + */ +class RemoveOldShares implements IRepairStep { + + /** @var IDBConnection */ + protected $connection; + + /** + * RemoveOldCalendarShares constructor. + * + * @param IDBConnection $db + */ + public function __construct(IDBConnection $connection) { + $this->connection = $connection; + } + + /** + * @return string + */ + public function getName() { + return 'Remove old (< 9.0) calendar/contact shares'; + } + + /** + * @param IOutput $output + */ + public function run(IOutput $output) { + $output->startProgress(4); + + $this->removeCalendarShares($output); + $this->removeContactShares($output); + + $output->finishProgress(); + } + + /** + * @param IOutput $output + */ + private function removeCalendarShares(IOutput $output) { + $qb = $this->connection->getQueryBuilder(); + $qb->delete('share') + ->where($qb->expr()->eq('item_type', $qb->createNamedParameter('calendar'))); + $qb->execute(); + + $output->advance(); + + $qb = $this->connection->getQueryBuilder(); + $qb->delete('share') + ->where($qb->expr()->eq('item_type', $qb->createNamedParameter('event'))); + $qb->execute(); + + $output->advance(); + } + + /** + * @param IOutput $output + */ + private function removeContactShares(IOutput $output) { + $qb = $this->connection->getQueryBuilder(); + $qb->delete('share') + ->where($qb->expr()->eq('item_type', $qb->createNamedParameter('contact'))); + $qb->execute(); + + $output->advance(); + + $qb = $this->connection->getQueryBuilder(); + $qb->delete('share') + ->where($qb->expr()->eq('item_type', $qb->createNamedParameter('addressbook'))); + $qb->execute(); + + $output->advance(); + } +} + diff --git a/tests/lib/Repair/RemoveOldSharesTest.php b/tests/lib/Repair/RemoveOldSharesTest.php new file mode 100644 index 0000000000..ac30585bdc --- /dev/null +++ b/tests/lib/Repair/RemoveOldSharesTest.php @@ -0,0 +1,160 @@ + + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ +namespace Test\Repair; + +use OC\Repair\RemoveOldShares; +use OCP\IDBConnection; +use OCP\Migration\IOutput; + +/** + * Class RemoveOldSharesTest + * + * @package Test\Repair + * @group DB + */ +class RemoveOldSharesTest extends \Test\TestCase { + + /** @var RemoveOldShares */ + protected $repair; + + /** @var IDBConnection */ + protected $connection; + + /** @var IOutput */ + private $outputMock; + + protected function setUp() { + parent::setUp(); + + $this->outputMock = $this->getMockBuilder('\OCP\Migration\IOutput') + ->disableOriginalConstructor() + ->getMock(); + + $this->connection = \OC::$server->getDatabaseConnection(); + $this->repair = new RemoveOldShares($this->connection); + } + + protected function tearDown() { + $qb = $this->connection->getQueryBuilder(); + $qb->delete('share'); + $qb->execute(); + + return parent::tearDown(); + } + + public function testRun() { + $qb = $this->connection->getQueryBuilder(); + $qb->insert('share') + ->values([ + 'share_type' => $qb->createNamedParameter(0), + 'share_with' => $qb->createNamedParameter('foo'), + 'uid_owner' => $qb->createNamedParameter('owner'), + 'item_type' => $qb->createNamedParameter('file'), + 'item_source' => $qb->createNamedParameter(42), + 'item_target' => $qb->createNamedParameter('/target'), + 'file_source' => $qb->createNamedParameter(42), + 'file_target' => $qb->createNamedParameter('/target'), + 'permissions' => $qb->createNamedParameter(1), + ]); + $qb->execute(); + + $qb = $this->connection->getQueryBuilder(); + $qb->insert('share') + ->values([ + 'share_type' => $qb->createNamedParameter(0), + 'share_with' => $qb->createNamedParameter('foo'), + 'uid_owner' => $qb->createNamedParameter('owner'), + 'item_type' => $qb->createNamedParameter('calendar'), + 'item_source' => $qb->createNamedParameter(42), + 'item_target' => $qb->createNamedParameter('/target'), + 'file_source' => $qb->createNamedParameter(42), + 'file_target' => $qb->createNamedParameter('/target'), + 'permissions' => $qb->createNamedParameter(1), + ]); + $qb->execute(); + + $qb = $this->connection->getQueryBuilder(); + $qb->insert('share') + ->values([ + 'share_type' => $qb->createNamedParameter(0), + 'share_with' => $qb->createNamedParameter('foo'), + 'uid_owner' => $qb->createNamedParameter('owner'), + 'item_type' => $qb->createNamedParameter('event'), + 'item_source' => $qb->createNamedParameter(42), + 'item_target' => $qb->createNamedParameter('/target'), + 'file_source' => $qb->createNamedParameter(42), + 'file_target' => $qb->createNamedParameter('/target'), + 'permissions' => $qb->createNamedParameter(1), + ]); + $qb->execute(); + + $qb = $this->connection->getQueryBuilder(); + $qb->insert('share') + ->values([ + 'share_type' => $qb->createNamedParameter(0), + 'share_with' => $qb->createNamedParameter('foo'), + 'uid_owner' => $qb->createNamedParameter('owner'), + 'item_type' => $qb->createNamedParameter('contact'), + 'item_source' => $qb->createNamedParameter(42), + 'item_target' => $qb->createNamedParameter('/target'), + 'file_source' => $qb->createNamedParameter(42), + 'file_target' => $qb->createNamedParameter('/target'), + 'permissions' => $qb->createNamedParameter(1), + ]); + $qb->execute(); + + $qb = $this->connection->getQueryBuilder(); + $qb->insert('share') + ->values([ + 'share_type' => $qb->createNamedParameter(0), + 'share_with' => $qb->createNamedParameter('foo'), + 'uid_owner' => $qb->createNamedParameter('owner'), + 'item_type' => $qb->createNamedParameter('addressbook'), + 'item_source' => $qb->createNamedParameter(42), + 'item_target' => $qb->createNamedParameter('/target'), + 'file_source' => $qb->createNamedParameter(42), + 'file_target' => $qb->createNamedParameter('/target'), + 'permissions' => $qb->createNamedParameter(1), + ]); + $qb->execute(); + + $qb = $this->connection->getQueryBuilder(); + $qb->selectAlias($qb->createFunction('COUNT(*)'), 'count') + ->from('share'); + + $cursor = $qb->execute(); + $data = $cursor->fetchAll(); + $cursor->closeCursor(); + $this->assertEquals(5, $data[0]['count']); + + $this->repair->run($this->outputMock); + + $qb = $this->connection->getQueryBuilder(); + $qb->select('*') + ->from('share'); + + $cursor = $qb->execute(); + $data = $cursor->fetchAll(); + $cursor->closeCursor(); + $this->assertCount(1, $data); + $this->assertEquals('file', $data[0]['item_type']); + } +} From 1399e87d57e1b91a5d6888021bef310d35f10b4f Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Thu, 9 Jun 2016 11:29:20 +0200 Subject: [PATCH 18/30] DAV now returns file name with Content-Disposition header Fixes issue where Chrome would append ".txt" to XML files when downloaded in the web UI --- apps/dav/lib/Connector/Sabre/FilesPlugin.php | 23 ++++++- .../dav/lib/Connector/Sabre/ServerFactory.php | 1 + apps/dav/lib/Server.php | 1 + .../unit/Connector/Sabre/FilesPluginTest.php | 66 ++++++++++++++++++- .../Connector/Sabre/FilesReportPluginTest.php | 3 +- .../features/webdav-related.feature | 4 +- 6 files changed, 93 insertions(+), 5 deletions(-) diff --git a/apps/dav/lib/Connector/Sabre/FilesPlugin.php b/apps/dav/lib/Connector/Sabre/FilesPlugin.php index dc47416cca..0a2e6713cb 100644 --- a/apps/dav/lib/Connector/Sabre/FilesPlugin.php +++ b/apps/dav/lib/Connector/Sabre/FilesPlugin.php @@ -42,6 +42,7 @@ use \Sabre\HTTP\RequestInterface; use \Sabre\HTTP\ResponseInterface; use OCP\Files\StorageNotAvailableException; use OCP\IConfig; +use OCP\IRequest; class FilesPlugin extends ServerPlugin { @@ -95,20 +96,29 @@ class FilesPlugin extends ServerPlugin { */ private $config; + /** + * @var IRequest + */ + private $request; + /** * @param Tree $tree * @param View $view + * @param IConfig $config + * @param IRequest $request * @param bool $isPublic * @param bool $downloadAttachment */ public function __construct(Tree $tree, View $view, IConfig $config, + IRequest $request, $isPublic = false, $downloadAttachment = true) { $this->tree = $tree; $this->fileView = $view; $this->config = $config; + $this->request = $request; $this->isPublic = $isPublic; $this->downloadAttachment = $downloadAttachment; } @@ -225,7 +235,18 @@ class FilesPlugin extends ServerPlugin { // adds a 'Content-Disposition: attachment' header if ($this->downloadAttachment) { - $response->addHeader('Content-Disposition', 'attachment'); + $filename = $node->getName(); + if ($this->request->isUserAgent( + [ + \OC\AppFramework\Http\Request::USER_AGENT_IE, + \OC\AppFramework\Http\Request::USER_AGENT_ANDROID_MOBILE_CHROME, + \OC\AppFramework\Http\Request::USER_AGENT_FREEBOX, + ])) { + $response->addHeader('Content-Disposition', 'attachment; filename="' . rawurlencode($filename) . '"'); + } else { + $response->addHeader('Content-Disposition', 'attachment; filename*=UTF-8\'\'' . rawurlencode($filename) + . '; filename="' . rawurlencode($filename) . '"'); + } } if ($node instanceof \OCA\DAV\Connector\Sabre\File) { diff --git a/apps/dav/lib/Connector/Sabre/ServerFactory.php b/apps/dav/lib/Connector/Sabre/ServerFactory.php index b193bfc76c..699dd77166 100644 --- a/apps/dav/lib/Connector/Sabre/ServerFactory.php +++ b/apps/dav/lib/Connector/Sabre/ServerFactory.php @@ -144,6 +144,7 @@ class ServerFactory { $objectTree, $view, $this->config, + $this->request, false, !$this->config->getSystemValue('debug', false) ) diff --git a/apps/dav/lib/Server.php b/apps/dav/lib/Server.php index 179558e97a..e150f441b8 100644 --- a/apps/dav/lib/Server.php +++ b/apps/dav/lib/Server.php @@ -141,6 +141,7 @@ class Server { $this->server->tree, $view, \OC::$server->getConfig(), + $this->request, false, !\OC::$server->getConfig()->getSystemValue('debug', false) ) diff --git a/apps/dav/tests/unit/Connector/Sabre/FilesPluginTest.php b/apps/dav/tests/unit/Connector/Sabre/FilesPluginTest.php index 80f284e470..2b3f3e15d1 100644 --- a/apps/dav/tests/unit/Connector/Sabre/FilesPluginTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/FilesPluginTest.php @@ -73,6 +73,11 @@ class FilesPluginTest extends TestCase { */ private $config; + /** + * @var \OCP\IRequest | \PHPUnit_Framework_MockObject_MockObject + */ + private $request; + public function setUp() { parent::setUp(); $this->server = $this->getMockBuilder('\Sabre\DAV\Server') @@ -88,11 +93,13 @@ class FilesPluginTest extends TestCase { $this->config->expects($this->any())->method('getSystemValue') ->with($this->equalTo('data-fingerprint'), $this->equalTo('')) ->willReturn('my_fingerprint'); + $this->request = $this->getMock('\OCP\IRequest'); $this->plugin = new FilesPlugin( $this->tree, $this->view, - $this->config + $this->config, + $this->request ); $this->plugin->initialize($this->server); } @@ -268,6 +275,7 @@ class FilesPluginTest extends TestCase { $this->tree, $this->view, $this->config, + $this->getMock('\OCP\IRequest'), true); $this->plugin->initialize($this->server); @@ -484,4 +492,60 @@ class FilesPluginTest extends TestCase { $this->plugin->checkMove('FolderA/test.txt', 'test.txt'); } + + public function downloadHeadersProvider() { + return [ + [ + false, + 'attachment; filename*=UTF-8\'\'somefile.xml; filename="somefile.xml"' + ], + [ + true, + 'attachment; filename="somefile.xml"' + ], + ]; + } + + /** + * @dataProvider downloadHeadersProvider + */ + public function testDownloadHeaders($isClumsyAgent, $contentDispositionHeader) { + $request = $this->getMockBuilder('Sabre\HTTP\RequestInterface') + ->disableOriginalConstructor() + ->getMock(); + $response = $this->getMockBuilder('Sabre\HTTP\ResponseInterface') + ->disableOriginalConstructor() + ->getMock(); + + $request + ->expects($this->once()) + ->method('getPath') + ->will($this->returnValue('test/somefile.xml')); + + $node = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\File') + ->disableOriginalConstructor() + ->getMock(); + $node + ->expects($this->once()) + ->method('getName') + ->will($this->returnValue('somefile.xml')); + + $this->tree + ->expects($this->once()) + ->method('getNodeForPath') + ->with('test/somefile.xml') + ->will($this->returnValue($node)); + + $this->request + ->expects($this->once()) + ->method('isUserAgent') + ->will($this->returnValue($isClumsyAgent)); + + $response + ->expects($this->once()) + ->method('addHeader') + ->with('Content-Disposition', $contentDispositionHeader); + + $this->plugin->httpGet($request, $response); + } } diff --git a/apps/dav/tests/unit/Connector/Sabre/FilesReportPluginTest.php b/apps/dav/tests/unit/Connector/Sabre/FilesReportPluginTest.php index 41d44efd89..baf4259b21 100644 --- a/apps/dav/tests/unit/Connector/Sabre/FilesReportPluginTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/FilesReportPluginTest.php @@ -343,7 +343,8 @@ class FilesReportPluginTest extends \Test\TestCase { new \OCA\DAV\Connector\Sabre\FilesPlugin( $this->tree, $this->view, - $config + $config, + $this->getMock('\OCP\IRequest') ) ); $this->plugin->initialize($this->server); diff --git a/build/integration/features/webdav-related.feature b/build/integration/features/webdav-related.feature index f4d40615fa..14ff505463 100644 --- a/build/integration/features/webdav-related.feature +++ b/build/integration/features/webdav-related.feature @@ -82,7 +82,7 @@ Feature: webdav-related And As an "admin" When Downloading file "/welcome.txt" Then The following headers should be set - |Content-Disposition|attachment| + |Content-Disposition|attachment; filename*=UTF-8''welcome.txt; filename="welcome.txt"| |Content-Security-Policy|default-src 'none';| |X-Content-Type-Options |nosniff| |X-Download-Options|noopen| @@ -97,7 +97,7 @@ Feature: webdav-related And As an "admin" When Downloading file "/welcome.txt" Then The following headers should be set - |Content-Disposition|attachment| + |Content-Disposition|attachment; filename*=UTF-8''welcome.txt; filename="welcome.txt"| |Content-Security-Policy|default-src 'none';| |X-Content-Type-Options |nosniff| |X-Download-Options|noopen| From 232d7358934ab8e1fa5e871c37f0997e5f394e86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Thu, 9 Jun 2016 16:44:31 +0200 Subject: [PATCH 19/30] Do not leak the login name - fixes #25047 --- core/Controller/LoginController.php | 5 +- tests/Core/Controller/LoginControllerTest.php | 48 +++++++++++++++---- 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/core/Controller/LoginController.php b/core/Controller/LoginController.php index c64f58ae2c..7806e1de90 100644 --- a/core/Controller/LoginController.php +++ b/core/Controller/LoginController.php @@ -171,6 +171,7 @@ class LoginController extends Controller { * @return RedirectResponse */ public function tryLogin($user, $password, $redirect_url) { + $originalUser = $user; // TODO: Add all the insane error handling /* @var $loginResult IUser */ $loginResult = $this->userManager->checkPassword($user, $password); @@ -186,8 +187,8 @@ class LoginController extends Controller { $this->session->set('loginMessages', [ ['invalidpassword'] ]); - // Read current user and append if possible - $args = !is_null($user) ? ['user' => $user] : []; + // Read current user and append if possible - we need to return the unmodified user otherwise we will leak the login name + $args = !is_null($user) ? ['user' => $originalUser] : []; return new RedirectResponse($this->urlGenerator->linkToRoute('core.login.showLoginForm', $args)); } // TODO: remove password checks from above and let the user session handle failures diff --git a/tests/Core/Controller/LoginControllerTest.php b/tests/Core/Controller/LoginControllerTest.php index ea9d6a4414..d6fa772d38 100644 --- a/tests/Core/Controller/LoginControllerTest.php +++ b/tests/Core/Controller/LoginControllerTest.php @@ -29,6 +29,7 @@ use OCP\IConfig; use OCP\IRequest; use OCP\ISession; use OCP\IURLGenerator; +use OCP\IUser; use OCP\IUserManager; use OCP\IUserSession; use Test\TestCase; @@ -36,19 +37,19 @@ use Test\TestCase; class LoginControllerTest extends TestCase { /** @var LoginController */ private $loginController; - /** @var IRequest */ + /** @var IRequest | \PHPUnit_Framework_MockObject_MockObject */ private $request; - /** @var IUserManager */ + /** @var IUserManager | \PHPUnit_Framework_MockObject_MockObject */ private $userManager; - /** @var IConfig */ + /** @var IConfig | \PHPUnit_Framework_MockObject_MockObject */ private $config; - /** @var ISession */ + /** @var ISession | \PHPUnit_Framework_MockObject_MockObject */ private $session; - /** @var IUserSession */ + /** @var IUserSession | \PHPUnit_Framework_MockObject_MockObject */ private $userSession; - /** @var IURLGenerator */ + /** @var IURLGenerator | \PHPUnit_Framework_MockObject_MockObject */ private $urlGenerator; - /** @var Manager */ + /** @var Manager | \PHPUnit_Framework_MockObject_MockObject */ private $twoFactorManager; public function setUp() { @@ -296,6 +297,7 @@ class LoginControllerTest extends TestCase { } public function testLoginWithValidCredentials() { + /** @var IUser | \PHPUnit_Framework_MockObject_MockObject $user */ $user = $this->getMock('\OCP\IUser'); $password = 'secret'; $indexPageUrl = 'some url'; @@ -323,6 +325,7 @@ class LoginControllerTest extends TestCase { } public function testLoginWithValidCredentialsAndRedirectUrl() { + /** @var IUser | \PHPUnit_Framework_MockObject_MockObject $user */ $user = $this->getMock('\OCP\IUser'); $user->expects($this->any()) ->method('getUID') @@ -352,6 +355,7 @@ class LoginControllerTest extends TestCase { } public function testLoginWithTwoFactorEnforced() { + /** @var IUser | \PHPUnit_Framework_MockObject_MockObject $user */ $user = $this->getMock('\OCP\IUser'); $user->expects($this->any()) ->method('getUID') @@ -380,8 +384,36 @@ class LoginControllerTest extends TestCase { ->with('core.TwoFactorChallenge.selectChallenge') ->will($this->returnValue($challengeUrl)); - $expected = new \OCP\AppFramework\Http\RedirectResponse($challengeUrl); + $expected = new RedirectResponse($challengeUrl); $this->assertEquals($expected, $this->loginController->tryLogin('john@doe.com', $password, null)); } + public function testToNotLeakLoginName() { + /** @var IUser | \PHPUnit_Framework_MockObject_MockObject $user */ + $user = $this->getMock('\OCP\IUser'); + $user->expects($this->any()) + ->method('getUID') + ->will($this->returnValue('john')); + + $this->userManager->expects($this->exactly(2)) + ->method('checkPassword') + ->withConsecutive( + ['john@doe.com', 'just wrong'], + ['john', 'just wrong'] + ) + ->willReturn(false); + + $this->userManager->expects($this->once()) + ->method('getByEmail') + ->with('john@doe.com') + ->willReturn([$user]); + + $this->urlGenerator->expects($this->once()) + ->method('linkToRoute') + ->with('core.login.showLoginForm', ['user' => 'john@doe.com']) + ->will($this->returnValue('')); + + $expected = new RedirectResponse(''); + $this->assertEquals($expected, $this->loginController->tryLogin('john@doe.com', 'just wrong', null)); + } } From 059778bef0fbc37b7d46a0ca2a02dcb339e36f9a Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Thu, 9 Jun 2016 17:57:07 +0200 Subject: [PATCH 20/30] Add unit test for LDAP multi group caching --- apps/user_ldap/tests/Group_LDAPTest.php | 53 +++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/apps/user_ldap/tests/Group_LDAPTest.php b/apps/user_ldap/tests/Group_LDAPTest.php index 556c4b0b39..35d525068a 100644 --- a/apps/user_ldap/tests/Group_LDAPTest.php +++ b/apps/user_ldap/tests/Group_LDAPTest.php @@ -454,4 +454,57 @@ class Group_LDAPTest extends \Test\TestCase { $groupBackend->getUserGroups('userX'); } + public function testGetGroupsByMember() { + $access = $this->getAccessMock(); + + $access->connection->expects($this->any()) + ->method('__get') + ->will($this->returnCallback(function($name) { + if($name === 'useMemberOfToDetectMembership') { + return 0; + } else if($name === 'ldapDynamicGroupMemberURL') { + return ''; + } else if($name === 'ldapNestedGroups') { + return false; + } + return 1; + })); + + $dn = 'cn=userX,dc=foobar'; + + $access->connection->hasPrimaryGroups = false; + + $access->expects($this->exactly(2)) + ->method('username2dn') + ->will($this->returnValue($dn)); + + $access->expects($this->never()) + ->method('readAttribute') + ->with($dn, 'memberOf'); + + $group1 = [ + 'cn' => 'group1', + 'dn' => ['cn=group1,ou=groups,dc=domain,dc=com'], + ]; + $group2 = [ + 'cn' => 'group2', + 'dn' => ['cn=group2,ou=groups,dc=domain,dc=com'], + ]; + + $access->expects($this->once()) + ->method('ownCloudGroupNames') + ->with([$group1, $group2]) + ->will($this->returnValue(['group1', 'group2'])); + + $access->expects($this->once()) + ->method('fetchListOfGroups') + ->will($this->returnValue([$group1, $group2])); + + $groupBackend = new GroupLDAP($access); + $groups = $groupBackend->getUserGroups('userX'); + $this->assertEquals(['group1', 'group2'], $groups); + + $groupsAgain = $groupBackend->getUserGroups('userX'); + $this->assertEquals(['group1', 'group2'], $groupsAgain); + } } From e8a66f8e7d6079c0982e2e377ca9eea7e451acea Mon Sep 17 00:00:00 2001 From: Jenkins for ownCloud Date: Fri, 10 Jun 2016 05:55:19 +0000 Subject: [PATCH 21/30] [tx-robot] updated from transifex --- apps/comments/l10n/bg_BG.js | 13 +++++- apps/comments/l10n/bg_BG.json | 13 +++++- apps/encryption/l10n/pl.js | 2 + apps/encryption/l10n/pl.json | 2 + apps/federatedfilesharing/l10n/hu_HU.js | 4 ++ apps/federatedfilesharing/l10n/hu_HU.json | 4 ++ apps/federation/l10n/pt_PT.js | 2 +- apps/federation/l10n/pt_PT.json | 2 +- apps/federation/l10n/ro.js | 8 +++- apps/federation/l10n/ro.json | 8 +++- apps/files/l10n/ar.js | 6 +++ apps/files/l10n/ar.json | 6 +++ apps/files/l10n/de.js | 1 + apps/files/l10n/de.json | 1 + apps/files/l10n/de_DE.js | 1 + apps/files/l10n/de_DE.json | 1 + apps/files/l10n/en_GB.js | 1 + apps/files/l10n/en_GB.json | 1 + apps/files/l10n/fi_FI.js | 1 + apps/files/l10n/fi_FI.json | 1 + apps/files/l10n/it.js | 1 + apps/files/l10n/it.json | 1 + apps/files/l10n/pl.js | 2 + apps/files/l10n/pl.json | 2 + apps/files/l10n/pt_BR.js | 1 + apps/files/l10n/pt_BR.json | 1 + apps/files/l10n/sq.js | 1 + apps/files/l10n/sq.json | 1 + apps/files_external/l10n/pl.js | 1 + apps/files_external/l10n/pl.json | 1 + apps/files_sharing/l10n/pl.js | 3 ++ apps/files_sharing/l10n/pl.json | 3 ++ apps/files_sharing/l10n/ro.js | 22 ++++++++- apps/files_sharing/l10n/ro.json | 22 ++++++++- apps/systemtags/l10n/hu_HU.js | 1 + apps/systemtags/l10n/hu_HU.json | 1 + apps/updatenotification/l10n/hu_HU.js | 3 ++ apps/updatenotification/l10n/hu_HU.json | 3 ++ apps/user_ldap/l10n/pl.js | 1 + apps/user_ldap/l10n/pl.json | 1 + core/l10n/de.js | 1 + core/l10n/de.json | 1 + core/l10n/de_DE.js | 1 + core/l10n/de_DE.json | 1 + core/l10n/en_GB.js | 1 + core/l10n/en_GB.json | 1 + core/l10n/fi_FI.js | 2 + core/l10n/fi_FI.json | 2 + core/l10n/it.js | 1 + core/l10n/it.json | 1 + core/l10n/pt_BR.js | 1 + core/l10n/pt_BR.json | 1 + core/l10n/ro.js | 56 ++++++++++++++++++++++- core/l10n/ro.json | 56 ++++++++++++++++++++++- core/l10n/ru.js | 1 + core/l10n/ru.json | 1 + core/l10n/sq.js | 1 + core/l10n/sq.json | 1 + lib/l10n/pl.js | 1 + lib/l10n/pl.json | 1 + lib/l10n/pt_PT.js | 4 +- lib/l10n/pt_PT.json | 4 +- settings/l10n/en_GB.js | 2 + settings/l10n/en_GB.json | 2 + 64 files changed, 280 insertions(+), 14 deletions(-) diff --git a/apps/comments/l10n/bg_BG.js b/apps/comments/l10n/bg_BG.js index e63aedf80d..c5fbba69f7 100644 --- a/apps/comments/l10n/bg_BG.js +++ b/apps/comments/l10n/bg_BG.js @@ -1,8 +1,19 @@ OC.L10N.register( "comments", { + "Type in a new comment..." : "Напиши нов коментар...", + "Delete comment" : "Изтрий коментар", "Cancel" : "Отказ", + "Edit comment" : "Редактирай коментра", + "[Deleted user]" : "[Изтрит потребител]", + "Comments" : "Коментари", + "No other comments available" : "Няма други коментари", + "More comments..." : "Още коментари...", "Save" : "Запазване", - "Comment" : "Коментар" + "Allowed characters {count} of {max}" : "Позволени символи {count} от {max}", + "{count} unread comments" : "{count} нечетени коментари", + "Comment" : "Коментар", + "Comments for files (always listed in stream)" : "Коментари на файлове (винаги изписвани в stream-а)", + "You commented" : "Вие коментирахте" }, "nplurals=2; plural=(n != 1);"); diff --git a/apps/comments/l10n/bg_BG.json b/apps/comments/l10n/bg_BG.json index 78ad0b57d4..64f516861c 100644 --- a/apps/comments/l10n/bg_BG.json +++ b/apps/comments/l10n/bg_BG.json @@ -1,6 +1,17 @@ { "translations": { + "Type in a new comment..." : "Напиши нов коментар...", + "Delete comment" : "Изтрий коментар", "Cancel" : "Отказ", + "Edit comment" : "Редактирай коментра", + "[Deleted user]" : "[Изтрит потребител]", + "Comments" : "Коментари", + "No other comments available" : "Няма други коментари", + "More comments..." : "Още коментари...", "Save" : "Запазване", - "Comment" : "Коментар" + "Allowed characters {count} of {max}" : "Позволени символи {count} от {max}", + "{count} unread comments" : "{count} нечетени коментари", + "Comment" : "Коментар", + "Comments for files (always listed in stream)" : "Коментари на файлове (винаги изписвани в stream-а)", + "You commented" : "Вие коментирахте" },"pluralForm" :"nplurals=2; plural=(n != 1);" } \ No newline at end of file diff --git a/apps/encryption/l10n/pl.js b/apps/encryption/l10n/pl.js index 164a4a36d5..17f73e4fdb 100644 --- a/apps/encryption/l10n/pl.js +++ b/apps/encryption/l10n/pl.js @@ -27,6 +27,8 @@ OC.L10N.register( "Can not decrypt this file, probably this is a shared file. Please ask the file owner to reshare the file with you." : "Nie można odszyfrować tego pliku, prawdopodobnie jest to plik udostępniony. Poproś właściciela pliku o ponowne udostępnianie pliku Tobie.", "The share will expire on %s." : "Ten zasób wygaśnie %s", "Cheers!" : "Dzięki!", + "Enable recovery key" : "Włącz klucz odzyskiwania", + "Disable recovery key" : "Wyłącz klucz odzyskiwania", "Recovery key password" : "Hasło klucza odzyskiwania", "Repeat recovery key password" : "Powtórz hasło klucza odzyskiwania", "Change recovery key password:" : "Zmień hasło klucza odzyskiwania", diff --git a/apps/encryption/l10n/pl.json b/apps/encryption/l10n/pl.json index 2bd108ba4c..f1ef3faf44 100644 --- a/apps/encryption/l10n/pl.json +++ b/apps/encryption/l10n/pl.json @@ -25,6 +25,8 @@ "Can not decrypt this file, probably this is a shared file. Please ask the file owner to reshare the file with you." : "Nie można odszyfrować tego pliku, prawdopodobnie jest to plik udostępniony. Poproś właściciela pliku o ponowne udostępnianie pliku Tobie.", "The share will expire on %s." : "Ten zasób wygaśnie %s", "Cheers!" : "Dzięki!", + "Enable recovery key" : "Włącz klucz odzyskiwania", + "Disable recovery key" : "Wyłącz klucz odzyskiwania", "Recovery key password" : "Hasło klucza odzyskiwania", "Repeat recovery key password" : "Powtórz hasło klucza odzyskiwania", "Change recovery key password:" : "Zmień hasło klucza odzyskiwania", diff --git a/apps/federatedfilesharing/l10n/hu_HU.js b/apps/federatedfilesharing/l10n/hu_HU.js index 0f0ffdd129..0a4f26cb20 100644 --- a/apps/federatedfilesharing/l10n/hu_HU.js +++ b/apps/federatedfilesharing/l10n/hu_HU.js @@ -1,10 +1,14 @@ OC.L10N.register( "federatedfilesharing", { + "Federated sharing" : "Egyesített megosztás", "Invalid Federated Cloud ID" : "Érvénytelen Egyesített Felhő Azonosító", "Sharing %s failed, because this item is already shared with %s" : "%s megosztása nem sikerült, mert ez már meg van osztva vele: %s", "Not allowed to create a federated share with the same user" : "Azonos felhasználóval nem lehet létrehozni egyesített megosztást", + "File is already shared with %s" : "Fájl már megosztva vele: %s", "Sharing %s failed, could not find %s, maybe the server is currently unreachable." : "%s megosztása sikertelen, mert %s nem található; talán a szerver jelenleg nem elérhető.", + "You received \"/%3$s\" as a remote share from %1$s (on behalf of %2$s)" : "Kapott egy távoli megosztást: \"/%3$s\", innen: %1$s (%2$s nevében)", + "You received \"/%3$s\" as a remote share from %1$s" : "Kapott egy távoli megosztást: \"/%3$s\", innen: %1$s", "Accept" : "Elfogadás", "Decline" : "Elutasítás", "Share with me through my #ownCloud Federated Cloud ID, see %s" : "Ossza meg velem az #ownCloud Egyesített Felhő Azonosító segítségével, lásd %s", diff --git a/apps/federatedfilesharing/l10n/hu_HU.json b/apps/federatedfilesharing/l10n/hu_HU.json index 24e25b6aa4..77a12ece1a 100644 --- a/apps/federatedfilesharing/l10n/hu_HU.json +++ b/apps/federatedfilesharing/l10n/hu_HU.json @@ -1,8 +1,12 @@ { "translations": { + "Federated sharing" : "Egyesített megosztás", "Invalid Federated Cloud ID" : "Érvénytelen Egyesített Felhő Azonosító", "Sharing %s failed, because this item is already shared with %s" : "%s megosztása nem sikerült, mert ez már meg van osztva vele: %s", "Not allowed to create a federated share with the same user" : "Azonos felhasználóval nem lehet létrehozni egyesített megosztást", + "File is already shared with %s" : "Fájl már megosztva vele: %s", "Sharing %s failed, could not find %s, maybe the server is currently unreachable." : "%s megosztása sikertelen, mert %s nem található; talán a szerver jelenleg nem elérhető.", + "You received \"/%3$s\" as a remote share from %1$s (on behalf of %2$s)" : "Kapott egy távoli megosztást: \"/%3$s\", innen: %1$s (%2$s nevében)", + "You received \"/%3$s\" as a remote share from %1$s" : "Kapott egy távoli megosztást: \"/%3$s\", innen: %1$s", "Accept" : "Elfogadás", "Decline" : "Elutasítás", "Share with me through my #ownCloud Federated Cloud ID, see %s" : "Ossza meg velem az #ownCloud Egyesített Felhő Azonosító segítségével, lásd %s", diff --git a/apps/federation/l10n/pt_PT.js b/apps/federation/l10n/pt_PT.js index 38ec142b68..c227ab490b 100644 --- a/apps/federation/l10n/pt_PT.js +++ b/apps/federation/l10n/pt_PT.js @@ -6,7 +6,7 @@ OC.L10N.register( "No ownCloud server found" : "Nenhum servidor ownCloud encontrado", "Could not add server" : "Não foi possível adicionar servidor", "Federation" : "Federação", - "ownCloud Federation allows you to connect with other trusted ownClouds to exchange the user directory. For example this will be used to auto-complete external users for federated sharing." : "Federação ownCloud permite-lhe conectar-se com outros ownClouds de confiança para partilhar directórios. Por exemplo, isto será utilizado para auto-completar utilizadores externos para partilhas federadas.", + "ownCloud Federation allows you to connect with other trusted ownClouds to exchange the user directory. For example this will be used to auto-complete external users for federated sharing." : "A Federação ownCloud permite-lhe conectar-se com outras ownClouds de confiança para partilhar directorias. Por exemplo, isto será utilizado para auto-completar utilizadores externos para partilhas federadas.", "Add server automatically once a federated share was created successfully" : "Adicionar o servidor automaticamente assim que uma partilha federada tenha sido criada com sucesso", "Trusted ownCloud Servers" : "Servidores ownCloud de confiança", "+ Add ownCloud server" : "+ Adicionar servidor ownCloud", diff --git a/apps/federation/l10n/pt_PT.json b/apps/federation/l10n/pt_PT.json index 796a212717..2316af9c25 100644 --- a/apps/federation/l10n/pt_PT.json +++ b/apps/federation/l10n/pt_PT.json @@ -4,7 +4,7 @@ "No ownCloud server found" : "Nenhum servidor ownCloud encontrado", "Could not add server" : "Não foi possível adicionar servidor", "Federation" : "Federação", - "ownCloud Federation allows you to connect with other trusted ownClouds to exchange the user directory. For example this will be used to auto-complete external users for federated sharing." : "Federação ownCloud permite-lhe conectar-se com outros ownClouds de confiança para partilhar directórios. Por exemplo, isto será utilizado para auto-completar utilizadores externos para partilhas federadas.", + "ownCloud Federation allows you to connect with other trusted ownClouds to exchange the user directory. For example this will be used to auto-complete external users for federated sharing." : "A Federação ownCloud permite-lhe conectar-se com outras ownClouds de confiança para partilhar directorias. Por exemplo, isto será utilizado para auto-completar utilizadores externos para partilhas federadas.", "Add server automatically once a federated share was created successfully" : "Adicionar o servidor automaticamente assim que uma partilha federada tenha sido criada com sucesso", "Trusted ownCloud Servers" : "Servidores ownCloud de confiança", "+ Add ownCloud server" : "+ Adicionar servidor ownCloud", diff --git a/apps/federation/l10n/ro.js b/apps/federation/l10n/ro.js index e303595514..19f37f329a 100644 --- a/apps/federation/l10n/ro.js +++ b/apps/federation/l10n/ro.js @@ -4,6 +4,12 @@ OC.L10N.register( "Server added to the list of trusted ownClouds" : "Server adăugat la lista serverelor ownCloud de încredere", "Server is already in the list of trusted servers." : "Serverul este deja pe lista celor de încredere.", "No ownCloud server found" : "Nu s-a găsit niciun server ownCloud", - "Could not add server" : "Nu s-a putut adăuga serverul" + "Could not add server" : "Nu s-a putut adăuga serverul", + "Federation" : "Federare", + "ownCloud Federation allows you to connect with other trusted ownClouds to exchange the user directory. For example this will be used to auto-complete external users for federated sharing." : "Federarea ownCloud îți permite să te conectezi la alte servere ownCloud de încredere pentru a partaja baza de utilizatori. De exemplu, va permite completarea automată a numelor utilizatorilor externi pentru partajarea federată.", + "Add server automatically once a federated share was created successfully" : "Adaugă serverul automat odată ce elementul partajat federat a fost creat cu succes", + "Trusted ownCloud Servers" : "Servere ownCloud de încredere", + "+ Add ownCloud server" : "+ Adaugă server ownCloud", + "ownCloud Server" : "Server ownCloud" }, "nplurals=3; plural=(n==1?0:(((n%100>19)||((n%100==0)&&(n!=0)))?2:1));"); diff --git a/apps/federation/l10n/ro.json b/apps/federation/l10n/ro.json index 75c514fd8c..0acb4ebd96 100644 --- a/apps/federation/l10n/ro.json +++ b/apps/federation/l10n/ro.json @@ -2,6 +2,12 @@ "Server added to the list of trusted ownClouds" : "Server adăugat la lista serverelor ownCloud de încredere", "Server is already in the list of trusted servers." : "Serverul este deja pe lista celor de încredere.", "No ownCloud server found" : "Nu s-a găsit niciun server ownCloud", - "Could not add server" : "Nu s-a putut adăuga serverul" + "Could not add server" : "Nu s-a putut adăuga serverul", + "Federation" : "Federare", + "ownCloud Federation allows you to connect with other trusted ownClouds to exchange the user directory. For example this will be used to auto-complete external users for federated sharing." : "Federarea ownCloud îți permite să te conectezi la alte servere ownCloud de încredere pentru a partaja baza de utilizatori. De exemplu, va permite completarea automată a numelor utilizatorilor externi pentru partajarea federată.", + "Add server automatically once a federated share was created successfully" : "Adaugă serverul automat odată ce elementul partajat federat a fost creat cu succes", + "Trusted ownCloud Servers" : "Servere ownCloud de încredere", + "+ Add ownCloud server" : "+ Adaugă server ownCloud", + "ownCloud Server" : "Server ownCloud" },"pluralForm" :"nplurals=3; plural=(n==1?0:(((n%100>19)||((n%100==0)&&(n!=0)))?2:1));" } \ No newline at end of file diff --git a/apps/files/l10n/ar.js b/apps/files/l10n/ar.js index a9093c45a9..ae180fca23 100644 --- a/apps/files/l10n/ar.js +++ b/apps/files/l10n/ar.js @@ -41,6 +41,12 @@ OC.L10N.register( "Pending" : "قيد الانتظار", "Unable to determine date" : "تعذر تحديد التاريخ", "This operation is forbidden" : "هذة العملية ممنوعة ", + "Could not rename \"{fileName}\"" : "إعادة تسمية الملف \"{fileName}\" لم تنجح", + "Could not create file \"{file}\"" : "لا يمكن إنشاء الملف\"{file}\"", + "Could not create file \"{file}\" because it already exists" : "لا يمكن إنشاء الملف \"{file}\" فهو موجود بالفعل", + "Could not create folder \"{dir}\"" : "لا يمكن إنشاء المجلد \"{dir}\"", + "Could not create folder \"{dir}\" because it already exists" : "لا يمكن إنشاء المجلد \"{dir}\" فهو موجود بالفعل", + "Error deleting file \"{fileName}\"." : "خطأ أثناء حذف الملف \"{fileName}\".", "No entries in this folder match '{filter}'" : "لا يوجد مدخلات في هذا المجلد تتوافق مع '{filter}'", "Name" : "اسم", "Size" : "حجم", diff --git a/apps/files/l10n/ar.json b/apps/files/l10n/ar.json index fe07f30c03..257aa4f1bb 100644 --- a/apps/files/l10n/ar.json +++ b/apps/files/l10n/ar.json @@ -39,6 +39,12 @@ "Pending" : "قيد الانتظار", "Unable to determine date" : "تعذر تحديد التاريخ", "This operation is forbidden" : "هذة العملية ممنوعة ", + "Could not rename \"{fileName}\"" : "إعادة تسمية الملف \"{fileName}\" لم تنجح", + "Could not create file \"{file}\"" : "لا يمكن إنشاء الملف\"{file}\"", + "Could not create file \"{file}\" because it already exists" : "لا يمكن إنشاء الملف \"{file}\" فهو موجود بالفعل", + "Could not create folder \"{dir}\"" : "لا يمكن إنشاء المجلد \"{dir}\"", + "Could not create folder \"{dir}\" because it already exists" : "لا يمكن إنشاء المجلد \"{dir}\" فهو موجود بالفعل", + "Error deleting file \"{fileName}\"." : "خطأ أثناء حذف الملف \"{fileName}\".", "No entries in this folder match '{filter}'" : "لا يوجد مدخلات في هذا المجلد تتوافق مع '{filter}'", "Name" : "اسم", "Size" : "حجم", diff --git a/apps/files/l10n/de.js b/apps/files/l10n/de.js index 068cbe8537..0866279a43 100644 --- a/apps/files/l10n/de.js +++ b/apps/files/l10n/de.js @@ -21,6 +21,7 @@ OC.L10N.register( "Invalid directory." : "Ungültiges Verzeichnis.", "Files" : "Dateien", "All files" : "Alle Dateien", + "File could not be found" : "Datei konnte nicht gefunden werden", "Home" : "Home", "Close" : "Schließen", "Favorites" : "Favoriten", diff --git a/apps/files/l10n/de.json b/apps/files/l10n/de.json index 2169d22829..0f2939217d 100644 --- a/apps/files/l10n/de.json +++ b/apps/files/l10n/de.json @@ -19,6 +19,7 @@ "Invalid directory." : "Ungültiges Verzeichnis.", "Files" : "Dateien", "All files" : "Alle Dateien", + "File could not be found" : "Datei konnte nicht gefunden werden", "Home" : "Home", "Close" : "Schließen", "Favorites" : "Favoriten", diff --git a/apps/files/l10n/de_DE.js b/apps/files/l10n/de_DE.js index 9a349357a9..6a2f0ab1f5 100644 --- a/apps/files/l10n/de_DE.js +++ b/apps/files/l10n/de_DE.js @@ -21,6 +21,7 @@ OC.L10N.register( "Invalid directory." : "Ungültiges Verzeichnis.", "Files" : "Dateien", "All files" : "Alle Dateien", + "File could not be found" : "Datei konnte nicht gefunden werden", "Home" : "Zuhause", "Close" : "Schließen", "Favorites" : "Favoriten", diff --git a/apps/files/l10n/de_DE.json b/apps/files/l10n/de_DE.json index 9df557e8fa..047299f951 100644 --- a/apps/files/l10n/de_DE.json +++ b/apps/files/l10n/de_DE.json @@ -19,6 +19,7 @@ "Invalid directory." : "Ungültiges Verzeichnis.", "Files" : "Dateien", "All files" : "Alle Dateien", + "File could not be found" : "Datei konnte nicht gefunden werden", "Home" : "Zuhause", "Close" : "Schließen", "Favorites" : "Favoriten", diff --git a/apps/files/l10n/en_GB.js b/apps/files/l10n/en_GB.js index ec8b821c3e..82ed2364dd 100644 --- a/apps/files/l10n/en_GB.js +++ b/apps/files/l10n/en_GB.js @@ -21,6 +21,7 @@ OC.L10N.register( "Invalid directory." : "Invalid directory.", "Files" : "Files", "All files" : "All files", + "File could not be found" : "File could not be found", "Home" : "Home", "Close" : "Close", "Favorites" : "Favourites", diff --git a/apps/files/l10n/en_GB.json b/apps/files/l10n/en_GB.json index 6dae8ba41b..98b3d2000c 100644 --- a/apps/files/l10n/en_GB.json +++ b/apps/files/l10n/en_GB.json @@ -19,6 +19,7 @@ "Invalid directory." : "Invalid directory.", "Files" : "Files", "All files" : "All files", + "File could not be found" : "File could not be found", "Home" : "Home", "Close" : "Close", "Favorites" : "Favourites", diff --git a/apps/files/l10n/fi_FI.js b/apps/files/l10n/fi_FI.js index 325955a549..8bf1c47612 100644 --- a/apps/files/l10n/fi_FI.js +++ b/apps/files/l10n/fi_FI.js @@ -21,6 +21,7 @@ OC.L10N.register( "Invalid directory." : "Virheellinen kansio.", "Files" : "Tiedostot", "All files" : "Kaikki tiedostot", + "File could not be found" : "TIedostoa ei löytynyt", "Home" : "Koti", "Close" : "Sulje", "Favorites" : "Suosikit", diff --git a/apps/files/l10n/fi_FI.json b/apps/files/l10n/fi_FI.json index 966ef7074e..dc8d168ffb 100644 --- a/apps/files/l10n/fi_FI.json +++ b/apps/files/l10n/fi_FI.json @@ -19,6 +19,7 @@ "Invalid directory." : "Virheellinen kansio.", "Files" : "Tiedostot", "All files" : "Kaikki tiedostot", + "File could not be found" : "TIedostoa ei löytynyt", "Home" : "Koti", "Close" : "Sulje", "Favorites" : "Suosikit", diff --git a/apps/files/l10n/it.js b/apps/files/l10n/it.js index 7d85f952ed..b560e6623c 100644 --- a/apps/files/l10n/it.js +++ b/apps/files/l10n/it.js @@ -21,6 +21,7 @@ OC.L10N.register( "Invalid directory." : "Cartella non valida.", "Files" : "File", "All files" : "Tutti i file", + "File could not be found" : "Il file non può essere trovato", "Home" : "Home", "Close" : "Chiudi", "Favorites" : "Preferiti", diff --git a/apps/files/l10n/it.json b/apps/files/l10n/it.json index ec7003f738..253fc5198a 100644 --- a/apps/files/l10n/it.json +++ b/apps/files/l10n/it.json @@ -19,6 +19,7 @@ "Invalid directory." : "Cartella non valida.", "Files" : "File", "All files" : "Tutti i file", + "File could not be found" : "Il file non può essere trovato", "Home" : "Home", "Close" : "Chiudi", "Favorites" : "Preferiti", diff --git a/apps/files/l10n/pl.js b/apps/files/l10n/pl.js index cb243f6bf4..a366c6a967 100644 --- a/apps/files/l10n/pl.js +++ b/apps/files/l10n/pl.js @@ -32,6 +32,8 @@ OC.L10N.register( "Could not get result from server." : "Nie można uzyskać wyniku z serwera.", "Uploading..." : "Wgrywanie....", "..." : "...", + "{seconds} second{plural_s} left" : "Pozostało sekund: {seconds}", + "{seconds}s" : "{seconds} s", "File upload is in progress. Leaving the page now will cancel the upload." : "Wysyłanie pliku jest w toku. Jeśli opuścisz tę stronę, wysyłanie zostanie przerwane.", "Actions" : "Akcje", "Download" : "Pobierz", diff --git a/apps/files/l10n/pl.json b/apps/files/l10n/pl.json index 60323d0e75..05b65b2f84 100644 --- a/apps/files/l10n/pl.json +++ b/apps/files/l10n/pl.json @@ -30,6 +30,8 @@ "Could not get result from server." : "Nie można uzyskać wyniku z serwera.", "Uploading..." : "Wgrywanie....", "..." : "...", + "{seconds} second{plural_s} left" : "Pozostało sekund: {seconds}", + "{seconds}s" : "{seconds} s", "File upload is in progress. Leaving the page now will cancel the upload." : "Wysyłanie pliku jest w toku. Jeśli opuścisz tę stronę, wysyłanie zostanie przerwane.", "Actions" : "Akcje", "Download" : "Pobierz", diff --git a/apps/files/l10n/pt_BR.js b/apps/files/l10n/pt_BR.js index d8a7097161..7bc2a94deb 100644 --- a/apps/files/l10n/pt_BR.js +++ b/apps/files/l10n/pt_BR.js @@ -21,6 +21,7 @@ OC.L10N.register( "Invalid directory." : "Diretório inválido.", "Files" : "Arquivos", "All files" : "Todos os arquivos", + "File could not be found" : "O arquivo não foi encontrado", "Home" : "Home", "Close" : "Fechar", "Favorites" : "Favoritos", diff --git a/apps/files/l10n/pt_BR.json b/apps/files/l10n/pt_BR.json index f5fa97a94a..f16c8c374a 100644 --- a/apps/files/l10n/pt_BR.json +++ b/apps/files/l10n/pt_BR.json @@ -19,6 +19,7 @@ "Invalid directory." : "Diretório inválido.", "Files" : "Arquivos", "All files" : "Todos os arquivos", + "File could not be found" : "O arquivo não foi encontrado", "Home" : "Home", "Close" : "Fechar", "Favorites" : "Favoritos", diff --git a/apps/files/l10n/sq.js b/apps/files/l10n/sq.js index c1485c154d..b104afdd72 100644 --- a/apps/files/l10n/sq.js +++ b/apps/files/l10n/sq.js @@ -21,6 +21,7 @@ OC.L10N.register( "Invalid directory." : "Drejtori e pavlefshme.", "Files" : "Kartela", "All files" : "Krejt kartelat", + "File could not be found" : "Kartela s’u gjet dot", "Home" : "Kreu", "Close" : "Mbylle", "Favorites" : "Të parapëlqyera", diff --git a/apps/files/l10n/sq.json b/apps/files/l10n/sq.json index 9b40e9f7c9..80e5f104fa 100644 --- a/apps/files/l10n/sq.json +++ b/apps/files/l10n/sq.json @@ -19,6 +19,7 @@ "Invalid directory." : "Drejtori e pavlefshme.", "Files" : "Kartela", "All files" : "Krejt kartelat", + "File could not be found" : "Kartela s’u gjet dot", "Home" : "Kreu", "Close" : "Mbylle", "Favorites" : "Të parapëlqyera", diff --git a/apps/files_external/l10n/pl.js b/apps/files_external/l10n/pl.js index 8c3e394627..00ae59b27c 100644 --- a/apps/files_external/l10n/pl.js +++ b/apps/files_external/l10n/pl.js @@ -16,6 +16,7 @@ OC.L10N.register( "Saved" : "Zapisano", "Username" : "Nazwa użytkownika", "Password" : "Hasło", + "Credentials required" : "Wymagane poświadczenia", "Save" : "Zapisz", "Storage with id \"%i\" not found" : "Id magazynu nie został znaleziony", "Invalid mount point" : "Nieprawidłowy punkt montowania", diff --git a/apps/files_external/l10n/pl.json b/apps/files_external/l10n/pl.json index 8e00fc3186..235de7e9d1 100644 --- a/apps/files_external/l10n/pl.json +++ b/apps/files_external/l10n/pl.json @@ -14,6 +14,7 @@ "Saved" : "Zapisano", "Username" : "Nazwa użytkownika", "Password" : "Hasło", + "Credentials required" : "Wymagane poświadczenia", "Save" : "Zapisz", "Storage with id \"%i\" not found" : "Id magazynu nie został znaleziony", "Invalid mount point" : "Nieprawidłowy punkt montowania", diff --git a/apps/files_sharing/l10n/pl.js b/apps/files_sharing/l10n/pl.js index ebdfe2aa73..e1bec66953 100644 --- a/apps/files_sharing/l10n/pl.js +++ b/apps/files_sharing/l10n/pl.js @@ -25,6 +25,9 @@ OC.L10N.register( "Invalid ownCloud url" : "Błędny adres URL", "Shared by" : "Udostępniane przez", "Sharing" : "Udostępnianie", + "Public link sharing is disabled by the administrator" : "Udostępnianie linków publicznych zostało zablokowane przez twojego administratora", + "Public upload disabled by the administrator" : "Publiczne wczytywanie zostało zablokowane przez twojego administratora", + "Public upload is only possible for publicly shared folders" : "Publiczne wczytywanie jest możliwe wyłącznie do katalogów publicznych", "A file or folder has been shared" : "Plik lub folder stał się współdzielony", "You shared %1$s with %2$s" : "Współdzielisz %1$s z %2$s", "You shared %1$s with group %2$s" : "Współdzielisz %1$s z grupą %2$s", diff --git a/apps/files_sharing/l10n/pl.json b/apps/files_sharing/l10n/pl.json index f5de5dd368..6190871b16 100644 --- a/apps/files_sharing/l10n/pl.json +++ b/apps/files_sharing/l10n/pl.json @@ -23,6 +23,9 @@ "Invalid ownCloud url" : "Błędny adres URL", "Shared by" : "Udostępniane przez", "Sharing" : "Udostępnianie", + "Public link sharing is disabled by the administrator" : "Udostępnianie linków publicznych zostało zablokowane przez twojego administratora", + "Public upload disabled by the administrator" : "Publiczne wczytywanie zostało zablokowane przez twojego administratora", + "Public upload is only possible for publicly shared folders" : "Publiczne wczytywanie jest możliwe wyłącznie do katalogów publicznych", "A file or folder has been shared" : "Plik lub folder stał się współdzielony", "You shared %1$s with %2$s" : "Współdzielisz %1$s z %2$s", "You shared %1$s with group %2$s" : "Współdzielisz %1$s z grupą %2$s", diff --git a/apps/files_sharing/l10n/ro.js b/apps/files_sharing/l10n/ro.js index 2481f77bcc..5cddb6e8cd 100644 --- a/apps/files_sharing/l10n/ro.js +++ b/apps/files_sharing/l10n/ro.js @@ -12,12 +12,28 @@ OC.L10N.register( "Shared with others" : "Partajat cu alții", "Shared by link" : "Partajat prin link", "Nothing shared with you yet" : "Nimic nu e partajat cu tine încă", + "Files and folders others share with you will show up here" : "Fișierele și directoarele partajate cu tine vor apărea aici", "Nothing shared yet" : "Nimic partajat încă", + "Files and folders you share will show up here" : "Fișierele și directoarele pe care le partajezi vor apărea aici", + "No shared links" : "Nicio legătură partajată", + "Files and folders you share by link will show up here" : "Fișierele și directoarele pe care le partajezi prin legături vor apărea aici", + "Remote share" : "Element partajat la distanță", + "Remote share password" : "Parolă element partajat la distanță", "Cancel" : "Anulare", + "Add remote share" : "Adaugă element partajat la distanță", + "You can upload into this folder" : "Poți încărca în acest director", "No ownCloud installation (7 or higher) found at {remote}" : "Nu s-a găsit nicio instanță ownCloud (versiunea 7 sau mai mare) la {remote}", "Invalid ownCloud url" : "URL ownCloud invalid", "Shared by" : "impartite in ", "Sharing" : "Partajare", + "Share API is disabled" : "API-ul de partajare este dezactivat", + "Wrong share ID, share doesn't exist" : "ID greșit al elementului partajat, acesta nu există", + "Could not delete share" : "Nu s-a putut șterge elementul partajat", + "Please specify a file or folder path" : "Specifică un fișier sau o cale către un director", + "Wrong path, file/folder doesn't exist" : "Cale greșită, fișierul/directorul nu există", + "Please specify a valid user" : "Specifică un utilizator valid", + "Please specify a valid group" : "Specifică un grup valid", + "Invalid date, date format must be YYYY-MM-DD" : "Dată invalidă, formatul trebuie să fie AAAA-LL-ZZ", "Not a directory" : "Nu este un director", "Could not lock path" : "Calea nu a putut fi blocată", "Cannot increase permissions" : "Nu se pot extinde permisiunile", @@ -26,6 +42,9 @@ OC.L10N.register( "You shared %1$s with group %2$s" : "Ai partajat %1$s cu grupul %2$s", "You shared %1$s via link" : "Ai partajat %1$s prin legătură", "%2$s shared %1$s with you" : "%2$s a partajat %1$s cu tine", + "Shared with %2$s" : "Partajat cu %2$s", + "Shared with %3$s by %2$s" : "Partajat de %2$s cu %3$s", + "Shared with group %2$s" : "Partajat cu grupul %2$s", "Shares" : "Partajări", "This share is password-protected" : "Această partajare este protejată cu parolă", "The password is wrong. Try again." : "Parola este incorectă. Încercaţi din nou.", @@ -38,6 +57,7 @@ OC.L10N.register( "sharing is disabled" : "Partajare este oprită", "Add to your ownCloud" : "Adaugă propriul tău ownCloud", "Download" : "Descarcă", - "Download %s" : "Descarcă %s" + "Download %s" : "Descarcă %s", + "Direct link" : "Legătură directă" }, "nplurals=3; plural=(n==1?0:(((n%100>19)||((n%100==0)&&(n!=0)))?2:1));"); diff --git a/apps/files_sharing/l10n/ro.json b/apps/files_sharing/l10n/ro.json index e3f9450bcb..6d0d87037d 100644 --- a/apps/files_sharing/l10n/ro.json +++ b/apps/files_sharing/l10n/ro.json @@ -10,12 +10,28 @@ "Shared with others" : "Partajat cu alții", "Shared by link" : "Partajat prin link", "Nothing shared with you yet" : "Nimic nu e partajat cu tine încă", + "Files and folders others share with you will show up here" : "Fișierele și directoarele partajate cu tine vor apărea aici", "Nothing shared yet" : "Nimic partajat încă", + "Files and folders you share will show up here" : "Fișierele și directoarele pe care le partajezi vor apărea aici", + "No shared links" : "Nicio legătură partajată", + "Files and folders you share by link will show up here" : "Fișierele și directoarele pe care le partajezi prin legături vor apărea aici", + "Remote share" : "Element partajat la distanță", + "Remote share password" : "Parolă element partajat la distanță", "Cancel" : "Anulare", + "Add remote share" : "Adaugă element partajat la distanță", + "You can upload into this folder" : "Poți încărca în acest director", "No ownCloud installation (7 or higher) found at {remote}" : "Nu s-a găsit nicio instanță ownCloud (versiunea 7 sau mai mare) la {remote}", "Invalid ownCloud url" : "URL ownCloud invalid", "Shared by" : "impartite in ", "Sharing" : "Partajare", + "Share API is disabled" : "API-ul de partajare este dezactivat", + "Wrong share ID, share doesn't exist" : "ID greșit al elementului partajat, acesta nu există", + "Could not delete share" : "Nu s-a putut șterge elementul partajat", + "Please specify a file or folder path" : "Specifică un fișier sau o cale către un director", + "Wrong path, file/folder doesn't exist" : "Cale greșită, fișierul/directorul nu există", + "Please specify a valid user" : "Specifică un utilizator valid", + "Please specify a valid group" : "Specifică un grup valid", + "Invalid date, date format must be YYYY-MM-DD" : "Dată invalidă, formatul trebuie să fie AAAA-LL-ZZ", "Not a directory" : "Nu este un director", "Could not lock path" : "Calea nu a putut fi blocată", "Cannot increase permissions" : "Nu se pot extinde permisiunile", @@ -24,6 +40,9 @@ "You shared %1$s with group %2$s" : "Ai partajat %1$s cu grupul %2$s", "You shared %1$s via link" : "Ai partajat %1$s prin legătură", "%2$s shared %1$s with you" : "%2$s a partajat %1$s cu tine", + "Shared with %2$s" : "Partajat cu %2$s", + "Shared with %3$s by %2$s" : "Partajat de %2$s cu %3$s", + "Shared with group %2$s" : "Partajat cu grupul %2$s", "Shares" : "Partajări", "This share is password-protected" : "Această partajare este protejată cu parolă", "The password is wrong. Try again." : "Parola este incorectă. Încercaţi din nou.", @@ -36,6 +55,7 @@ "sharing is disabled" : "Partajare este oprită", "Add to your ownCloud" : "Adaugă propriul tău ownCloud", "Download" : "Descarcă", - "Download %s" : "Descarcă %s" + "Download %s" : "Descarcă %s", + "Direct link" : "Legătură directă" },"pluralForm" :"nplurals=3; plural=(n==1?0:(((n%100>19)||((n%100==0)&&(n!=0)))?2:1));" } \ No newline at end of file diff --git a/apps/systemtags/l10n/hu_HU.js b/apps/systemtags/l10n/hu_HU.js index 13d1705ff1..783b90ece8 100644 --- a/apps/systemtags/l10n/hu_HU.js +++ b/apps/systemtags/l10n/hu_HU.js @@ -21,6 +21,7 @@ OC.L10N.register( "%1$s assigned system tag %3$s to %2$s" : "%1$s hozzárendelte ezt a rendszer címkét: %3$s neki: %2$s", "You unassigned system tag %3$s from %2$s" : "%3$s rendszer címke hozzárendelést elvette tőle: %2$s", "%1$s unassigned system tag %3$s from %2$s" : "%1$s elvette ezt a rendszer címkét %3$s tőle: %2$s", + "%s (restricted)" : "%s (korlátozott)", "%s (invisible)" : "%s (láthatatlan)", "No files in here" : "Itt nincsenek fájlok", "No entries found in this folder" : "Nincsenek bejegyzések ebben a könyvtárban", diff --git a/apps/systemtags/l10n/hu_HU.json b/apps/systemtags/l10n/hu_HU.json index 6408b3a531..f89da5d3df 100644 --- a/apps/systemtags/l10n/hu_HU.json +++ b/apps/systemtags/l10n/hu_HU.json @@ -19,6 +19,7 @@ "%1$s assigned system tag %3$s to %2$s" : "%1$s hozzárendelte ezt a rendszer címkét: %3$s neki: %2$s", "You unassigned system tag %3$s from %2$s" : "%3$s rendszer címke hozzárendelést elvette tőle: %2$s", "%1$s unassigned system tag %3$s from %2$s" : "%1$s elvette ezt a rendszer címkét %3$s tőle: %2$s", + "%s (restricted)" : "%s (korlátozott)", "%s (invisible)" : "%s (láthatatlan)", "No files in here" : "Itt nincsenek fájlok", "No entries found in this folder" : "Nincsenek bejegyzések ebben a könyvtárban", diff --git a/apps/updatenotification/l10n/hu_HU.js b/apps/updatenotification/l10n/hu_HU.js index 1df38a6746..cce4549f01 100644 --- a/apps/updatenotification/l10n/hu_HU.js +++ b/apps/updatenotification/l10n/hu_HU.js @@ -1,8 +1,11 @@ OC.L10N.register( "updatenotification", { + "Update notifications" : "Frissítési értesítés", "{version} is available. Get more information on how to update." : "{version} rendelkezésre áll. További információ a frissítéshez.", "Updated channel" : "Frissített csatorna", + "ownCloud core" : "ownCloud mag", + "Update for %1$s to version %2$s is available." : "%1$s frissíthető %2$s verzióra.", "Updater" : "Frissítéskezelő", "A new version is available: %s" : "Új verzió érhető el: %s", "Open updater" : "Frissítő megnyitása", diff --git a/apps/updatenotification/l10n/hu_HU.json b/apps/updatenotification/l10n/hu_HU.json index eb953669ab..952b499d52 100644 --- a/apps/updatenotification/l10n/hu_HU.json +++ b/apps/updatenotification/l10n/hu_HU.json @@ -1,6 +1,9 @@ { "translations": { + "Update notifications" : "Frissítési értesítés", "{version} is available. Get more information on how to update." : "{version} rendelkezésre áll. További információ a frissítéshez.", "Updated channel" : "Frissített csatorna", + "ownCloud core" : "ownCloud mag", + "Update for %1$s to version %2$s is available." : "%1$s frissíthető %2$s verzióra.", "Updater" : "Frissítéskezelő", "A new version is available: %s" : "Új verzió érhető el: %s", "Open updater" : "Frissítő megnyitása", diff --git a/apps/user_ldap/l10n/pl.js b/apps/user_ldap/l10n/pl.js index de8f1f49ad..a75b38fc12 100644 --- a/apps/user_ldap/l10n/pl.js +++ b/apps/user_ldap/l10n/pl.js @@ -31,6 +31,7 @@ OC.L10N.register( "Confirm Deletion" : "Potwierdź usunięcie", "Mappings cleared successfully!" : "Mapowanie wyczyszczone!", "Error while clearing the mappings." : "Błąd podczas czyszczenia mapowania.", + "Mode switch" : "Przełącznik trybów", "Select attributes" : "Wybierz atrybuty", "_%s group found_::_%s groups found_" : ["%s znaleziona grupa","%s znalezionych grup","%s znalezionych grup"], "_%s user found_::_%s users found_" : ["%s znaleziony użytkownik","%s znalezionych użytkowników","%s znalezionych użytkowników"], diff --git a/apps/user_ldap/l10n/pl.json b/apps/user_ldap/l10n/pl.json index 5a853c3abe..472148fa3b 100644 --- a/apps/user_ldap/l10n/pl.json +++ b/apps/user_ldap/l10n/pl.json @@ -29,6 +29,7 @@ "Confirm Deletion" : "Potwierdź usunięcie", "Mappings cleared successfully!" : "Mapowanie wyczyszczone!", "Error while clearing the mappings." : "Błąd podczas czyszczenia mapowania.", + "Mode switch" : "Przełącznik trybów", "Select attributes" : "Wybierz atrybuty", "_%s group found_::_%s groups found_" : ["%s znaleziona grupa","%s znalezionych grup","%s znalezionych grup"], "_%s user found_::_%s users found_" : ["%s znaleziony użytkownik","%s znalezionych użytkowników","%s znalezionych użytkowników"], diff --git a/core/l10n/de.js b/core/l10n/de.js index 177376c380..52e1639eff 100644 --- a/core/l10n/de.js +++ b/core/l10n/de.js @@ -298,6 +298,7 @@ OC.L10N.register( "Thank you for your patience." : "Vielen Dank für Deine Geduld.", "Two-step verification" : "Bestätigung in zwei Schritten", "Enhanced security has been enabled for your account. Please authenticate using a second factor." : "Die erweiterte Sicherheit wurde für dich Konto aktiviert. Bitte authentifiziere dich mit einem zweiten Faktor. ", + "Cancel login" : "Anmelden abbrechen", "Please authenticate using the selected factor." : "Bitte authentifiziere dich mit dem ausgewählten zweiten Faktor. ", "An error occured while verifying the token" : "Es ist ein Fehler bei der Verifizierung des Tokens aufgetreten", "You are accessing the server from an untrusted domain." : "Du greifst von einer nicht vertrauenswürdigen Domain auf den Server zu.", diff --git a/core/l10n/de.json b/core/l10n/de.json index a66abe7016..d713c50724 100644 --- a/core/l10n/de.json +++ b/core/l10n/de.json @@ -296,6 +296,7 @@ "Thank you for your patience." : "Vielen Dank für Deine Geduld.", "Two-step verification" : "Bestätigung in zwei Schritten", "Enhanced security has been enabled for your account. Please authenticate using a second factor." : "Die erweiterte Sicherheit wurde für dich Konto aktiviert. Bitte authentifiziere dich mit einem zweiten Faktor. ", + "Cancel login" : "Anmelden abbrechen", "Please authenticate using the selected factor." : "Bitte authentifiziere dich mit dem ausgewählten zweiten Faktor. ", "An error occured while verifying the token" : "Es ist ein Fehler bei der Verifizierung des Tokens aufgetreten", "You are accessing the server from an untrusted domain." : "Du greifst von einer nicht vertrauenswürdigen Domain auf den Server zu.", diff --git a/core/l10n/de_DE.js b/core/l10n/de_DE.js index f10a73798d..9e379c2c02 100644 --- a/core/l10n/de_DE.js +++ b/core/l10n/de_DE.js @@ -298,6 +298,7 @@ OC.L10N.register( "Thank you for your patience." : "Vielen Dank für Ihre Geduld.", "Two-step verification" : "Bestätigung in zwei Schritten", "Enhanced security has been enabled for your account. Please authenticate using a second factor." : "Die erweiterte Sicherheit wurde für Ihr Konto aktiviert. Bitte authentifizieren Sie sich mit einem zweiten Faktor. ", + "Cancel login" : "Anmelden abbrechen", "Please authenticate using the selected factor." : "Bitte authentifizieren Sie sich mit dem ausgewählten zweiten Faktor. ", "An error occured while verifying the token" : "Es ist ein Fehler bei der Verifizierung des Tokens aufgetreten", "You are accessing the server from an untrusted domain." : "Sie greifen von einer nicht vertrauenswürdigen Domain auf den Server zu.", diff --git a/core/l10n/de_DE.json b/core/l10n/de_DE.json index 3b6267dca6..bcdf1f5279 100644 --- a/core/l10n/de_DE.json +++ b/core/l10n/de_DE.json @@ -296,6 +296,7 @@ "Thank you for your patience." : "Vielen Dank für Ihre Geduld.", "Two-step verification" : "Bestätigung in zwei Schritten", "Enhanced security has been enabled for your account. Please authenticate using a second factor." : "Die erweiterte Sicherheit wurde für Ihr Konto aktiviert. Bitte authentifizieren Sie sich mit einem zweiten Faktor. ", + "Cancel login" : "Anmelden abbrechen", "Please authenticate using the selected factor." : "Bitte authentifizieren Sie sich mit dem ausgewählten zweiten Faktor. ", "An error occured while verifying the token" : "Es ist ein Fehler bei der Verifizierung des Tokens aufgetreten", "You are accessing the server from an untrusted domain." : "Sie greifen von einer nicht vertrauenswürdigen Domain auf den Server zu.", diff --git a/core/l10n/en_GB.js b/core/l10n/en_GB.js index 6f5859aae3..76045cf033 100644 --- a/core/l10n/en_GB.js +++ b/core/l10n/en_GB.js @@ -298,6 +298,7 @@ OC.L10N.register( "Thank you for your patience." : "Thank you for your patience.", "Two-step verification" : "Two-step verification", "Enhanced security has been enabled for your account. Please authenticate using a second factor." : "Enhanced security has been enabled for your account. Please authenticate using a second factor.", + "Cancel login" : "Cancel login", "Please authenticate using the selected factor." : "Please authenticate using the selected factor.", "An error occured while verifying the token" : "An error occured while verifying the token", "You are accessing the server from an untrusted domain." : "You are accessing the server from an untrusted domain.", diff --git a/core/l10n/en_GB.json b/core/l10n/en_GB.json index 7aedc2a730..d71ff3f8ad 100644 --- a/core/l10n/en_GB.json +++ b/core/l10n/en_GB.json @@ -296,6 +296,7 @@ "Thank you for your patience." : "Thank you for your patience.", "Two-step verification" : "Two-step verification", "Enhanced security has been enabled for your account. Please authenticate using a second factor." : "Enhanced security has been enabled for your account. Please authenticate using a second factor.", + "Cancel login" : "Cancel login", "Please authenticate using the selected factor." : "Please authenticate using the selected factor.", "An error occured while verifying the token" : "An error occured while verifying the token", "You are accessing the server from an untrusted domain." : "You are accessing the server from an untrusted domain.", diff --git a/core/l10n/fi_FI.js b/core/l10n/fi_FI.js index d15457022a..d491cf213f 100644 --- a/core/l10n/fi_FI.js +++ b/core/l10n/fi_FI.js @@ -187,6 +187,7 @@ OC.L10N.register( "Warning" : "Varoitus", "Error while sending notification" : "Virhe ilmoitusta lähettäessä", "Non-existing tag #{tag}" : "Ei olemassa oleva tunniste #{tag}", + "restricted" : "rajoitettu", "invisible" : "näkymätön", "({scope})" : "({scope})", "Delete" : "Poista", @@ -289,6 +290,7 @@ OC.L10N.register( "Thank you for your patience." : "Kiitos kärsivällisyydestäsi.", "Two-step verification" : "Kaksivaiheinen vahvistus", "Enhanced security has been enabled for your account. Please authenticate using a second factor." : "Tililläsi on käytössä lisäturvatoimia. Tunnistaudu käyttäen kaksivaiheista vahvistusta.", + "Cancel login" : "Peru kirjautuminen", "Please authenticate using the selected factor." : "Tunnistaudu käyttäen valittua vahvistusta.", "You are accessing the server from an untrusted domain." : "Olet yhteydessä palvelimeen epäluotettavasta verkko-osoitteesta.", "Please contact your administrator. If you are an administrator of this instance, configure the \"trusted_domains\" setting in config/config.php. An example configuration is provided in config/config.sample.php." : "Ota yhteys ylläpitoon. Jos olet tämän asennuksen ylläpitäjä, määritä \"trusted_domains\"-asetus config/config.php-tiedostossa. Esimerkkimääritys on tarjolla tiedostossa config/config.sample.php.", diff --git a/core/l10n/fi_FI.json b/core/l10n/fi_FI.json index 14565b0b46..e3f7fb28a8 100644 --- a/core/l10n/fi_FI.json +++ b/core/l10n/fi_FI.json @@ -185,6 +185,7 @@ "Warning" : "Varoitus", "Error while sending notification" : "Virhe ilmoitusta lähettäessä", "Non-existing tag #{tag}" : "Ei olemassa oleva tunniste #{tag}", + "restricted" : "rajoitettu", "invisible" : "näkymätön", "({scope})" : "({scope})", "Delete" : "Poista", @@ -287,6 +288,7 @@ "Thank you for your patience." : "Kiitos kärsivällisyydestäsi.", "Two-step verification" : "Kaksivaiheinen vahvistus", "Enhanced security has been enabled for your account. Please authenticate using a second factor." : "Tililläsi on käytössä lisäturvatoimia. Tunnistaudu käyttäen kaksivaiheista vahvistusta.", + "Cancel login" : "Peru kirjautuminen", "Please authenticate using the selected factor." : "Tunnistaudu käyttäen valittua vahvistusta.", "You are accessing the server from an untrusted domain." : "Olet yhteydessä palvelimeen epäluotettavasta verkko-osoitteesta.", "Please contact your administrator. If you are an administrator of this instance, configure the \"trusted_domains\" setting in config/config.php. An example configuration is provided in config/config.sample.php." : "Ota yhteys ylläpitoon. Jos olet tämän asennuksen ylläpitäjä, määritä \"trusted_domains\"-asetus config/config.php-tiedostossa. Esimerkkimääritys on tarjolla tiedostossa config/config.sample.php.", diff --git a/core/l10n/it.js b/core/l10n/it.js index 7daff3e0da..6769455e66 100644 --- a/core/l10n/it.js +++ b/core/l10n/it.js @@ -298,6 +298,7 @@ OC.L10N.register( "Thank you for your patience." : "Grazie per la pazienza.", "Two-step verification" : "Verifica in due fasi", "Enhanced security has been enabled for your account. Please authenticate using a second factor." : "La sicurezza migliorata è stata abilitata per il tuo account. Autenticati utilizzando un secondo fattore.", + "Cancel login" : "Annulla l'accesso", "Please authenticate using the selected factor." : "Autentica utilizzando il fattore selezionato.", "An error occured while verifying the token" : "Si è verificato un errore durante la verifica del token", "You are accessing the server from an untrusted domain." : "Stai accedendo al server da un dominio non attendibile.", diff --git a/core/l10n/it.json b/core/l10n/it.json index 9d403e4f72..ac6d16d90b 100644 --- a/core/l10n/it.json +++ b/core/l10n/it.json @@ -296,6 +296,7 @@ "Thank you for your patience." : "Grazie per la pazienza.", "Two-step verification" : "Verifica in due fasi", "Enhanced security has been enabled for your account. Please authenticate using a second factor." : "La sicurezza migliorata è stata abilitata per il tuo account. Autenticati utilizzando un secondo fattore.", + "Cancel login" : "Annulla l'accesso", "Please authenticate using the selected factor." : "Autentica utilizzando il fattore selezionato.", "An error occured while verifying the token" : "Si è verificato un errore durante la verifica del token", "You are accessing the server from an untrusted domain." : "Stai accedendo al server da un dominio non attendibile.", diff --git a/core/l10n/pt_BR.js b/core/l10n/pt_BR.js index a0ed6ee2ab..e793cab783 100644 --- a/core/l10n/pt_BR.js +++ b/core/l10n/pt_BR.js @@ -298,6 +298,7 @@ OC.L10N.register( "Thank you for your patience." : "Obrigado pela sua paciência.", "Two-step verification" : "Verificação em dois passos", "Enhanced security has been enabled for your account. Please authenticate using a second factor." : "Segurança reforçada foi ativada para sua conta. Por favor autenticar usando um segundo fator.", + "Cancel login" : "Cancelar o login", "Please authenticate using the selected factor." : "Por favor autenticar usando o fator selecionado.", "An error occured while verifying the token" : "Ocorreu um erro ao verificar o token", "You are accessing the server from an untrusted domain." : "Você está acessando o servidor a partir de um domínio não confiável.", diff --git a/core/l10n/pt_BR.json b/core/l10n/pt_BR.json index 8642980d76..c6a944e177 100644 --- a/core/l10n/pt_BR.json +++ b/core/l10n/pt_BR.json @@ -296,6 +296,7 @@ "Thank you for your patience." : "Obrigado pela sua paciência.", "Two-step verification" : "Verificação em dois passos", "Enhanced security has been enabled for your account. Please authenticate using a second factor." : "Segurança reforçada foi ativada para sua conta. Por favor autenticar usando um segundo fator.", + "Cancel login" : "Cancelar o login", "Please authenticate using the selected factor." : "Por favor autenticar usando o fator selecionado.", "An error occured while verifying the token" : "Ocorreu um erro ao verificar o token", "You are accessing the server from an untrusted domain." : "Você está acessando o servidor a partir de um domínio não confiável.", diff --git a/core/l10n/ro.js b/core/l10n/ro.js index 7b4cf2c895..e73ec5cf79 100644 --- a/core/l10n/ro.js +++ b/core/l10n/ro.js @@ -8,23 +8,37 @@ OC.L10N.register( "Unknown filetype" : "Tip fișier necunoscut", "Invalid image" : "Imagine invalidă", "An error occurred. Please contact your admin." : "A apărut o eroare. Te rugăm să contactezi administratorul.", + "No temporary profile picture available, try again" : "Nu este disponibilă nicio imagine temporară a profilului, încearcă din nou", "%s password reset" : "%s resetare parola", "Couldn't send reset email. Please contact your administrator." : "Expedierea email-ului de resetare a eşuat. Vă rugăm să contactaţi administratorul dvs.", "Error loading tags" : "Eroare la încărcarea etichetelor", "Tag already exists" : "Eticheta deja există", + "Error deleting tag(s)" : "Eroare la ștergerea etichetei(-lor)", "Error tagging" : "Eroare la etichetare", "Error untagging" : "Eroare la scoaterea etichetei", + "Error favoriting" : "Eroare la adăugarea la favorite", + "Error unfavoriting" : "Eroare la scoaterea de la favorite", "Couldn't send mail to following users: %s " : "Nu s-a putut trimite mesajul către următorii utilizatori: %s", "Preparing update" : "Se pregătește actualizarea", + "[%d / %d]: %s" : "[%d / %d]: %s", "Repair warning: " : "Alerte reparare:", "Repair error: " : "Eroare de reparare:", + "[%d / %d]: Checking table %s" : "[%d / %d]: Se verifică tabela %s", "Turned on maintenance mode" : "Modul mentenanță a fost activat", "Turned off maintenance mode" : "Modul mentenanță a fost dezactivat", + "Maintenance mode is kept active" : "Modul de mentenanță este menținut activ", "Updating database schema" : "Se actualizează schema bazei de date", "Updated database" : "Bază de date actualizată", "Checking whether the database schema can be updated (this can take a long time depending on the database size)" : "Se verifică dacă schema bazei de date poate fi actualizată (această verificare poate lua mult timp în funcție de mărimea bazei de date)", + "Checking updates of apps" : "Se verifică actualizările aplicațiilor", "Updated \"%s\" to %s" : "\"%s\" a fost actualizat până la %s", + "Set log level to debug" : "Setează nivelul de logare la \"debug\"", + "Reset log level" : "Resetează nivelul de logare", + "Starting code integrity check" : "Începe verificarea integrității codului", + "Finished code integrity check" : "Verificarea integrității codului a fost finalizată", + "%s (3rdparty)" : "%s (terță parte)", "%s (incompatible)" : "%s (incompatibil)", + "Following apps have been disabled: %s" : "Următoarele aplicații au fost dezactivate: %s", "Already up to date" : "Deja actualizat", "Sunday" : "Duminică", "Monday" : "Luni", @@ -71,9 +85,11 @@ OC.L10N.register( "Oct." : "Oct.", "Nov." : "Noi.", "Dec." : "Dec.", + "There were problems with the code integrity check. More information…" : "Au apărut probleme la verificarea integrității codului. Mai multe informații…", "Settings" : "Setări", "Problem loading page, reloading in 5 seconds" : "A apărut o problemă la încărcarea paginii, se reîncearcă în 5 secunde", "Saving..." : "Se salvează...", + "Dismiss" : "Înlătură", "seconds ago" : "secunde în urmă", "I know what I'm doing" : "Eu știu ce fac", "Password can not be changed. Please contact your administrator." : "Parola nu poate fi modificată. Vă rugăm să contactați administratorul dvs.", @@ -135,24 +151,39 @@ OC.L10N.register( "access control" : "control acces", "Could not unshare" : "Nu s-a putut elimina partajarea", "Share details could not be loaded for this item." : "Nu s-au putut încărca detaliile de partajare pentru acest element.", + "An error occurred. Please try again" : "A apărut o eroare. Încearcă din nou", "Share" : "Partajează", "Share with people on other ownClouds using the syntax username@example.com/owncloud" : "Partajează cu persoane din alte instanțe ownCloud folosind sintaxa nume_utilizator@exemplu.com/owncloud", "Share with users…" : "Partajează cu utilizatori...", + "Share with users or groups…" : "Partajează cu utilizatori sau grupuri...", + "Error removing share" : "Eroare la înlăturarea elementului partajat", "Warning" : "Atenție", + "Error while sending notification" : "Eroare la trimiterea notificării", + "Non-existing tag #{tag}" : "Etichetă inexistentă #{tag}", + "restricted" : "restricționat", "invisible" : "invizibil", "Delete" : "Șterge", "Rename" : "Redenumește", + "Collaborative tags" : "Etichete colaborative", "The object type is not specified." : "Tipul obiectului nu este specificat.", "Enter new" : "Introducere nou", "Add" : "Adaugă", "Edit tags" : "Editează etichete", + "No tags selected for deletion." : "Nu au fost selectate etichete pentru ștergere.", "unknown text" : "text necunoscut", + "Hello world!" : "Hello world!", "sunny" : "însorit", + "Hello {name}" : "Salut {name}", "new" : "nou", + "The upgrade is in progress, leaving this page might interrupt the process in some environments." : "Actualizarea este în progres, părăsirea acestei pagini ar putea duce la întreruperea procesului în unele medii.", + "Updating to {version}" : "Actualizare la {version}", "An error occurred." : "A apărut o eroare.", "Please reload the page." : "Te rugăm să reîncarci pagina.", "The update was unsuccessful. Please report this issue to the ownCloud community." : "Actualizarea a eșuat! Raportați problema către comunitatea ownCloud.", + "The update was successful. There were warnings." : "Actualizarea nu a avut loc cu succes. Au existat avertismente.", "The update was successful. Redirecting you to ownCloud now." : "Actualizare reușită. Ești redirecționat către ownCloud.", + "Searching other places" : "Se caută în alte locuri", + "No search results in other folders" : "Nu există rezultate ale căutării în alte directoare", "Personal" : "Personal", "Users" : "Utilizatori", "Apps" : "Aplicații", @@ -160,8 +191,12 @@ OC.L10N.register( "Help" : "Ajutor", "Access forbidden" : "Acces restricționat", "File not found" : "Fișierul nu a fost găsit", + "The specified document has not been found on the server." : "Documentul specificat nu a fost găsit pe server.", + "You can click here to return to %s." : "Poți da click aici pentru a te întoarce la %s.", "The share will expire on %s." : "Partajarea va expira în data de %s.", "Cheers!" : "Noroc!", + "Internal Server Error" : "Eroare internă a serverului", + "The server encountered an internal error and was unable to complete your request." : "Serverul a întâmpinat o eroare și nu îți poate îndeplini cererea.", "Technical details" : "Detalii tehnice", "Type: %s" : "Tip: %s", "Code: %s" : "Cod: %s", @@ -190,16 +225,35 @@ OC.L10N.register( "See the documentation" : "Vezi documentația", "Log out" : "Ieșire", "Search" : "Căutare", + "Please contact your administrator." : "Contactează-ți administratorul.", "An internal error occurred." : "A apărut o eroare internă.", + "Please try again or contact your administrator." : "Încearcă din nou sau contactează-ți administratorul.", "Username or email" : "Nume de utilizator sau adresă email", "Log in" : "Autentificare", + "Wrong password. Reset it?" : "Parolă greșită. O resetezi?", + "Wrong password." : "Parolă greșită.", + "Stay logged in" : "Rămâi autentificat", "Alternative Logins" : "Conectări alternative", "Use the following link to reset your password: {link}" : "Folosește următorul link pentru a reseta parola: {link}", "New password" : "Noua parolă", "New Password" : "Noua parolă", "Reset password" : "Resetează parola", "Thank you for your patience." : "Îți mulțumim pentru răbdare.", + "Two-step verification" : "Verificare în doi pași", + "Cancel login" : "Anulează autentificarea", + "Please authenticate using the selected factor." : "Autentifică-te folosind factorul ales.", + "An error occured while verifying the token" : "A apărut o eroare la verificarea jetonului", "You are accessing the server from an untrusted domain." : "Accesezi serverul dintr-un domeniu care nu a fost configurat ca fiind de încredere.", - "Start update" : "Începe actualizarea" + "Add \"%s\" as trusted domain" : "Adaugă \"%s\" ca domeniu de încredere", + "App update required" : "E necesară o actualizare a aplicației", + "%s will be updated to version %s" : "%s va fi actualizat la versiunea %s", + "These apps will be updated:" : "Aceste aplicații vor fi actualizate:", + "These incompatible apps will be disabled:" : "Aceste aplicații incompatibile vor fi dezactivate:", + "The theme %s has been disabled." : "Tema %s a fost dezactivată.", + "Start update" : "Începe actualizarea", + "Detailed logs" : "Loguri detaliate", + "Update needed" : "E necesară actualizarea", + "Please use the command line updater because you have a big instance." : "Folosește actualizarea din linia de comandă deoarece ai o instanță mare.", + "This %s instance is currently in maintenance mode, which may take a while." : "Instanța %s este acum în modul de mentenanță, ceea ce ar putea dura o vreme." }, "nplurals=3; plural=(n==1?0:(((n%100>19)||((n%100==0)&&(n!=0)))?2:1));"); diff --git a/core/l10n/ro.json b/core/l10n/ro.json index cea43ae314..c154312dab 100644 --- a/core/l10n/ro.json +++ b/core/l10n/ro.json @@ -6,23 +6,37 @@ "Unknown filetype" : "Tip fișier necunoscut", "Invalid image" : "Imagine invalidă", "An error occurred. Please contact your admin." : "A apărut o eroare. Te rugăm să contactezi administratorul.", + "No temporary profile picture available, try again" : "Nu este disponibilă nicio imagine temporară a profilului, încearcă din nou", "%s password reset" : "%s resetare parola", "Couldn't send reset email. Please contact your administrator." : "Expedierea email-ului de resetare a eşuat. Vă rugăm să contactaţi administratorul dvs.", "Error loading tags" : "Eroare la încărcarea etichetelor", "Tag already exists" : "Eticheta deja există", + "Error deleting tag(s)" : "Eroare la ștergerea etichetei(-lor)", "Error tagging" : "Eroare la etichetare", "Error untagging" : "Eroare la scoaterea etichetei", + "Error favoriting" : "Eroare la adăugarea la favorite", + "Error unfavoriting" : "Eroare la scoaterea de la favorite", "Couldn't send mail to following users: %s " : "Nu s-a putut trimite mesajul către următorii utilizatori: %s", "Preparing update" : "Se pregătește actualizarea", + "[%d / %d]: %s" : "[%d / %d]: %s", "Repair warning: " : "Alerte reparare:", "Repair error: " : "Eroare de reparare:", + "[%d / %d]: Checking table %s" : "[%d / %d]: Se verifică tabela %s", "Turned on maintenance mode" : "Modul mentenanță a fost activat", "Turned off maintenance mode" : "Modul mentenanță a fost dezactivat", + "Maintenance mode is kept active" : "Modul de mentenanță este menținut activ", "Updating database schema" : "Se actualizează schema bazei de date", "Updated database" : "Bază de date actualizată", "Checking whether the database schema can be updated (this can take a long time depending on the database size)" : "Se verifică dacă schema bazei de date poate fi actualizată (această verificare poate lua mult timp în funcție de mărimea bazei de date)", + "Checking updates of apps" : "Se verifică actualizările aplicațiilor", "Updated \"%s\" to %s" : "\"%s\" a fost actualizat până la %s", + "Set log level to debug" : "Setează nivelul de logare la \"debug\"", + "Reset log level" : "Resetează nivelul de logare", + "Starting code integrity check" : "Începe verificarea integrității codului", + "Finished code integrity check" : "Verificarea integrității codului a fost finalizată", + "%s (3rdparty)" : "%s (terță parte)", "%s (incompatible)" : "%s (incompatibil)", + "Following apps have been disabled: %s" : "Următoarele aplicații au fost dezactivate: %s", "Already up to date" : "Deja actualizat", "Sunday" : "Duminică", "Monday" : "Luni", @@ -69,9 +83,11 @@ "Oct." : "Oct.", "Nov." : "Noi.", "Dec." : "Dec.", + "There were problems with the code integrity check. More information…" : "Au apărut probleme la verificarea integrității codului. Mai multe informații…", "Settings" : "Setări", "Problem loading page, reloading in 5 seconds" : "A apărut o problemă la încărcarea paginii, se reîncearcă în 5 secunde", "Saving..." : "Se salvează...", + "Dismiss" : "Înlătură", "seconds ago" : "secunde în urmă", "I know what I'm doing" : "Eu știu ce fac", "Password can not be changed. Please contact your administrator." : "Parola nu poate fi modificată. Vă rugăm să contactați administratorul dvs.", @@ -133,24 +149,39 @@ "access control" : "control acces", "Could not unshare" : "Nu s-a putut elimina partajarea", "Share details could not be loaded for this item." : "Nu s-au putut încărca detaliile de partajare pentru acest element.", + "An error occurred. Please try again" : "A apărut o eroare. Încearcă din nou", "Share" : "Partajează", "Share with people on other ownClouds using the syntax username@example.com/owncloud" : "Partajează cu persoane din alte instanțe ownCloud folosind sintaxa nume_utilizator@exemplu.com/owncloud", "Share with users…" : "Partajează cu utilizatori...", + "Share with users or groups…" : "Partajează cu utilizatori sau grupuri...", + "Error removing share" : "Eroare la înlăturarea elementului partajat", "Warning" : "Atenție", + "Error while sending notification" : "Eroare la trimiterea notificării", + "Non-existing tag #{tag}" : "Etichetă inexistentă #{tag}", + "restricted" : "restricționat", "invisible" : "invizibil", "Delete" : "Șterge", "Rename" : "Redenumește", + "Collaborative tags" : "Etichete colaborative", "The object type is not specified." : "Tipul obiectului nu este specificat.", "Enter new" : "Introducere nou", "Add" : "Adaugă", "Edit tags" : "Editează etichete", + "No tags selected for deletion." : "Nu au fost selectate etichete pentru ștergere.", "unknown text" : "text necunoscut", + "Hello world!" : "Hello world!", "sunny" : "însorit", + "Hello {name}" : "Salut {name}", "new" : "nou", + "The upgrade is in progress, leaving this page might interrupt the process in some environments." : "Actualizarea este în progres, părăsirea acestei pagini ar putea duce la întreruperea procesului în unele medii.", + "Updating to {version}" : "Actualizare la {version}", "An error occurred." : "A apărut o eroare.", "Please reload the page." : "Te rugăm să reîncarci pagina.", "The update was unsuccessful. Please report this issue to the ownCloud community." : "Actualizarea a eșuat! Raportați problema către comunitatea ownCloud.", + "The update was successful. There were warnings." : "Actualizarea nu a avut loc cu succes. Au existat avertismente.", "The update was successful. Redirecting you to ownCloud now." : "Actualizare reușită. Ești redirecționat către ownCloud.", + "Searching other places" : "Se caută în alte locuri", + "No search results in other folders" : "Nu există rezultate ale căutării în alte directoare", "Personal" : "Personal", "Users" : "Utilizatori", "Apps" : "Aplicații", @@ -158,8 +189,12 @@ "Help" : "Ajutor", "Access forbidden" : "Acces restricționat", "File not found" : "Fișierul nu a fost găsit", + "The specified document has not been found on the server." : "Documentul specificat nu a fost găsit pe server.", + "You can click here to return to %s." : "Poți da click aici pentru a te întoarce la %s.", "The share will expire on %s." : "Partajarea va expira în data de %s.", "Cheers!" : "Noroc!", + "Internal Server Error" : "Eroare internă a serverului", + "The server encountered an internal error and was unable to complete your request." : "Serverul a întâmpinat o eroare și nu îți poate îndeplini cererea.", "Technical details" : "Detalii tehnice", "Type: %s" : "Tip: %s", "Code: %s" : "Cod: %s", @@ -188,16 +223,35 @@ "See the documentation" : "Vezi documentația", "Log out" : "Ieșire", "Search" : "Căutare", + "Please contact your administrator." : "Contactează-ți administratorul.", "An internal error occurred." : "A apărut o eroare internă.", + "Please try again or contact your administrator." : "Încearcă din nou sau contactează-ți administratorul.", "Username or email" : "Nume de utilizator sau adresă email", "Log in" : "Autentificare", + "Wrong password. Reset it?" : "Parolă greșită. O resetezi?", + "Wrong password." : "Parolă greșită.", + "Stay logged in" : "Rămâi autentificat", "Alternative Logins" : "Conectări alternative", "Use the following link to reset your password: {link}" : "Folosește următorul link pentru a reseta parola: {link}", "New password" : "Noua parolă", "New Password" : "Noua parolă", "Reset password" : "Resetează parola", "Thank you for your patience." : "Îți mulțumim pentru răbdare.", + "Two-step verification" : "Verificare în doi pași", + "Cancel login" : "Anulează autentificarea", + "Please authenticate using the selected factor." : "Autentifică-te folosind factorul ales.", + "An error occured while verifying the token" : "A apărut o eroare la verificarea jetonului", "You are accessing the server from an untrusted domain." : "Accesezi serverul dintr-un domeniu care nu a fost configurat ca fiind de încredere.", - "Start update" : "Începe actualizarea" + "Add \"%s\" as trusted domain" : "Adaugă \"%s\" ca domeniu de încredere", + "App update required" : "E necesară o actualizare a aplicației", + "%s will be updated to version %s" : "%s va fi actualizat la versiunea %s", + "These apps will be updated:" : "Aceste aplicații vor fi actualizate:", + "These incompatible apps will be disabled:" : "Aceste aplicații incompatibile vor fi dezactivate:", + "The theme %s has been disabled." : "Tema %s a fost dezactivată.", + "Start update" : "Începe actualizarea", + "Detailed logs" : "Loguri detaliate", + "Update needed" : "E necesară actualizarea", + "Please use the command line updater because you have a big instance." : "Folosește actualizarea din linia de comandă deoarece ai o instanță mare.", + "This %s instance is currently in maintenance mode, which may take a while." : "Instanța %s este acum în modul de mentenanță, ceea ce ar putea dura o vreme." },"pluralForm" :"nplurals=3; plural=(n==1?0:(((n%100>19)||((n%100==0)&&(n!=0)))?2:1));" } \ No newline at end of file diff --git a/core/l10n/ru.js b/core/l10n/ru.js index 398fe84160..619d007c85 100644 --- a/core/l10n/ru.js +++ b/core/l10n/ru.js @@ -298,6 +298,7 @@ OC.L10N.register( "Thank you for your patience." : "Спасибо за терпение.", "Two-step verification" : "Двухшаговая проверка", "Enhanced security has been enabled for your account. Please authenticate using a second factor." : "Для вашей учётной записи включена повышенная безопасность. Пожалуйста, аутентифицируйтесь через второй фактор.", + "Cancel login" : "Отменить вход", "Please authenticate using the selected factor." : "Пожалуйста, аутентифицируйтесь выбранным фактором.", "An error occured while verifying the token" : "При проверке токена возникла ошибка.", "You are accessing the server from an untrusted domain." : "Вы пытаетесь получить доступ к серверу с недоверенного домена.", diff --git a/core/l10n/ru.json b/core/l10n/ru.json index fb4bf2a105..9fe8d22fea 100644 --- a/core/l10n/ru.json +++ b/core/l10n/ru.json @@ -296,6 +296,7 @@ "Thank you for your patience." : "Спасибо за терпение.", "Two-step verification" : "Двухшаговая проверка", "Enhanced security has been enabled for your account. Please authenticate using a second factor." : "Для вашей учётной записи включена повышенная безопасность. Пожалуйста, аутентифицируйтесь через второй фактор.", + "Cancel login" : "Отменить вход", "Please authenticate using the selected factor." : "Пожалуйста, аутентифицируйтесь выбранным фактором.", "An error occured while verifying the token" : "При проверке токена возникла ошибка.", "You are accessing the server from an untrusted domain." : "Вы пытаетесь получить доступ к серверу с недоверенного домена.", diff --git a/core/l10n/sq.js b/core/l10n/sq.js index 06da819b5a..049f33d335 100644 --- a/core/l10n/sq.js +++ b/core/l10n/sq.js @@ -298,6 +298,7 @@ OC.L10N.register( "Thank you for your patience." : "Ju faleminderit për durimin.", "Two-step verification" : "Verifikim dyhapësh", "Enhanced security has been enabled for your account. Please authenticate using a second factor." : "Siguria e zgjeruar është aktivizuar për llogarinë tuaj. Ju lutemi, bëni mirëfilltësimin duke përdorur një faktor të dytë.", + "Cancel login" : "Anuloje hyrjen", "Please authenticate using the selected factor." : "Ju lutemi, bëni mirëfilltësimin duke përdorur faktorin e përzgjedhur.", "An error occured while verifying the token" : "Ndodhi një gabim gjatë verifikimit të token-it", "You are accessing the server from an untrusted domain." : "Po hyni në shërbyes nga një përkatësi jo e besuar.", diff --git a/core/l10n/sq.json b/core/l10n/sq.json index 91d40b34e9..d6dee599bf 100644 --- a/core/l10n/sq.json +++ b/core/l10n/sq.json @@ -296,6 +296,7 @@ "Thank you for your patience." : "Ju faleminderit për durimin.", "Two-step verification" : "Verifikim dyhapësh", "Enhanced security has been enabled for your account. Please authenticate using a second factor." : "Siguria e zgjeruar është aktivizuar për llogarinë tuaj. Ju lutemi, bëni mirëfilltësimin duke përdorur një faktor të dytë.", + "Cancel login" : "Anuloje hyrjen", "Please authenticate using the selected factor." : "Ju lutemi, bëni mirëfilltësimin duke përdorur faktorin e përzgjedhur.", "An error occured while verifying the token" : "Ndodhi një gabim gjatë verifikimit të token-it", "You are accessing the server from an untrusted domain." : "Po hyni në shërbyes nga një përkatësi jo e besuar.", diff --git a/lib/l10n/pl.js b/lib/l10n/pl.js index 3124a33526..1a36e96077 100644 --- a/lib/l10n/pl.js +++ b/lib/l10n/pl.js @@ -30,6 +30,7 @@ OC.L10N.register( "_%n minute ago_::_%n minutes ago_" : ["%n minute temu","%n minut temu","%n minut temu"], "seconds ago" : "sekund temu", "Empty filename is not allowed" : "Pusta nazwa nie jest dozwolona.", + "4-byte characters are not supported in file names" : "Znaki 4-bajtowe są niedozwolone w nazwach plików", "File name contains at least one invalid character" : "Nazwa pliku zawiera co najmniej jeden nieprawidłowy znak", "File name is too long" : "Nazwa pliku zbyt długa", "App directory already exists" : "Katalog aplikacji już isnieje", diff --git a/lib/l10n/pl.json b/lib/l10n/pl.json index bbb4cc7b5e..048530779e 100644 --- a/lib/l10n/pl.json +++ b/lib/l10n/pl.json @@ -28,6 +28,7 @@ "_%n minute ago_::_%n minutes ago_" : ["%n minute temu","%n minut temu","%n minut temu"], "seconds ago" : "sekund temu", "Empty filename is not allowed" : "Pusta nazwa nie jest dozwolona.", + "4-byte characters are not supported in file names" : "Znaki 4-bajtowe są niedozwolone w nazwach plików", "File name contains at least one invalid character" : "Nazwa pliku zawiera co najmniej jeden nieprawidłowy znak", "File name is too long" : "Nazwa pliku zbyt długa", "App directory already exists" : "Katalog aplikacji już isnieje", diff --git a/lib/l10n/pt_PT.js b/lib/l10n/pt_PT.js index 196009d71d..f069531895 100644 --- a/lib/l10n/pt_PT.js +++ b/lib/l10n/pt_PT.js @@ -18,12 +18,12 @@ OC.L10N.register( "Following platforms are supported: %s" : "São suportadas as seguintes plataformas: %s", "ownCloud %s or higher is required." : "É necessário ownCloud %s ou superior.", "ownCloud %s or lower is required." : "É necessário ownCloud %s ou inferior.", - "Unknown filetype" : "Ficheiro desconhecido", + "Unknown filetype" : "Tipo de ficheiro desconhecido", "Invalid image" : "Imagem inválida", "today" : "hoje", "yesterday" : "ontem", "_%n day ago_::_%n days ago_" : ["%n dia atrás","%n dias atrás"], - "last month" : "ultímo mês", + "last month" : "ultimo mês", "last year" : "ano passado", "_%n year ago_::_%n years ago_" : ["%n ano atrás","%n anos atrás"], "seconds ago" : "Minutos atrás", diff --git a/lib/l10n/pt_PT.json b/lib/l10n/pt_PT.json index 5dc215f41b..55492d0f14 100644 --- a/lib/l10n/pt_PT.json +++ b/lib/l10n/pt_PT.json @@ -16,12 +16,12 @@ "Following platforms are supported: %s" : "São suportadas as seguintes plataformas: %s", "ownCloud %s or higher is required." : "É necessário ownCloud %s ou superior.", "ownCloud %s or lower is required." : "É necessário ownCloud %s ou inferior.", - "Unknown filetype" : "Ficheiro desconhecido", + "Unknown filetype" : "Tipo de ficheiro desconhecido", "Invalid image" : "Imagem inválida", "today" : "hoje", "yesterday" : "ontem", "_%n day ago_::_%n days ago_" : ["%n dia atrás","%n dias atrás"], - "last month" : "ultímo mês", + "last month" : "ultimo mês", "last year" : "ano passado", "_%n year ago_::_%n years ago_" : ["%n ano atrás","%n anos atrás"], "seconds ago" : "Minutos atrás", diff --git a/settings/l10n/en_GB.js b/settings/l10n/en_GB.js index 443ca4adc3..bf3bf387fa 100644 --- a/settings/l10n/en_GB.js +++ b/settings/l10n/en_GB.js @@ -137,6 +137,7 @@ OC.L10N.register( "The Read-Only config has been enabled. This prevents setting some configurations via the web-interface. Furthermore, the file needs to be made writable manually for every update." : "The Read-Only config has been enabled. This prevents setting some configurations via the web-interface. Furthermore, the file needs to be made writable manually for every update.", "PHP is apparently setup to strip inline doc blocks. This will make several core apps inaccessible." : "PHP is apparently setup to strip inline doc blocks. This will make several core apps inaccessible.", "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator.", + "Your database does not run with \"READ COMMITED\" transaction isolation level. This can cause problems when multiple actions are executed in parallel." : "Your database does not run with \"READ COMMITED\" transaction isolation level. This can cause problems when multiple actions are executed in parallel.", "Your server is running on Microsoft Windows. We highly recommend Linux for optimal user experience." : "Your server is running on Microsoft Windows. We highly recommend Linux for optimal user experience.", "%1$s below version %2$s is installed, for stability and performance reasons we recommend updating to a newer %1$s version." : "%1$s below version %2$s is installed, for stability and performance reasons we recommend updating to a newer %1$s version.", "The PHP module 'fileinfo' is missing. We strongly recommend to enable this module to get best results with mime-type detection." : "The PHP module 'fileinfo' is missing. We strongly recommend enabling this module to get best results with mime-type detection.", @@ -266,6 +267,7 @@ OC.L10N.register( "Current password" : "Current password", "New password" : "New password", "Change password" : "Change password", + "These are the web, desktop and mobile clients currently logged in to your ownCloud." : "These are the web, desktop and mobile clients currently logged in to your ownCloud.", "Browser" : "Browser", "Most recent activity" : "Most recent activity", "You've linked these devices." : "You've linked these devices.", diff --git a/settings/l10n/en_GB.json b/settings/l10n/en_GB.json index 838710e407..9ab2f0e5a9 100644 --- a/settings/l10n/en_GB.json +++ b/settings/l10n/en_GB.json @@ -135,6 +135,7 @@ "The Read-Only config has been enabled. This prevents setting some configurations via the web-interface. Furthermore, the file needs to be made writable manually for every update." : "The Read-Only config has been enabled. This prevents setting some configurations via the web-interface. Furthermore, the file needs to be made writable manually for every update.", "PHP is apparently setup to strip inline doc blocks. This will make several core apps inaccessible." : "PHP is apparently setup to strip inline doc blocks. This will make several core apps inaccessible.", "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator." : "This is probably caused by a cache/accelerator such as Zend OPcache or eAccelerator.", + "Your database does not run with \"READ COMMITED\" transaction isolation level. This can cause problems when multiple actions are executed in parallel." : "Your database does not run with \"READ COMMITED\" transaction isolation level. This can cause problems when multiple actions are executed in parallel.", "Your server is running on Microsoft Windows. We highly recommend Linux for optimal user experience." : "Your server is running on Microsoft Windows. We highly recommend Linux for optimal user experience.", "%1$s below version %2$s is installed, for stability and performance reasons we recommend updating to a newer %1$s version." : "%1$s below version %2$s is installed, for stability and performance reasons we recommend updating to a newer %1$s version.", "The PHP module 'fileinfo' is missing. We strongly recommend to enable this module to get best results with mime-type detection." : "The PHP module 'fileinfo' is missing. We strongly recommend enabling this module to get best results with mime-type detection.", @@ -264,6 +265,7 @@ "Current password" : "Current password", "New password" : "New password", "Change password" : "Change password", + "These are the web, desktop and mobile clients currently logged in to your ownCloud." : "These are the web, desktop and mobile clients currently logged in to your ownCloud.", "Browser" : "Browser", "Most recent activity" : "Most recent activity", "You've linked these devices." : "You've linked these devices.", From 7b3dc806eb45a65279acee2e54f32f2d2f388980 Mon Sep 17 00:00:00 2001 From: Christoph Wurst Date: Fri, 10 Jun 2016 09:52:52 +0200 Subject: [PATCH 22/30] Check 2FA state for raw php files too --- lib/private/legacy/json.php | 4 +++- lib/private/legacy/util.php | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/private/legacy/json.php b/lib/private/legacy/json.php index d201d69723..1dde63602b 100644 --- a/lib/private/legacy/json.php +++ b/lib/private/legacy/json.php @@ -64,7 +64,9 @@ class OC_JSON{ * @deprecated Use annotation based ACLs from the AppFramework instead */ public static function checkLoggedIn() { - if( !OC_User::isLoggedIn()) { + $twoFactorAuthManger = \OC::$server->getTwoFactorAuthManager(); + if( !OC_User::isLoggedIn() + || $twoFactorAuthManger->needsSecondFactor()) { $l = \OC::$server->getL10N('lib'); http_response_code(\OCP\AppFramework\Http::STATUS_UNAUTHORIZED); self::error(array( 'data' => array( 'message' => $l->t('Authentication error'), 'error' => 'authentication_error' ))); diff --git a/lib/private/legacy/util.php b/lib/private/legacy/util.php index a863348566..65d00c1638 100644 --- a/lib/private/legacy/util.php +++ b/lib/private/legacy/util.php @@ -970,6 +970,11 @@ class OC_Util { ); exit(); } + // Redirect to index page if 2FA challenge was not solved yet + if (\OC::$server->getTwoFactorAuthManager()->needsSecondFactor()) { + header('Location: ' . \OCP\Util::linkToAbsolute('', 'index.php')); + exit(); + } } /** From c45b7b0bdf801a2a58fc28602285d52331456222 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Fri, 10 Jun 2016 11:16:32 +0200 Subject: [PATCH 23/30] Allow empty host when installing on oracle via CLI (#25034) --- core/Command/Maintenance/Install.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/core/Command/Maintenance/Install.php b/core/Command/Maintenance/Install.php index b1b63b9b3b..12a61d6341 100644 --- a/core/Command/Maintenance/Install.php +++ b/core/Command/Maintenance/Install.php @@ -106,7 +106,12 @@ class Install extends Command { $dbUser = $input->getOption('database-user'); $dbPass = $input->getOption('database-pass'); $dbName = $input->getOption('database-name'); - $dbHost = $input->getOption('database-host'); + if ($db === 'oci') { + // an empty hostname needs to be read from the raw parameters + $dbHost = $input->getParameterOption('--database-host', ''); + } else { + $dbHost = $input->getOption('database-host'); + } $dbTablePrefix = 'oc_'; if ($input->hasParameterOption('--database-table-prefix')) { $dbTablePrefix = (string) $input->getOption('database-table-prefix'); From 1b66db72d916a763b1c22613b9df5c42e93593a8 Mon Sep 17 00:00:00 2001 From: Roeland Jago Douma Date: Mon, 30 May 2016 14:56:19 +0200 Subject: [PATCH 24/30] Repair job to fix permissions for avatars Fixes #22978 On some older installations the permissions for the userRoot and the avatars are not correct. This breaks since we now use the Node API in the avatar code. This repair job makes sure that the permissions are set correctly. * Unit tests added --- lib/private/Repair.php | 2 + lib/private/Repair/AvatarPermissions.php | 107 ++++++++++++ tests/lib/Repair/AvatarPermissionsTest.php | 180 +++++++++++++++++++++ 3 files changed, 289 insertions(+) create mode 100644 lib/private/Repair/AvatarPermissions.php create mode 100644 tests/lib/Repair/AvatarPermissionsTest.php diff --git a/lib/private/Repair.php b/lib/private/Repair.php index 710bbbbaed..bb2967d7e6 100644 --- a/lib/private/Repair.php +++ b/lib/private/Repair.php @@ -29,6 +29,7 @@ namespace OC; use OC\Repair\AssetCache; +use OC\Repair\AvatarPermissions; use OC\Repair\CleanTags; use OC\Repair\Collation; use OC\Repair\DropOldJobs; @@ -134,6 +135,7 @@ class Repair implements IOutput{ new RepairInvalidShares(\OC::$server->getConfig(), \OC::$server->getDatabaseConnection()), new SharePropagation(\OC::$server->getConfig()), new RemoveOldShares(\OC::$server->getDatabaseConnection()), + new AvatarPermissions(\OC::$server->getDatabaseConnection()), ]; } diff --git a/lib/private/Repair/AvatarPermissions.php b/lib/private/Repair/AvatarPermissions.php new file mode 100644 index 0000000000..1d96a79ac1 --- /dev/null +++ b/lib/private/Repair/AvatarPermissions.php @@ -0,0 +1,107 @@ + + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ +namespace OC\Repair; + +use OCP\IDBConnection; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; + +/** + * Class AvatarPermissions + * + * @package OC\Repair + */ +class AvatarPermissions implements IRepairStep { + /** @var IDBConnection */ + private $connection; + + /** + * AvatarPermissions constructor. + * + * @param IDBConnection $connection + */ + public function __construct(IDBConnection $connection) { + $this->connection = $connection; + } + + /** + * @return string + */ + public function getName() { + return 'Fix permissions so avatars can be stored again'; + } + + /** + * @param IOutput $output + */ + public function run(IOutput $output) { + $output->startProgress(2); + $this->fixUserRootPermissions(); + $output->advance(); + $this->fixAvatarPermissions(); + $output->finishProgress(); + } + + /** + * Make sure all user roots have permissions 23 (all but share) + */ + protected function fixUserRootPermissions() { + $qb = $this->connection->getQueryBuilder(); + $qb2 = $this->connection->getQueryBuilder(); + + $qb->select('numeric_id') + ->from('storages') + ->where($qb->expr()->like('id', $qb2->createParameter('like'))); + + $qb2->update('filecache') + ->set('permissions', $qb2->createNamedParameter(23)) + ->where($qb2->expr()->eq('path', $qb2->createNamedParameter(''))) + ->andWhere($qb2->expr()->in('storage', $qb2->createFunction($qb->getSQL()))) + ->andWhere($qb2->expr()->neq('permissions', $qb2->createNamedParameter(23))) + ->setParameter('like', 'home::%'); + + + $qb2->execute(); + } + + /** + * Make sure all avatar files in the user roots have permission 27 + */ + protected function fixAvatarPermissions() { + $qb = $this->connection->getQueryBuilder(); + $qb2 = $this->connection->getQueryBuilder(); + + $qb->select('numeric_id') + ->from('storages') + ->where($qb->expr()->like('id', $qb2->createParameter('like'))); + + $qb2->update('filecache') + ->set('permissions', $qb2->createNamedParameter(27)) + ->where($qb2->expr()->like('path', $qb2->createNamedParameter('avatar.%'))) + ->andWhere($qb2->expr()->in('storage', $qb2->createFunction($qb->getSQL()))) + ->andWhere($qb2->expr()->neq('permissions', $qb2->createNamedParameter(27))) + ->setParameter('like', 'home::%'); + + $qb2->execute(); + } + +} + diff --git a/tests/lib/Repair/AvatarPermissionsTest.php b/tests/lib/Repair/AvatarPermissionsTest.php new file mode 100644 index 0000000000..fb0b29daeb --- /dev/null +++ b/tests/lib/Repair/AvatarPermissionsTest.php @@ -0,0 +1,180 @@ + + * + * @copyright Copyright (c) 2016, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see + * + */ +namespace Test\Repair; + +/** + * Test for fixing the userRoot and avatar permissions + * + * @group DB + * + * @see \OC\Repair\AvatarPermissionsTest + */ +class AvatarPermissionsTest extends \Test\TestCase { + + /** @var \OC\Repair\AvatarPermissions */ + protected $repair; + + /** @var \OCP\IDBConnection */ + protected $connection; + + protected function setUp() { + parent::setUp(); + + $this->connection = \OC::$server->getDatabaseConnection(); + $this->repair = new \OC\Repair\AvatarPermissions($this->connection); + $this->cleanUpTables(); + } + + protected function tearDown() { + $this->cleanUpTables(); + + parent::tearDown(); + } + + protected function cleanUpTables() { + $qb = $this->connection->getQueryBuilder(); + $qb->delete('filecache')->execute(); + $qb->delete('storages')->execute(); + } + + public function dataFixUserRootPermissions() { + return [ + ['home::user', '', 0, 23], + ['home::user', 'foo', 0, 0], + ['home::user', 'avatar.jpg', 0, 0], + ['ABC::user', '', 0, 0], + ['ABC::user', 'foo', 0, 0], + ]; + } + + /** + * @dataProvider dataFixUserRootPermissions + * + * @param string $storageId + * @param string $path + * @param int $permissionsBefore + * @param int $permissionsAfter + */ + public function testFixUserRootPermissions($storageId, $path, $permissionsBefore, $permissionsAfter) { + $userStorage = $this->addStorage($storageId); + $userHome = $this->addFileCacheEntry($userStorage, $path, $permissionsBefore); + + $this->invokePrivate($this->repair, 'fixUserRootPermissions', []); + + $this->verifyPermissions($userHome, $permissionsAfter); + } + + public function dataFixAvatarPermissions() { + return [ + ['home::user', '', 0, 0], + ['home::user', 'avatar.jpg', 0, 27], + ['home::user', 'avatar.png', 0, 27], + ['home::user', 'avatar.32.png', 0, 27], + ['home::user', 'mine.txt', 0, 0], + ['ABC::user', '', 0, 0], + ['ABC::user', 'avatar.jpg', 0, 0], + ['ABC::user', 'avatar.png', 0, 0], + ['ABC::user', 'avatar.32.png', 0, 0], + ['ABC::user', 'mine.txt', 0, 0], + ]; + } + + /** + * @dataProvider dataFixAvatarPermissions + * + * @param string $storageId + * @param string $path + * @param int $permissionsBefore + * @param int $permissionsAfter + */ + public function testFixAvatarPermissions($storageId, $path, $permissionsBefore, $permissionsAfter) { + $userStorage = $this->addStorage($storageId); + $userHome = $this->addFileCacheEntry($userStorage, $path, $permissionsBefore); + + $this->invokePrivate($this->repair, 'fixAvatarPermissions', []); + + $this->verifyPermissions($userHome, $permissionsAfter); + } + + /** + * Add a new storage + * + * @param string $id + * @return int The numeric id + */ + protected function addStorage($id) { + $qb = $this->connection->getQueryBuilder(); + + $qb->insert('storages') + ->values([ + 'id' => $qb->createNamedParameter($id) + ]); + + $qb->execute(); + + return $qb->getLastInsertId(); + } + + /** + * Add a filecache entry + * + * @param int $storage + * @param string $path + * @param int $permissions + * + * @return int The fileid + */ + protected function addFileCacheEntry($storage, $path, $permissions) { + $qb = $this->connection->getQueryBuilder(); + + $qb->insert('filecache') + ->values([ + 'path' => $qb->createNamedParameter($path), + 'storage' => $qb->createNamedParameter($storage), + 'permissions' => $qb->createNamedParameter($permissions), + ]); + + $qb->execute(); + + return $qb->getLastInsertId(); + } + + /** + * @param int $fileId + * @param int $permissions + */ + protected function verifyPermissions($fileId, $permissions) { + $qb = $this->connection->getQueryBuilder(); + + $qb->select('permissions') + ->from('filecache') + ->where($qb->expr()->eq('fileid', $qb->createNamedParameter($fileId))); + + $cursor = $qb->execute(); + + $data = $cursor->fetch(); + $cursor->closeCursor(); + + $this->assertSame($permissions, (int)$data['permissions']); + } + + +} From 60225284f3b8ef947f67bbe168b12dca867d6f3d Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Thu, 2 Jun 2016 13:45:13 +0200 Subject: [PATCH 25/30] Add not-null columns for oracle --- tests/lib/Repair/AvatarPermissionsTest.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/lib/Repair/AvatarPermissionsTest.php b/tests/lib/Repair/AvatarPermissionsTest.php index fb0b29daeb..e3f582dc51 100644 --- a/tests/lib/Repair/AvatarPermissionsTest.php +++ b/tests/lib/Repair/AvatarPermissionsTest.php @@ -148,6 +148,15 @@ class AvatarPermissionsTest extends \Test\TestCase { $qb->insert('filecache') ->values([ 'path' => $qb->createNamedParameter($path), + 'path_hash' => $qb->createNamedParameter(md5($path)), + 'parent' => $qb->createNamedParameter(42), + 'mimetype' => $qb->createNamedParameter(23), + 'mimepart' => $qb->createNamedParameter(32), + 'size' => $qb->createNamedParameter(16), + 'mtime' => $qb->createNamedParameter(1), + 'storage_mtime' => $qb->createNamedParameter(2), + 'encrypted' => $qb->createNamedParameter(0), + 'unencrypted_size' => $qb->createNamedParameter(0), 'storage' => $qb->createNamedParameter($storage), 'permissions' => $qb->createNamedParameter($permissions), ]); From 28d9ad2817b744bcef35a9259f65eb56b0b2ae78 Mon Sep 17 00:00:00 2001 From: Roeland Jago Douma Date: Fri, 3 Jun 2016 14:43:31 +0200 Subject: [PATCH 26/30] Empty string is null on oracle --- lib/private/Repair/AvatarPermissions.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/private/Repair/AvatarPermissions.php b/lib/private/Repair/AvatarPermissions.php index 1d96a79ac1..d23479f5ba 100644 --- a/lib/private/Repair/AvatarPermissions.php +++ b/lib/private/Repair/AvatarPermissions.php @@ -20,6 +20,7 @@ */ namespace OC\Repair; +use Doctrine\DBAL\Platforms\OraclePlatform; use OCP\IDBConnection; use OCP\Migration\IOutput; use OCP\Migration\IRepairStep; @@ -71,9 +72,16 @@ class AvatarPermissions implements IRepairStep { ->from('storages') ->where($qb->expr()->like('id', $qb2->createParameter('like'))); + if ($this->connection->getDatabasePlatform() instanceof OraclePlatform) { + // '' is null on oracle + $path = $qb2->expr()->isNull('path'); + } else { + $path = $qb2->expr()->eq('path', $qb2->createNamedParameter('')); + } + $qb2->update('filecache') ->set('permissions', $qb2->createNamedParameter(23)) - ->where($qb2->expr()->eq('path', $qb2->createNamedParameter(''))) + ->where($path) ->andWhere($qb2->expr()->in('storage', $qb2->createFunction($qb->getSQL()))) ->andWhere($qb2->expr()->neq('permissions', $qb2->createNamedParameter(23))) ->setParameter('like', 'home::%'); From 77caa0e91346ec4478afcac10d102a0e9e4c3ce4 Mon Sep 17 00:00:00 2001 From: Vincent Petry Date: Fri, 10 Jun 2016 13:02:41 +0200 Subject: [PATCH 27/30] Use capped cache for encryption's user access list --- lib/private/Encryption/File.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/private/Encryption/File.php b/lib/private/Encryption/File.php index 2bd9fb0210..3573ff19c6 100644 --- a/lib/private/Encryption/File.php +++ b/lib/private/Encryption/File.php @@ -22,6 +22,8 @@ namespace OC\Encryption; +use OC\Cache\CappedMemoryCache; + class File implements \OCP\Encryption\IFile { /** @var Util */ @@ -36,6 +38,7 @@ class File implements \OCP\Encryption\IFile { public function __construct(Util $util) { $this->util = $util; + $this->cache = new CappedMemoryCache(); } From c668588e1757b16a9b9a8586ee2a2ab936cf1c72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Fri, 10 Jun 2016 14:07:31 +0200 Subject: [PATCH 28/30] map oracle driver options to params (#23938) --- lib/private/DB/ConnectionFactory.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/private/DB/ConnectionFactory.php b/lib/private/DB/ConnectionFactory.php index 0856d8d19c..6a096e504d 100644 --- a/lib/private/DB/ConnectionFactory.php +++ b/lib/private/DB/ConnectionFactory.php @@ -104,6 +104,10 @@ class ConnectionFactory { break; case 'oci': $eventManager->addEventSubscriber(new OracleSessionInit); + // the driverOptions are unused in dbal and need to be mapped to the parameters + if (isset($additionalConnectionParams['driverOptions'])) { + $additionalConnectionParams = array_merge($additionalConnectionParams, $additionalConnectionParams['driverOptions']); + } break; case 'sqlite3': $journalMode = $additionalConnectionParams['sqlite.journal_mode']; From 372213b052dedf51e568c04cd9c724995b316986 Mon Sep 17 00:00:00 2001 From: Joas Schilling Date: Fri, 10 Jun 2016 15:15:27 +0200 Subject: [PATCH 29/30] Add developer documentation, website and bug to the applist (#25041) --- settings/templates/apps.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/settings/templates/apps.php b/settings/templates/apps.php index ecb00fb27c..d3c1433269 100644 --- a/settings/templates/apps.php +++ b/settings/templates/apps.php @@ -91,8 +91,22 @@ script( t('Admin documentation'));?> ↗ {{/if}} + + {{#if documentation.developer}} + + t('Developer documentation'));?> ↗ + + {{/if}}

{{/if}} + + {{#if website}} + t('Visit website'));?> ↗ + {{/if}} + + {{#if bugs}} + t('Report a bug'));?> ↗ + {{/if}}
t("Show description …"));?>
From c14198b874e147c635d8f47da285dda8c99fdbb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Fri, 10 Jun 2016 15:20:22 +0200 Subject: [PATCH 30/30] fetchall appconfig entries (#23835) --- lib/private/AppConfig.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/private/AppConfig.php b/lib/private/AppConfig.php index f84c8a41f1..4e48ee149e 100644 --- a/lib/private/AppConfig.php +++ b/lib/private/AppConfig.php @@ -273,7 +273,9 @@ class AppConfig implements IAppConfig { ->from('appconfig'); $result = $sql->execute(); - while ($row = $result->fetch()) { + // we are going to store the result in memory anyway + $rows = $result->fetchAll(); + foreach ($rows as $row) { if (!isset($this->cache[$row['appid']])) { $this->cache[$row['appid']] = []; }