diff options
Diffstat (limited to 'apps')
111 files changed, 2190 insertions, 590 deletions
diff --git a/apps/dav/composer/composer/autoload_classmap.php b/apps/dav/composer/composer/autoload_classmap.php index fe7557f7e08..c9680651ff9 100644 --- a/apps/dav/composer/composer/autoload_classmap.php +++ b/apps/dav/composer/composer/autoload_classmap.php @@ -156,6 +156,7 @@ return array( 'OCA\\DAV\\Migration\\Version1005Date20180530124431' => $baseDir . '/../lib/Migration/Version1005Date20180530124431.php', 'OCA\\DAV\\Migration\\Version1006Date20180619154313' => $baseDir . '/../lib/Migration/Version1006Date20180619154313.php', 'OCA\\DAV\\Migration\\Version1007Date20181007225117' => $baseDir . '/../lib/Migration/Version1007Date20181007225117.php', + 'OCA\\DAV\\Migration\\Version1008Date20181030113700' => $baseDir . '/../lib/Migration/Version1008Date20181030113700.php', 'OCA\\DAV\\RootCollection' => $baseDir . '/../lib/RootCollection.php', 'OCA\\DAV\\Server' => $baseDir . '/../lib/Server.php', 'OCA\\DAV\\Settings\\CalDAVSettings' => $baseDir . '/../lib/Settings/CalDAVSettings.php', diff --git a/apps/dav/composer/composer/autoload_static.php b/apps/dav/composer/composer/autoload_static.php index 1668f1270f5..71879abc0a5 100644 --- a/apps/dav/composer/composer/autoload_static.php +++ b/apps/dav/composer/composer/autoload_static.php @@ -171,6 +171,7 @@ class ComposerStaticInitDAV 'OCA\\DAV\\Migration\\Version1005Date20180530124431' => __DIR__ . '/..' . '/../lib/Migration/Version1005Date20180530124431.php', 'OCA\\DAV\\Migration\\Version1006Date20180619154313' => __DIR__ . '/..' . '/../lib/Migration/Version1006Date20180619154313.php', 'OCA\\DAV\\Migration\\Version1007Date20181007225117' => __DIR__ . '/..' . '/../lib/Migration/Version1007Date20181007225117.php', + 'OCA\\DAV\\Migration\\Version1008Date20181030113700' => __DIR__ . '/..' . '/../lib/Migration/Version1008Date20181030113700.php', 'OCA\\DAV\\RootCollection' => __DIR__ . '/..' . '/../lib/RootCollection.php', 'OCA\\DAV\\Server' => __DIR__ . '/..' . '/../lib/Server.php', 'OCA\\DAV\\Settings\\CalDAVSettings' => __DIR__ . '/..' . '/../lib/Settings/CalDAVSettings.php', diff --git a/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php b/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php index 6f303acba33..3ff3ed0c569 100644 --- a/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php +++ b/apps/dav/lib/CalDAV/Schedule/IMipPlugin.php @@ -170,7 +170,7 @@ class IMipPlugin extends SabreIMipPlugin { $vevent = $iTipMessage->message->VEVENT; $attendee = $this->getCurrentAttendee($iTipMessage); - $defaultLang = $this->config->getUserValue($this->userId, 'core', 'lang', $this->l10nFactory->findLanguage()); + $defaultLang = $this->l10nFactory->findLanguage(); $lang = $this->getAttendeeLangOrDefault($defaultLang, $attendee); $l10n = $this->l10nFactory->get('dav', $lang); diff --git a/apps/dav/lib/CardDAV/AddressBookImpl.php b/apps/dav/lib/CardDAV/AddressBookImpl.php index 5034b16ed2f..1aedd5d5643 100644 --- a/apps/dav/lib/CardDAV/AddressBookImpl.php +++ b/apps/dav/lib/CardDAV/AddressBookImpl.php @@ -88,16 +88,26 @@ class AddressBookImpl implements IAddressBook { /** * @param string $pattern which should match within the $searchProperties * @param array $searchProperties defines the properties within the query pattern should match - * @param array $options - for future use. One should always have options! + * @param array $options Options to define the output format + * - types boolean (since 15.0.0) If set to true, fields that come with a TYPE property will be an array + * example: ['id' => 5, 'FN' => 'Thomas Tanghus', 'EMAIL' => ['type => 'HOME', 'value' => 'g@h.i']] + * @return array an array of contacts which are arrays of key-value-pairs + * example result: + * [ + * ['id' => 0, 'FN' => 'Thomas Müller', 'EMAIL' => 'a@b.c', 'GEO' => '37.386013;-122.082932'], + * ['id' => 5, 'FN' => 'Thomas Tanghus', 'EMAIL' => ['d@e.f', 'g@h.i']] + * ] * @return array an array of contacts which are arrays of key-value-pairs * @since 5.0.0 */ public function search($pattern, $searchProperties, $options) { $results = $this->backend->search($this->getKey(), $pattern, $searchProperties); + $withTypes = \array_key_exists('types', $options) && $options['types'] === true; + $vCards = []; foreach ($results as $result) { - $vCards[] = $this->vCard2Array($result['uri'], $this->readCard($result['carddata'])); + $vCards[] = $this->vCard2Array($result['uri'], $this->readCard($result['carddata']), $withTypes); } return $vCards; @@ -220,7 +230,7 @@ class AddressBookImpl implements IAddressBook { * @param VCard $vCard * @return array */ - protected function vCard2Array($uri, VCard $vCard) { + protected function vCard2Array($uri, VCard $vCard, $withTypes = false) { $result = [ 'URI' => $uri, ]; @@ -255,15 +265,28 @@ class AddressBookImpl implements IAddressBook { $result[$property->name] = []; } - $result[$property->name][] = $property->getValue(); + $type = $this->getTypeFromProperty($property); + if ($withTypes) { + $result[$property->name][] = [ + 'type' => $type, + 'value' => $property->getValue() + ]; + } else { + $result[$property->name][] = $property->getValue(); + } + } else { $result[$property->name] = $property->getValue(); } } - if ($this->addressBookInfo['principaluri'] === 'principals/system/system' && - $this->addressBookInfo['uri'] === 'system') { + if ( + $this->addressBookInfo['principaluri'] === 'principals/system/system' && ( + $this->addressBookInfo['uri'] === 'system' || + $this->addressBookInfo['{DAV:}displayname'] === $this->urlGenerator->getBaseUrl() + ) + ) { $result['isLocalSystemBook'] = true; } return $result; diff --git a/apps/dav/lib/CardDAV/CardDavBackend.php b/apps/dav/lib/CardDAV/CardDavBackend.php index a2d3b03147b..a8907f631cd 100644 --- a/apps/dav/lib/CardDAV/CardDavBackend.php +++ b/apps/dav/lib/CardDAV/CardDavBackend.php @@ -494,7 +494,7 @@ class CardDavBackend implements BackendInterface, SyncSupport { */ function getCards($addressBookId) { $query = $this->db->getQueryBuilder(); - $query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata']) + $query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid']) ->from('cards') ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId))); @@ -525,7 +525,7 @@ class CardDavBackend implements BackendInterface, SyncSupport { */ function getCard($addressBookId, $cardUri) { $query = $this->db->getQueryBuilder(); - $query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata']) + $query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid']) ->from('cards') ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId))) ->andWhere($query->expr()->eq('uri', $query->createNamedParameter($cardUri))) @@ -563,7 +563,7 @@ class CardDavBackend implements BackendInterface, SyncSupport { $cards = []; $query = $this->db->getQueryBuilder(); - $query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata']) + $query->select(['id', 'uri', 'lastmodified', 'etag', 'size', 'carddata', 'uid']) ->from('cards') ->where($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId))) ->andWhere($query->expr()->in('uri', $query->createParameter('uri'))); @@ -609,6 +609,7 @@ class CardDavBackend implements BackendInterface, SyncSupport { */ function createCard($addressBookId, $cardUri, $cardData) { $etag = md5($cardData); + $uid = $this->getUID($cardData); $query = $this->db->getQueryBuilder(); $query->insert('cards') @@ -619,6 +620,7 @@ class CardDavBackend implements BackendInterface, SyncSupport { 'addressbookid' => $query->createNamedParameter($addressBookId), 'size' => $query->createNamedParameter(strlen($cardData)), 'etag' => $query->createNamedParameter($etag), + 'uid' => $query->createNamedParameter($uid), ]) ->execute(); @@ -661,6 +663,7 @@ class CardDavBackend implements BackendInterface, SyncSupport { */ function updateCard($addressBookId, $cardUri, $cardData) { + $uid = $this->getUID($cardData); $etag = md5($cardData); $query = $this->db->getQueryBuilder(); $query->update('cards') @@ -668,6 +671,7 @@ class CardDavBackend implements BackendInterface, SyncSupport { ->set('lastmodified', $query->createNamedParameter(time())) ->set('size', $query->createNamedParameter(strlen($cardData))) ->set('etag', $query->createNamedParameter($etag)) + ->set('uid', $query->createNamedParameter($uid)) ->where($query->expr()->eq('uri', $query->createNamedParameter($cardUri))) ->andWhere($query->expr()->eq('addressbookid', $query->createNamedParameter($addressBookId))) ->execute(); @@ -1125,4 +1129,25 @@ class CardDavBackend implements BackendInterface, SyncSupport { $addressbookInfo[$displaynameKey] = $principalInformation['{DAV:}displayname']; } } + + /** + * Extract UID from vcard + * + * @param string $cardData the vcard raw data + * @return string the uid + * @throws BadRequest if no UID is available + */ + private function getUID($cardData) { + if ($cardData != '') { + $vCard = Reader::read($cardData); + if ($vCard->UID) { + $uid = $vCard->UID->getValue(); + return $uid; + } + // should already be handled, but just in case + throw new BadRequest('vCards on CardDAV servers MUST have a UID property'); + } + // should already be handled, but just in case + throw new BadRequest('vCard can not be empty'); + } } diff --git a/apps/dav/lib/Connector/Sabre/Auth.php b/apps/dav/lib/Connector/Sabre/Auth.php index fcd1b34edbc..292be61c9dc 100644 --- a/apps/dav/lib/Connector/Sabre/Auth.php +++ b/apps/dav/lib/Connector/Sabre/Auth.php @@ -228,11 +228,12 @@ class Auth extends AbstractBasic { if($this->twoFactorManager->needsSecondFactor($this->userSession->getUser())) { throw new \Sabre\DAV\Exception\NotAuthenticated('2FA challenge not passed.'); } - if (\OC_User::handleApacheAuth() || + if ( //Fix for broken webdav clients ($this->userSession->isLoggedIn() && is_null($this->session->get(self::DAV_AUTHENTICATED))) || //Well behaved clients that only send the cookie are allowed - ($this->userSession->isLoggedIn() && $this->session->get(self::DAV_AUTHENTICATED) === $this->userSession->getUser()->getUID() && $request->getHeader('Authorization') === null) + ($this->userSession->isLoggedIn() && $this->session->get(self::DAV_AUTHENTICATED) === $this->userSession->getUser()->getUID() && $request->getHeader('Authorization') === null) || + \OC_User::handleApacheAuth() ) { $user = $this->userSession->getUser()->getUID(); \OC_Util::setupFS($user); diff --git a/apps/dav/lib/Connector/Sabre/File.php b/apps/dav/lib/Connector/Sabre/File.php index 9e927ff85e5..57c072fda47 100644 --- a/apps/dav/lib/Connector/Sabre/File.php +++ b/apps/dav/lib/Connector/Sabre/File.php @@ -164,14 +164,19 @@ class File extends Node implements IFile { $this->changeLock(ILockingProvider::LOCK_EXCLUSIVE); } - $target = $partStorage->fopen($internalPartPath, 'wb'); - if ($target === false) { - \OC::$server->getLogger()->error('\OC\Files\Filesystem::fopen() failed', ['app' => 'webdav']); - // because we have no clue about the cause we can only throw back a 500/Internal Server Error - throw new Exception('Could not write file contents'); + if ($partStorage->instanceOfStorage(Storage\IWriteStreamStorage::class)) { + $count = $partStorage->writeStream($internalPartPath, $data); + $result = $count > 0; + } else { + $target = $partStorage->fopen($internalPartPath, 'wb'); + if ($target === false) { + \OC::$server->getLogger()->error('\OC\Files\Filesystem::fopen() failed', ['app' => 'webdav']); + // because we have no clue about the cause we can only throw back a 500/Internal Server Error + throw new Exception('Could not write file contents'); + } + list($count, $result) = \OC_Helper::streamCopy($data, $target); + fclose($target); } - list($count, $result) = \OC_Helper::streamCopy($data, $target); - fclose($target); if ($result === false) { $expected = -1; @@ -185,7 +190,7 @@ class File extends Node implements IFile { // double check if the file was fully received // compare expected and actual size if (isset($_SERVER['CONTENT_LENGTH']) && $_SERVER['REQUEST_METHOD'] === 'PUT') { - $expected = (int) $_SERVER['CONTENT_LENGTH']; + $expected = (int)$_SERVER['CONTENT_LENGTH']; if ($count !== $expected) { throw new BadRequest('expected filesize ' . $expected . ' got ' . $count); } @@ -219,7 +224,7 @@ class File extends Node implements IFile { $renameOkay = $storage->moveFromStorage($partStorage, $internalPartPath, $internalPath); $fileExists = $storage->file_exists($internalPath); if ($renameOkay === false || $fileExists === false) { - \OC::$server->getLogger()->error('renaming part file to final file failed ($run: ' . ( $run ? 'true' : 'false' ) . ', $renameOkay: ' . ( $renameOkay ? 'true' : 'false' ) . ', $fileExists: ' . ( $fileExists ? 'true' : 'false' ) . ')', ['app' => 'webdav']); + \OC::$server->getLogger()->error('renaming part file to final file failed $renameOkay: ' . ($renameOkay ? 'true' : 'false') . ', $fileExists: ' . ($fileExists ? 'true' : 'false') . ')', ['app' => 'webdav']); throw new Exception('Could not rename part file to final file'); } } catch (ForbiddenException $ex) { @@ -246,7 +251,7 @@ class File extends Node implements IFile { $this->header('X-OC-MTime: accepted'); } } - + if ($view) { $this->emitPostHooks($exists); } @@ -443,7 +448,7 @@ class File extends Node implements IFile { //detect aborted upload if (isset ($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'PUT') { if (isset($_SERVER['CONTENT_LENGTH'])) { - $expected = (int) $_SERVER['CONTENT_LENGTH']; + $expected = (int)$_SERVER['CONTENT_LENGTH']; if ($bytesWritten !== $expected) { $chunk_handler->remove($info['index']); throw new BadRequest( diff --git a/apps/dav/lib/Migration/Version1008Date20181030113700.php b/apps/dav/lib/Migration/Version1008Date20181030113700.php new file mode 100644 index 00000000000..1cc6223a9e9 --- /dev/null +++ b/apps/dav/lib/Migration/Version1008Date20181030113700.php @@ -0,0 +1,52 @@ +<?php +/** + * @copyright Copyright (c) 2018, John Molakvoæ (skjnldsv@protonmail.com) + * + * @author John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\DAV\Migration; + +use Closure; + +use Doctrine\DBAL\Types\Type; +use OCP\DB\ISchemaWrapper; +use OCP\Migration\SimpleMigrationStep; +use OCP\Migration\IOutput; + +/** + * add column for share notes + * + * Class Version15000Date20180927120000 + */ +class Version1008Date20181030113700 extends SimpleMigrationStep { + public function changeSchema(IOutput $output, Closure $schemaClosure, array $options) { + + /** @var ISchemaWrapper $schema */ + $schema = $schemaClosure(); + + $table = $schema->getTable('cards'); + $table->addColumn('uid', Type::STRING, [ + 'notnull' => false, + 'length' => 255 + ]); + + return $schema; + } +} diff --git a/apps/dav/tests/unit/CardDAV/BirthdayServiceTest.php b/apps/dav/tests/unit/CardDAV/BirthdayServiceTest.php index 739b81ddf01..651fbf5eaf8 100644 --- a/apps/dav/tests/unit/CardDAV/BirthdayServiceTest.php +++ b/apps/dav/tests/unit/CardDAV/BirthdayServiceTest.php @@ -231,9 +231,9 @@ class BirthdayServiceTest extends TestCase { if ($expectedOp === 'create') { $service->expects($this->exactly(3))->method('buildDateFromContact')->willReturn(new VCalendar()); $this->calDav->expects($this->exactly(3))->method('createCalendarObject')->withConsecutive( - [1234, 'default-gump.vcf.ics', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.2//EN\r\nCALSCALE:GREGORIAN\r\nEND:VCALENDAR\r\n"], - [1234, 'default-gump.vcf-death.ics', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.2//EN\r\nCALSCALE:GREGORIAN\r\nEND:VCALENDAR\r\n"], - [1234, 'default-gump.vcf-anniversary.ics', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.2//EN\r\nCALSCALE:GREGORIAN\r\nEND:VCALENDAR\r\n"] + [1234, 'default-gump.vcf.ics', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.6//EN\r\nCALSCALE:GREGORIAN\r\nEND:VCALENDAR\r\n"], + [1234, 'default-gump.vcf-death.ics', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.6//EN\r\nCALSCALE:GREGORIAN\r\nEND:VCALENDAR\r\n"], + [1234, 'default-gump.vcf-anniversary.ics', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.6//EN\r\nCALSCALE:GREGORIAN\r\nEND:VCALENDAR\r\n"] ); } if ($expectedOp === 'update') { @@ -241,9 +241,9 @@ class BirthdayServiceTest extends TestCase { $service->expects($this->exactly(3))->method('birthdayEvenChanged')->willReturn(true); $this->calDav->expects($this->exactly(3))->method('getCalendarObject')->willReturn(['calendardata' => '']); $this->calDav->expects($this->exactly(3))->method('updateCalendarObject')->withConsecutive( - [1234, 'default-gump.vcf.ics', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.2//EN\r\nCALSCALE:GREGORIAN\r\nEND:VCALENDAR\r\n"], - [1234, 'default-gump.vcf-death.ics', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.2//EN\r\nCALSCALE:GREGORIAN\r\nEND:VCALENDAR\r\n"], - [1234, 'default-gump.vcf-anniversary.ics', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.2//EN\r\nCALSCALE:GREGORIAN\r\nEND:VCALENDAR\r\n"] + [1234, 'default-gump.vcf.ics', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.6//EN\r\nCALSCALE:GREGORIAN\r\nEND:VCALENDAR\r\n"], + [1234, 'default-gump.vcf-death.ics', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.6//EN\r\nCALSCALE:GREGORIAN\r\nEND:VCALENDAR\r\n"], + [1234, 'default-gump.vcf-anniversary.ics', "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Sabre//Sabre VObject 4.1.6//EN\r\nCALSCALE:GREGORIAN\r\nEND:VCALENDAR\r\n"] ); } diff --git a/apps/dav/tests/unit/CardDAV/CardDavBackendTest.php b/apps/dav/tests/unit/CardDAV/CardDavBackendTest.php index 816ba670990..2f5287df82c 100644 --- a/apps/dav/tests/unit/CardDAV/CardDavBackendTest.php +++ b/apps/dav/tests/unit/CardDAV/CardDavBackendTest.php @@ -87,6 +87,14 @@ class CardDavBackendTest extends TestCase { const UNIT_TEST_USER1 = 'principals/users/carddav-unit-test1'; const UNIT_TEST_GROUP = 'principals/groups/carddav-unit-test-group'; + private $vcardTest = 'BEGIN:VCARD'.PHP_EOL. + 'VERSION:3.0'.PHP_EOL. + 'PRODID:-//Sabre//Sabre VObject 4.1.2//EN'.PHP_EOL. + 'UID:Test'.PHP_EOL. + 'FN:Test'.PHP_EOL. + 'N:Test;;;;'.PHP_EOL. + 'END:VCARD'; + public function setUp() { parent::setUp(); @@ -121,7 +129,6 @@ class CardDavBackendTest extends TestCase { $query = $this->db->getQueryBuilder(); $query->delete('cards')->execute(); - $this->tearDown(); } @@ -217,8 +224,8 @@ class CardDavBackendTest extends TestCase { $uri = $this->getUniqueID('card'); // updateProperties is expected twice, once for createCard and once for updateCard - $backend->expects($this->at(0))->method('updateProperties')->with($bookId, $uri, ''); - $backend->expects($this->at(1))->method('updateProperties')->with($bookId, $uri, '***'); + $backend->expects($this->at(0))->method('updateProperties')->with($bookId, $uri, $this->vcardTest); + $backend->expects($this->at(1))->method('updateProperties')->with($bookId, $uri, $this->vcardTest); // Expect event $this->dispatcher->expects($this->at(0)) @@ -226,16 +233,16 @@ class CardDavBackendTest extends TestCase { ->with('\OCA\DAV\CardDAV\CardDavBackend::createCard', $this->callback(function(GenericEvent $e) use ($bookId, $uri) { return $e->getArgument('addressBookId') === $bookId && $e->getArgument('cardUri') === $uri && - $e->getArgument('cardData') === ''; + $e->getArgument('cardData') === $this->vcardTest; })); // create a card - $backend->createCard($bookId, $uri, ''); + $backend->createCard($bookId, $uri, $this->vcardTest); // get all the cards $cards = $backend->getCards($bookId); $this->assertEquals(1, count($cards)); - $this->assertEquals('', $cards[0]['carddata']); + $this->assertEquals($this->vcardTest, $cards[0]['carddata']); // get the cards $card = $backend->getCard($bookId, $uri); @@ -245,7 +252,7 @@ class CardDavBackendTest extends TestCase { $this->assertArrayHasKey('lastmodified', $card); $this->assertArrayHasKey('etag', $card); $this->assertArrayHasKey('size', $card); - $this->assertEquals('', $card['carddata']); + $this->assertEquals($this->vcardTest, $card['carddata']); // Expect event $this->dispatcher->expects($this->at(0)) @@ -253,13 +260,13 @@ class CardDavBackendTest extends TestCase { ->with('\OCA\DAV\CardDAV\CardDavBackend::updateCard', $this->callback(function(GenericEvent $e) use ($bookId, $uri) { return $e->getArgument('addressBookId') === $bookId && $e->getArgument('cardUri') === $uri && - $e->getArgument('cardData') === '***'; + $e->getArgument('cardData') === $this->vcardTest; })); // update the card - $backend->updateCard($bookId, $uri, '***'); + $backend->updateCard($bookId, $uri, $this->vcardTest); $card = $backend->getCard($bookId, $uri); - $this->assertEquals('***', $card['carddata']); + $this->assertEquals($this->vcardTest, $card['carddata']); // Expect event $this->dispatcher->expects($this->at(0)) @@ -290,18 +297,18 @@ class CardDavBackendTest extends TestCase { // create a card $uri0 = $this->getUniqueID('card'); - $this->backend->createCard($bookId, $uri0, ''); + $this->backend->createCard($bookId, $uri0, $this->vcardTest); $uri1 = $this->getUniqueID('card'); - $this->backend->createCard($bookId, $uri1, ''); + $this->backend->createCard($bookId, $uri1, $this->vcardTest); $uri2 = $this->getUniqueID('card'); - $this->backend->createCard($bookId, $uri2, ''); + $this->backend->createCard($bookId, $uri2, $this->vcardTest); // get all the cards $cards = $this->backend->getCards($bookId); $this->assertEquals(3, count($cards)); - $this->assertEquals('', $cards[0]['carddata']); - $this->assertEquals('', $cards[1]['carddata']); - $this->assertEquals('', $cards[2]['carddata']); + $this->assertEquals($this->vcardTest, $cards[0]['carddata']); + $this->assertEquals($this->vcardTest, $cards[1]['carddata']); + $this->assertEquals($this->vcardTest, $cards[2]['carddata']); // get the cards $cards = $this->backend->getMultipleCards($bookId, [$uri1, $uri2]); @@ -312,7 +319,7 @@ class CardDavBackendTest extends TestCase { $this->assertArrayHasKey('lastmodified', $card); $this->assertArrayHasKey('etag', $card); $this->assertArrayHasKey('size', $card); - $this->assertEquals('', $card['carddata']); + $this->assertEquals($this->vcardTest, $card['carddata']); } // delete the card @@ -357,7 +364,7 @@ class CardDavBackendTest extends TestCase { ->method('purgeProperties'); // create a card - $this->backend->createCard($bookId, $uri, ''); + $this->backend->createCard($bookId, $uri, $this->vcardTest); // delete the card $this->assertTrue($this->backend->deleteCard($bookId, $uri)); @@ -380,7 +387,7 @@ class CardDavBackendTest extends TestCase { // add a change $uri0 = $this->getUniqueID('card'); - $this->backend->createCard($bookId, $uri0, ''); + $this->backend->createCard($bookId, $uri0, $this->vcardTest); // look for changes $changes = $this->backend->getChangesForAddressBook($bookId, $syncToken, 1); @@ -683,7 +690,7 @@ class CardDavBackendTest extends TestCase { } $result = $this->backend->getContact(0, 'uri0'); - $this->assertSame(7, count($result)); + $this->assertSame(8, count($result)); $this->assertSame(0, (int)$result['addressbookid']); $this->assertSame('uri0', $result['uri']); $this->assertSame(5489543, (int)$result['lastmodified']); diff --git a/apps/dav/tests/unit/Connector/Sabre/FileTest.php b/apps/dav/tests/unit/Connector/Sabre/FileTest.php index 5e7a6374206..edb61edc6ed 100644 --- a/apps/dav/tests/unit/Connector/Sabre/FileTest.php +++ b/apps/dav/tests/unit/Connector/Sabre/FileTest.php @@ -164,7 +164,7 @@ class FileTest extends \Test\TestCase { public function testSimplePutFails($thrownException, $expectedException, $checkPreviousClass = true) { // setup $storage = $this->getMockBuilder(Local::class) - ->setMethods(['fopen']) + ->setMethods(['writeStream']) ->setConstructorArgs([['datadir' => \OC::$server->getTempManager()->getTemporaryFolder()]]) ->getMock(); \OC\Files\Filesystem::mount($storage, [], $this->user . '/'); @@ -182,11 +182,11 @@ class FileTest extends \Test\TestCase { if ($thrownException !== null) { $storage->expects($this->once()) - ->method('fopen') + ->method('writeStream') ->will($this->throwException($thrownException)); } else { $storage->expects($this->once()) - ->method('fopen') + ->method('writeStream') ->will($this->returnValue(false)); } diff --git a/apps/federatedfilesharing/l10n/pl.js b/apps/federatedfilesharing/l10n/pl.js index a8ce17ef8e9..d60b590dd48 100644 --- a/apps/federatedfilesharing/l10n/pl.js +++ b/apps/federatedfilesharing/l10n/pl.js @@ -17,8 +17,12 @@ OC.L10N.register( "Couldn't establish a federated share." : "Nie udało się ustalić Stowarzyszonego udostępnienia.", "Couldn't establish a federated share, maybe the password was wrong." : "Nie udało się ustalić Stowarzyszonego udostępnienia, może być błędne hasło.", "Federated Share request sent, you will receive an invitation. Check your notifications." : "Wysłano żądanie Udostępniania Stowarzyszonego, otrzymasz zaproszenie. Sprawdzaj swoje powiadomienia.", + "Couldn't establish a federated share, it looks like the server to federate with is too old (Nextcloud <= 9)." : "Nie można ustanowić stowarzyszonego udziału, wygląda na to, że serwer wybrany do stowarzyszenia jest zbyt stary (Nextcloud <= 9).", + "It is not allowed to send federated group shares from this server." : "Wysyłanie stowarzyszonych udziałów grupowych z tego serwera jest zabronione.", + "Sharing %1$s failed, because this item is already shared with %2$s" : "Udostępnianie %1$snie powiodło się, ponieważ ten element jest już udostępniony jako %2$s", "Not allowed to create a federated share with the same user" : "Nie można tworzyć stowarzyszonego udziału z tym samym użytkownikiem", "File is already shared with %s" : "Plik jest już współdzielony z %s", + "Sharing %1$s failed, could not find %2$s, maybe the server is currently unreachable or uses a self-signed certificate." : "Udostępnianie %1$s nie powiodło się, nie można odnaleźć %2$s, być może serwer jest nieosiągalny lub używa certyfikatu z podpisem własnym.", "Could not find share" : "Nie można znaleźć powiązania", "You received \"%3$s\" as a remote share from %1$s (on behalf of %2$s)" : "Otrzymałeś \"%3$s\" w zdalnym udziale z %1$s (w imieniu %2$s)", "You received {share} as a remote share from {user} (on behalf of {behalf})" : "Otrzymałeś {share} w zdalnym udziale z {user} (w imieniu {behalf})", @@ -29,11 +33,15 @@ OC.L10N.register( "Share with me through my #Nextcloud Federated Cloud ID, see %s" : "Podziel się ze mną przez mój ID #Nextcloud Stowarzyszonej Chmury, zobacz %s", "Share with me through my #Nextcloud Federated Cloud ID" : "Podziel się ze mną przez mój ID #Nextcloud Stowarzyszonej Chmury", "Sharing" : "Udostępnianie", + "Federated file sharing" : "Stowarzyszone udziały grupowe", + "Provide federated file sharing across servers" : "Zezwól na stowarzyszone udostępnianie plików na serwerach", "Federated Cloud Sharing" : "Dzielenie się ze Stowarzyszoną Chmurą", "Open documentation" : "Otwórz dokumentację", "Adjust how people can share between servers." : "Dostosuj ustawienia współdzielenia między serwerami.", "Allow users on this server to send shares to other servers" : "Zezwalaj użytkownikom na tym serwerze wysłać udostępnienia do innych serwerów", "Allow users on this server to receive shares from other servers" : "Zezwalaj użytkownikom na tym serwerze do otrzymania udostępnień z innych serwerów", + "Allow users on this server to send shares to groups on other servers" : "Pozwól użytkownikom na tym serwerze na wysyłanie udziałów do grup na innych serwerach", + "Allow users on this server to receive group shares from other servers" : "Pozwól użytkownikom na tym serwerze na odbieranie udziałów grupy z innych serwerów", "Search global and public address book for users" : "Szukaj użytkowników w globalnej i publicznej książce adresowej", "Allow users to publish their data to a global and public address book" : "Pozwól użytkownikom na publikację ich danych do globalnej i publicznej książki adresowej", "Federated Cloud" : "Stowarzyszona Chmura", diff --git a/apps/federatedfilesharing/l10n/pl.json b/apps/federatedfilesharing/l10n/pl.json index b7191f76892..745a1faf504 100644 --- a/apps/federatedfilesharing/l10n/pl.json +++ b/apps/federatedfilesharing/l10n/pl.json @@ -15,8 +15,12 @@ "Couldn't establish a federated share." : "Nie udało się ustalić Stowarzyszonego udostępnienia.", "Couldn't establish a federated share, maybe the password was wrong." : "Nie udało się ustalić Stowarzyszonego udostępnienia, może być błędne hasło.", "Federated Share request sent, you will receive an invitation. Check your notifications." : "Wysłano żądanie Udostępniania Stowarzyszonego, otrzymasz zaproszenie. Sprawdzaj swoje powiadomienia.", + "Couldn't establish a federated share, it looks like the server to federate with is too old (Nextcloud <= 9)." : "Nie można ustanowić stowarzyszonego udziału, wygląda na to, że serwer wybrany do stowarzyszenia jest zbyt stary (Nextcloud <= 9).", + "It is not allowed to send federated group shares from this server." : "Wysyłanie stowarzyszonych udziałów grupowych z tego serwera jest zabronione.", + "Sharing %1$s failed, because this item is already shared with %2$s" : "Udostępnianie %1$snie powiodło się, ponieważ ten element jest już udostępniony jako %2$s", "Not allowed to create a federated share with the same user" : "Nie można tworzyć stowarzyszonego udziału z tym samym użytkownikiem", "File is already shared with %s" : "Plik jest już współdzielony z %s", + "Sharing %1$s failed, could not find %2$s, maybe the server is currently unreachable or uses a self-signed certificate." : "Udostępnianie %1$s nie powiodło się, nie można odnaleźć %2$s, być może serwer jest nieosiągalny lub używa certyfikatu z podpisem własnym.", "Could not find share" : "Nie można znaleźć powiązania", "You received \"%3$s\" as a remote share from %1$s (on behalf of %2$s)" : "Otrzymałeś \"%3$s\" w zdalnym udziale z %1$s (w imieniu %2$s)", "You received {share} as a remote share from {user} (on behalf of {behalf})" : "Otrzymałeś {share} w zdalnym udziale z {user} (w imieniu {behalf})", @@ -27,11 +31,15 @@ "Share with me through my #Nextcloud Federated Cloud ID, see %s" : "Podziel się ze mną przez mój ID #Nextcloud Stowarzyszonej Chmury, zobacz %s", "Share with me through my #Nextcloud Federated Cloud ID" : "Podziel się ze mną przez mój ID #Nextcloud Stowarzyszonej Chmury", "Sharing" : "Udostępnianie", + "Federated file sharing" : "Stowarzyszone udziały grupowe", + "Provide federated file sharing across servers" : "Zezwól na stowarzyszone udostępnianie plików na serwerach", "Federated Cloud Sharing" : "Dzielenie się ze Stowarzyszoną Chmurą", "Open documentation" : "Otwórz dokumentację", "Adjust how people can share between servers." : "Dostosuj ustawienia współdzielenia między serwerami.", "Allow users on this server to send shares to other servers" : "Zezwalaj użytkownikom na tym serwerze wysłać udostępnienia do innych serwerów", "Allow users on this server to receive shares from other servers" : "Zezwalaj użytkownikom na tym serwerze do otrzymania udostępnień z innych serwerów", + "Allow users on this server to send shares to groups on other servers" : "Pozwól użytkownikom na tym serwerze na wysyłanie udziałów do grup na innych serwerach", + "Allow users on this server to receive group shares from other servers" : "Pozwól użytkownikom na tym serwerze na odbieranie udziałów grupy z innych serwerów", "Search global and public address book for users" : "Szukaj użytkowników w globalnej i publicznej książce adresowej", "Allow users to publish their data to a global and public address book" : "Pozwól użytkownikom na publikację ich danych do globalnej i publicznej książki adresowej", "Federated Cloud" : "Stowarzyszona Chmura", diff --git a/apps/files/css/detailsView.scss b/apps/files/css/detailsView.scss index f64a3702850..71062648c97 100644 --- a/apps/files/css/detailsView.scss +++ b/apps/files/css/detailsView.scss @@ -15,7 +15,13 @@ #app-sidebar .mainFileInfoView .permalink { padding: 6px 10px; - vertical-align: text-top; + vertical-align: top; + opacity: .6; + + &:hover, + &:focus { + opacity: 1; + } } #app-sidebar .mainFileInfoView .permalink-field>input { clear: both; @@ -87,7 +93,7 @@ } #app-sidebar .fileName h3 { - width: calc(100% - 36px); /* 36px is the with of the copy link icon */ + width: calc(100% - 42px); /* 36px is the with of the copy link icon, but this breaks so we add some more to be sure */ display: inline-block; padding: 5px 0; margin: -5px 0; diff --git a/apps/files/css/files.scss b/apps/files/css/files.scss index 4e0bc9c0160..d6f9bd6131e 100644 --- a/apps/files/css/files.scss +++ b/apps/files/css/files.scss @@ -8,7 +8,12 @@ } /* FILE MENU */ -.actions { padding:5px; height:32px; display: inline-block; float: left; } +.actions { + padding: 5px; + height: 100%; + display: inline-block; + float: left; +} .actions input, .actions button, .actions .button { margin:0; float:left; } .actions .button a { color: #555; } .actions .button a:hover, @@ -316,6 +321,7 @@ table td.filename .thumbnail { background-size: 32px; margin-left: 9px; margin-top: 9px; + border-radius: var(--border-radius); cursor: pointer; position: absolute; z-index: 4; @@ -658,8 +664,14 @@ table.dragshadow td.size { top: 100%; margin-top: 4px; min-width: 100px; - margin-left: 7px; + margin-left: 22px; /* Align left edge below center of + button … */ + transform: translateX(-50%); /* … then center it below button */ z-index: 1001; + + /* Center triangle */ + &::after { + left: calc(50% - 8px) !important; + } } #filestable .filename .action .icon, diff --git a/apps/files/js/detailsview.js b/apps/files/js/detailsview.js index cd602961c0a..bac2a5ebd21 100644 --- a/apps/files/js/detailsview.js +++ b/apps/files/js/detailsview.js @@ -174,6 +174,9 @@ // hide other tabs $tabsContainer.find('.tab').addClass('hidden'); + $tabsContainer.attr('class', 'tabsContainer'); + $tabsContainer.addClass(tabView.getTabsContainerExtraClasses()); + // tab already rendered ? if (!$tabEl.length) { // render tab diff --git a/apps/files/js/detailtabview.js b/apps/files/js/detailtabview.js index a66cedbc15d..1e046f30246 100644 --- a/apps/files/js/detailtabview.js +++ b/apps/files/js/detailtabview.js @@ -41,6 +41,21 @@ }, /** + * Returns the extra CSS classes used by the tabs container when this + * tab is the selected one. + * + * In general you should not extend this method, as tabs should not + * modify the classes of its container; this is reserved as a last + * resort for very specific cases in which there is no other way to get + * the proper style or behaviour. + * + * @return {String} space-separated CSS classes + */ + getTabsContainerExtraClasses: function() { + return ''; + }, + + /** * Returns the tab label * * @return {String} label diff --git a/apps/files/l10n/cs.js b/apps/files/l10n/cs.js index cbd6c72d572..96b93d361e7 100644 --- a/apps/files/l10n/cs.js +++ b/apps/files/l10n/cs.js @@ -142,6 +142,7 @@ OC.L10N.register( "WebDAV" : "WebDAV", "Use this address to <a href=\"%s\" target=\"_blank\" rel=\"noreferrer noopener\">access your Files via WebDAV</a>" : "Použijte tuto adresu pro <a href=\"%s\" target=\"_blank\" rel=\"noreferrer noopener\">přístup k vašim souborům přes WebDAV</a>", "Cancel upload" : "Zrušit nahrávání", + "Toggle grid view" : "Přepnout zobrazení mřížky", "No files in here" : "Žádné soubory", "Upload some content or sync with your devices!" : "Nahrajte nějaký obsah nebo synchronizujte se svými přístroji!", "No entries found in this folder" : "V této složce nebylo nic nalezeno", diff --git a/apps/files/l10n/cs.json b/apps/files/l10n/cs.json index a8f82c5c5d7..cb01165503d 100644 --- a/apps/files/l10n/cs.json +++ b/apps/files/l10n/cs.json @@ -140,6 +140,7 @@ "WebDAV" : "WebDAV", "Use this address to <a href=\"%s\" target=\"_blank\" rel=\"noreferrer noopener\">access your Files via WebDAV</a>" : "Použijte tuto adresu pro <a href=\"%s\" target=\"_blank\" rel=\"noreferrer noopener\">přístup k vašim souborům přes WebDAV</a>", "Cancel upload" : "Zrušit nahrávání", + "Toggle grid view" : "Přepnout zobrazení mřížky", "No files in here" : "Žádné soubory", "Upload some content or sync with your devices!" : "Nahrajte nějaký obsah nebo synchronizujte se svými přístroji!", "No entries found in this folder" : "V této složce nebylo nic nalezeno", diff --git a/apps/files_external/js/app.js b/apps/files_external/js/app.js index d3f738dcf8a..4406882cd21 100644 --- a/apps/files_external/js/app.js +++ b/apps/files_external/js/app.js @@ -8,16 +8,16 @@ * */ -if (!OCA.External) { +if (!OCA.Files_External) { /** * @namespace */ - OCA.External = {}; + OCA.Files_External = {}; } /** * @namespace */ -OCA.External.App = { +OCA.Files_External.App = { fileList: null, @@ -26,7 +26,7 @@ OCA.External.App = { return this.fileList; } - this.fileList = new OCA.External.FileList( + this.fileList = new OCA.Files_External.FileList( $el, { fileActions: this._createFileActions() @@ -67,10 +67,10 @@ OCA.External.App = { $(document).ready(function() { $('#app-content-extstoragemounts').on('show', function(e) { - OCA.External.App.initList($(e.target)); + OCA.Files_External.App.initList($(e.target)); }); $('#app-content-extstoragemounts').on('hide', function() { - OCA.External.App.removeList(); + OCA.Files_External.App.removeList(); }); /* Status Manager */ @@ -82,27 +82,27 @@ $(document).ready(function() { if (e.dir === '/') { var mount_point = e.previousDir.split('/', 2)[1]; // Every time that we return to / root folder from a mountpoint, mount_point status is rechecked - OCA.External.StatusManager.getMountPointList(function() { - OCA.External.StatusManager.recheckConnectivityForMount([mount_point], true); + OCA.Files_External.StatusManager.getMountPointList(function() { + OCA.Files_External.StatusManager.recheckConnectivityForMount([mount_point], true); }); } }) .on('fileActionsReady', function(e){ if ($.isArray(e.$files)) { - if (OCA.External.StatusManager.mountStatus === null || - OCA.External.StatusManager.mountPointList === null || - _.size(OCA.External.StatusManager.mountStatus) !== _.size(OCA.External.StatusManager.mountPointList)) { + if (OCA.Files_External.StatusManager.mountStatus === null || + OCA.Files_External.StatusManager.mountPointList === null || + _.size(OCA.Files_External.StatusManager.mountStatus) !== _.size(OCA.Files_External.StatusManager.mountPointList)) { // Will be the very first check when the files view will be loaded - OCA.External.StatusManager.launchFullConnectivityCheckOneByOne(); + OCA.Files_External.StatusManager.launchFullConnectivityCheckOneByOne(); } else { // When we change between general files view and external files view - OCA.External.StatusManager.getMountPointList(function(){ + OCA.Files_External.StatusManager.getMountPointList(function(){ var fileNames = []; $.each(e.$files, function(key, value){ fileNames.push(value.attr('data-file')); }); // Recheck if launched but work from cache - OCA.External.StatusManager.recheckConnectivityForMount(fileNames, false); + OCA.Files_External.StatusManager.recheckConnectivityForMount(fileNames, false); }); } } diff --git a/apps/files_external/js/mountsfilelist.js b/apps/files_external/js/mountsfilelist.js index 90b90e38745..034c29c05c2 100644 --- a/apps/files_external/js/mountsfilelist.js +++ b/apps/files_external/js/mountsfilelist.js @@ -10,7 +10,7 @@ (function() { /** - * @class OCA.External.FileList + * @class OCA.Files_External.FileList * @augments OCA.Files.FileList * * @classdesc External storage file list. @@ -27,7 +27,7 @@ }; FileList.prototype = _.extend({}, OCA.Files.FileList.prototype, - /** @lends OCA.External.FileList.prototype */ { + /** @lends OCA.Files_External.FileList.prototype */ { appName: 'External storages', _allowSelection: false, @@ -43,7 +43,7 @@ }, /** - * @param {OCA.External.MountPointInfo} fileData + * @param {OCA.Files_External.MountPointInfo} fileData */ _createRow: function(fileData) { // TODO: hook earlier and render the whole row here @@ -138,12 +138,12 @@ /** * Mount point info attributes. * - * @typedef {Object} OCA.External.MountPointInfo + * @typedef {Object} OCA.Files_External.MountPointInfo * * @property {String} name mount point name * @property {String} scope mount point scope "personal" or "system" * @property {String} backend external storage backend name */ - OCA.External.FileList = FileList; + OCA.Files_External.FileList = FileList; })(); diff --git a/apps/files_external/js/oauth1.js b/apps/files_external/js/oauth1.js index 79248a3e3b2..56e674b213b 100644 --- a/apps/files_external/js/oauth1.js +++ b/apps/files_external/js/oauth1.js @@ -4,7 +4,7 @@ $(document).ready(function() { $tr.find('.configuration input.auth-param').attr('disabled', 'disabled').addClass('disabled-success'); } - OCA.External.Settings.mountConfig.whenSelectAuthMechanism(function($tr, authMechanism, scheme, onCompletion) { + OCA.Files_External.Settings.mountConfig.whenSelectAuthMechanism(function($tr, authMechanism, scheme, onCompletion) { if (authMechanism === 'oauth1::oauth1') { var config = $tr.find('.configuration'); config.append($(document.createElement('input')) @@ -34,7 +34,7 @@ $(document).ready(function() { $(token).val(result.access_token); $(token_secret).val(result.access_token_secret); $(configured).val('true'); - OCA.External.Settings.mountConfig.saveStorageConfig($tr, function(status) { + OCA.Files_External.Settings.mountConfig.saveStorageConfig($tr, function(status) { if (status) { displayGranted($tr); } @@ -64,7 +64,7 @@ $(document).ready(function() { $(configured).val('false'); $(token).val(result.data.request_token); $(token_secret).val(result.data.request_token_secret); - OCA.External.Settings.mountConfig.saveStorageConfig(tr, function() { + OCA.Files_External.Settings.mountConfig.saveStorageConfig(tr, function() { window.location = result.data.url; }); } else { diff --git a/apps/files_external/js/oauth2.js b/apps/files_external/js/oauth2.js index 13b5162694e..fb7160d6684 100644 --- a/apps/files_external/js/oauth2.js +++ b/apps/files_external/js/oauth2.js @@ -4,7 +4,7 @@ $(document).ready(function() { $tr.find('.configuration input.auth-param').attr('disabled', 'disabled').addClass('disabled-success'); } - OCA.External.Settings.mountConfig.whenSelectAuthMechanism(function($tr, authMechanism, scheme, onCompletion) { + OCA.Files_External.Settings.mountConfig.whenSelectAuthMechanism(function($tr, authMechanism, scheme, onCompletion) { if (authMechanism === 'oauth2::oauth2') { var config = $tr.find('.configuration'); config.append($(document.createElement('input')) @@ -43,7 +43,7 @@ $(document).ready(function() { if (result && result.status == 'success') { $(token).val(result.data.token); $(configured).val('true'); - OCA.External.Settings.mountConfig.saveStorageConfig($tr, function(status) { + OCA.Files_External.Settings.mountConfig.saveStorageConfig($tr, function(status) { if (status) { displayGranted($tr); } @@ -80,7 +80,7 @@ $(document).ready(function() { if (result && result.status == 'success') { $(configured).val('false'); $(token).val('false'); - OCA.External.Settings.mountConfig.saveStorageConfig(tr, function(status) { + OCA.Files_External.Settings.mountConfig.saveStorageConfig(tr, function(status) { window.location = result.data.url; }); } else { diff --git a/apps/files_external/js/public_key.js b/apps/files_external/js/public_key.js index 6856c7d021f..9b9ca6038c8 100644 --- a/apps/files_external/js/public_key.js +++ b/apps/files_external/js/public_key.js @@ -1,6 +1,6 @@ $(document).ready(function() { - OCA.External.Settings.mountConfig.whenSelectAuthMechanism(function($tr, authMechanism, scheme, onCompletion) { + OCA.Files_External.Settings.mountConfig.whenSelectAuthMechanism(function($tr, authMechanism, scheme, onCompletion) { if (scheme === 'publickey' && authMechanism === 'publickey::rsa') { var config = $tr.find('.configuration'); if ($(config).find('[name="public_key_generate"]').length === 0) { @@ -53,7 +53,7 @@ $(document).ready(function() { if (result && result.status === 'success') { $(config).find('[data-parameter="public_key"]').val(result.data.public_key).keyup(); $(config).find('[data-parameter="private_key"]').val(result.data.private_key); - OCA.External.Settings.mountConfig.saveStorageConfig(tr, function() { + OCA.Files_External.Settings.mountConfig.saveStorageConfig(tr, function() { // Nothing to do }); } else { diff --git a/apps/files_external/js/rollingqueue.js b/apps/files_external/js/rollingqueue.js index 53e11cb1219..df3797ada89 100644 --- a/apps/files_external/js/rollingqueue.js +++ b/apps/files_external/js/rollingqueue.js @@ -124,14 +124,14 @@ var RollingQueue = function (functionList, queueWindow, callback) { }; }; -if (!OCA.External) { - OCA.External = {}; +if (!OCA.Files_External) { + OCA.Files_External = {}; } -if (!OCA.External.StatusManager) { - OCA.External.StatusManager = {}; +if (!OCA.Files_External.StatusManager) { + OCA.Files_External.StatusManager = {}; } -OCA.External.StatusManager.RollingQueue = RollingQueue; +OCA.Files_External.StatusManager.RollingQueue = RollingQueue; })(); diff --git a/apps/files_external/js/settings.js b/apps/files_external/js/settings.js index 2d495281527..adf3f766202 100644 --- a/apps/files_external/js/settings.js +++ b/apps/files_external/js/settings.js @@ -163,7 +163,7 @@ function addSelect2 ($elements, userListLimit) { } /** - * @class OCA.External.Settings.StorageConfig + * @class OCA.Files_External.Settings.StorageConfig * * @classdesc External storage config */ @@ -185,7 +185,7 @@ StorageConfig.Visibility = { DEFAULT: 3 }; /** - * @memberof OCA.External.Settings + * @memberof OCA.Files_External.Settings */ StorageConfig.prototype = { _url: null, @@ -348,8 +348,8 @@ StorageConfig.prototype = { }; /** - * @class OCA.External.Settings.GlobalStorageConfig - * @augments OCA.External.Settings.StorageConfig + * @class OCA.Files_External.Settings.GlobalStorageConfig + * @augments OCA.Files_External.Settings.StorageConfig * * @classdesc Global external storage config */ @@ -359,10 +359,10 @@ var GlobalStorageConfig = function(id) { this.applicableGroups = []; }; /** - * @memberOf OCA.External.Settings + * @memberOf OCA.Files_External.Settings */ GlobalStorageConfig.prototype = _.extend({}, StorageConfig.prototype, - /** @lends OCA.External.Settings.GlobalStorageConfig.prototype */ { + /** @lends OCA.Files_External.Settings.GlobalStorageConfig.prototype */ { _url: 'apps/files_external/globalstorages', /** @@ -402,8 +402,8 @@ GlobalStorageConfig.prototype = _.extend({}, StorageConfig.prototype, }); /** - * @class OCA.External.Settings.UserStorageConfig - * @augments OCA.External.Settings.StorageConfig + * @class OCA.Files_External.Settings.UserStorageConfig + * @augments OCA.Files_External.Settings.StorageConfig * * @classdesc User external storage config */ @@ -411,13 +411,13 @@ var UserStorageConfig = function(id) { this.id = id; }; UserStorageConfig.prototype = _.extend({}, StorageConfig.prototype, - /** @lends OCA.External.Settings.UserStorageConfig.prototype */ { + /** @lends OCA.Files_External.Settings.UserStorageConfig.prototype */ { _url: 'apps/files_external/userstorages' }); /** - * @class OCA.External.Settings.UserGlobalStorageConfig - * @augments OCA.External.Settings.StorageConfig + * @class OCA.Files_External.Settings.UserGlobalStorageConfig + * @augments OCA.Files_External.Settings.StorageConfig * * @classdesc User external storage config */ @@ -425,13 +425,13 @@ var UserGlobalStorageConfig = function (id) { this.id = id; }; UserGlobalStorageConfig.prototype = _.extend({}, StorageConfig.prototype, - /** @lends OCA.External.Settings.UserStorageConfig.prototype */ { + /** @lends OCA.Files_External.Settings.UserStorageConfig.prototype */ { _url: 'apps/files_external/userglobalstorages' }); /** - * @class OCA.External.Settings.MountOptionsDropdown + * @class OCA.Files_External.Settings.MountOptionsDropdown * * @classdesc Dropdown for mount options * @@ -440,7 +440,7 @@ UserGlobalStorageConfig.prototype = _.extend({}, StorageConfig.prototype, var MountOptionsDropdown = function() { }; /** - * @memberof OCA.External.Settings + * @memberof OCA.Files_External.Settings */ MountOptionsDropdown.prototype = { /** @@ -462,7 +462,7 @@ MountOptionsDropdown.prototype = { MountOptionsDropdown._last.hide(); } - var $el = $(OCA.External.Templates.mountOptionsDropDown({ + var $el = $(OCA.Files_External.Templates.mountOptionsDropDown({ mountOptionsEncodingLabel: t('files_external', 'Compatibility with Mac NFD encoding (slow)'), mountOptionsEncryptLabel: t('files_external', 'Enable encryption'), mountOptionsPreviewsLabel: t('files_external', 'Enable previews'), @@ -549,7 +549,7 @@ MountOptionsDropdown.prototype = { }; /** - * @class OCA.External.Settings.MountConfigListView + * @class OCA.Files_External.Settings.MountConfigListView * * @classdesc Mount configuration list view * @@ -574,7 +574,7 @@ MountConfigListView.ParameterTypes = { }; /** - * @memberOf OCA.External.Settings + * @memberOf OCA.Files_External.Settings */ MountConfigListView.prototype = _.extend({ @@ -633,9 +633,9 @@ MountConfigListView.prototype = _.extend({ this.$el = $el; this._isPersonal = ($el.data('admin') !== true); if (this._isPersonal) { - this._storageConfigClass = OCA.External.Settings.UserStorageConfig; + this._storageConfigClass = OCA.Files_External.Settings.UserStorageConfig; } else { - this._storageConfigClass = OCA.External.Settings.GlobalStorageConfig; + this._storageConfigClass = OCA.Files_External.Settings.GlobalStorageConfig; } if (options && !_.isUndefined(options.userListLimit)) { @@ -1008,7 +1008,7 @@ MountConfigListView.prototype = _.extend({ * Gets the storage model from the given row * * @param $tr row element - * @return {OCA.External.StorageConfig} storage model instance + * @return {OCA.Files_External.StorageConfig} storage model instance */ getStorageConfig: function($tr) { var storageId = $tr.data('id'); @@ -1367,13 +1367,13 @@ $(document).ready(function() { }); // global instance - OCA.External.Settings.mountConfig = mountConfigListView; + OCA.Files_External.Settings.mountConfig = mountConfigListView; /** * Legacy * * @namespace - * @deprecated use OCA.External.Settings.mountConfig instead + * @deprecated use OCA.Files_External.Settings.mountConfig instead */ OC.MountConfig = { saveStorage: _.bind(mountConfigListView.saveStorageConfig, mountConfigListView) @@ -1382,14 +1382,14 @@ $(document).ready(function() { // export -OCA.External = OCA.External || {}; +OCA.Files_External = OCA.Files_External || {}; /** * @namespace */ -OCA.External.Settings = OCA.External.Settings || {}; +OCA.Files_External.Settings = OCA.Files_External.Settings || {}; -OCA.External.Settings.GlobalStorageConfig = GlobalStorageConfig; -OCA.External.Settings.UserStorageConfig = UserStorageConfig; -OCA.External.Settings.MountConfigListView = MountConfigListView; +OCA.Files_External.Settings.GlobalStorageConfig = GlobalStorageConfig; +OCA.Files_External.Settings.UserStorageConfig = UserStorageConfig; +OCA.Files_External.Settings.MountConfigListView = MountConfigListView; })(); diff --git a/apps/files_external/js/statusmanager.js b/apps/files_external/js/statusmanager.js index b8b5e1a9364..b4e89bd6232 100644 --- a/apps/files_external/js/statusmanager.js +++ b/apps/files_external/js/statusmanager.js @@ -14,15 +14,15 @@ /** @global Handlebars */ -if (!OCA.External) { - OCA.External = {}; +if (!OCA.Files_External) { + OCA.Files_External = {}; } -if (!OCA.External.StatusManager) { - OCA.External.StatusManager = {}; +if (!OCA.Files_External.StatusManager) { + OCA.Files_External.StatusManager = {}; } -OCA.External.StatusManager = { +OCA.Files_External.StatusManager = { mountStatus: null, mountPointList: null, @@ -209,18 +209,18 @@ OCA.External.StatusManager = { var mountPoint = mountData.mount_point; if (mountStatus.status > 0) { - var trElement = FileList.findFileEl(OCA.External.StatusManager.Utils.jqSelEscape(mountPoint)); + var trElement = FileList.findFileEl(OCA.Files_External.StatusManager.Utils.jqSelEscape(mountPoint)); - var route = OCA.External.StatusManager.Utils.getIconRoute(trElement) + '-error'; + var route = OCA.Files_External.StatusManager.Utils.getIconRoute(trElement) + '-error'; - if (OCA.External.StatusManager.Utils.isCorrectViewAndRootFolder()) { - OCA.External.StatusManager.Utils.showIconError(mountPoint, $.proxy(OCA.External.StatusManager.manageMountPointError, OCA.External.StatusManager), route); + if (OCA.Files_External.StatusManager.Utils.isCorrectViewAndRootFolder()) { + OCA.Files_External.StatusManager.Utils.showIconError(mountPoint, $.proxy(OCA.Files_External.StatusManager.manageMountPointError, OCA.Files_External.StatusManager), route); } return false; } else { - if (OCA.External.StatusManager.Utils.isCorrectViewAndRootFolder()) { - OCA.External.StatusManager.Utils.restoreFolder(mountPoint); - OCA.External.StatusManager.Utils.toggleLink(mountPoint, true, true); + if (OCA.Files_External.StatusManager.Utils.isCorrectViewAndRootFolder()) { + OCA.Files_External.StatusManager.Utils.restoreFolder(mountPoint); + OCA.Files_External.StatusManager.Utils.toggleLink(mountPoint, true, true); } return true; } @@ -235,7 +235,7 @@ OCA.External.StatusManager = { processMountList: function (mountList) { var elementList = null; $.each(mountList, function (name, value) { - var trElement = $('#fileList tr[data-file=\"' + OCA.External.StatusManager.Utils.jqSelEscape(value.mount_point) + '\"]'); //FileList.findFileEl(OCA.External.StatusManager.Utils.jqSelEscape(value.mount_point)); + var trElement = $('#fileList tr[data-file=\"' + OCA.Files_External.StatusManager.Utils.jqSelEscape(value.mount_point) + '\"]'); //FileList.findFileEl(OCA.Files_External.StatusManager.Utils.jqSelEscape(value.mount_point)); trElement.attr('data-external-backend', value.backend); if (elementList) { elementList = elementList.add(trElement); @@ -245,14 +245,14 @@ OCA.External.StatusManager = { }); if (elementList instanceof $) { - if (OCA.External.StatusManager.Utils.isCorrectViewAndRootFolder()) { + if (OCA.Files_External.StatusManager.Utils.isCorrectViewAndRootFolder()) { // Put their custom icon - OCA.External.StatusManager.Utils.changeFolderIcon(elementList); + OCA.Files_External.StatusManager.Utils.changeFolderIcon(elementList); // Save default view - OCA.External.StatusManager.Utils.storeDefaultFolderIconAndBgcolor(elementList); + OCA.Files_External.StatusManager.Utils.storeDefaultFolderIconAndBgcolor(elementList); // Disable row until check status elementList.addClass('externalDisabledRow'); - OCA.External.StatusManager.Utils.toggleLink(elementList.find('a.name'), false, false); + OCA.Files_External.StatusManager.Utils.toggleLink(elementList.find('a.name'), false, false); } } }, @@ -289,7 +289,7 @@ OCA.External.StatusManager = { ajaxQueue.push(queueElement); }); - var rolQueue = new OCA.External.StatusManager.RollingQueue(ajaxQueue, 4, function () { + var rolQueue = new OCA.Files_External.StatusManager.RollingQueue(ajaxQueue, 4, function () { if (!self.notificationHasShown) { var showNotification = false; $.each(self.mountStatus, function (key, value) { @@ -335,7 +335,7 @@ OCA.External.StatusManager = { }; ajaxQueue.push(queueElement); }); - new OCA.External.StatusManager.RollingQueue(ajaxQueue, 4).runQueue(); + new OCA.Files_External.StatusManager.RollingQueue(ajaxQueue, 4).runQueue(); }, @@ -392,7 +392,7 @@ OCA.External.StatusManager = { * @param mountData */ showCredentialsDialog: function (mountPoint, mountData) { - var dialog = $(OCA.External.Templates.credentialsDialog({ + var dialog = $(OCA.Files_External.Templates.credentialsDialog({ credentials_text: t('files_external', 'Please enter the credentials for the {mount} mount', { 'mount': mountPoint }), @@ -422,7 +422,7 @@ OCA.External.StatusManager = { OC.Notification.show(t('files_external', 'Credentials saved'), {type: 'error'}); dialog.ocdialog('close'); /* Trigger status check again */ - OCA.External.StatusManager.recheckConnectivityForMount([OC.basename(data.mountPoint)], true); + OCA.Files_External.StatusManager.recheckConnectivityForMount([OC.basename(data.mountPoint)], true); }, error: function () { $('.oc-dialog-close').show(); @@ -461,11 +461,11 @@ OCA.External.StatusManager = { } }; -OCA.External.StatusManager.Utils = { +OCA.Files_External.StatusManager.Utils = { showIconError: function (folder, clickAction, errorImageUrl) { var imageUrl = "url(" + errorImageUrl + ")"; - var trFolder = $('#fileList tr[data-file=\"' + OCA.External.StatusManager.Utils.jqSelEscape(folder) + '\"]'); //FileList.findFileEl(OCA.External.StatusManager.Utils.jqSelEscape(folder)); + var trFolder = $('#fileList tr[data-file=\"' + OCA.Files_External.StatusManager.Utils.jqSelEscape(folder) + '\"]'); //FileList.findFileEl(OCA.Files_External.StatusManager.Utils.jqSelEscape(folder)); this.changeFolderIcon(folder, imageUrl); this.toggleLink(folder, false, clickAction); trFolder.addClass('externalErroredRow'); @@ -479,7 +479,7 @@ OCA.External.StatusManager.Utils = { if (folder instanceof $) { trFolder = folder; } else { - trFolder = $('#fileList tr[data-file=\"' + OCA.External.StatusManager.Utils.jqSelEscape(folder) + '\"]'); //FileList.findFileEl(OCA.External.StatusManager.Utils.jqSelEscape(folder)); //$('#fileList tr[data-file=\"' + OCA.External.StatusManager.Utils.jqSelEscape(folder) + '\"]'); + trFolder = $('#fileList tr[data-file=\"' + OCA.Files_External.StatusManager.Utils.jqSelEscape(folder) + '\"]'); //FileList.findFileEl(OCA.Files_External.StatusManager.Utils.jqSelEscape(folder)); //$('#fileList tr[data-file=\"' + OCA.Files_External.StatusManager.Utils.jqSelEscape(folder) + '\"]'); } trFolder.each(function () { var thisElement = $(this); @@ -505,8 +505,8 @@ OCA.External.StatusManager.Utils = { if (folder instanceof $) { trFolder = folder; } else { - // can't use here FileList.findFileEl(OCA.External.StatusManager.Utils.jqSelEscape(folder)); return incorrect instance of filelist - trFolder = $('#fileList tr[data-file=\"' + OCA.External.StatusManager.Utils.jqSelEscape(folder) + '\"]'); + // can't use here FileList.findFileEl(OCA.Files_External.StatusManager.Utils.jqSelEscape(folder)); return incorrect instance of filelist + trFolder = $('#fileList tr[data-file=\"' + OCA.Files_External.StatusManager.Utils.jqSelEscape(folder) + '\"]'); } trFolder.removeClass('externalErroredRow').removeClass('externalDisabledRow'); var tdChilds = trFolder.find("td.filename div.thumbnail"); @@ -526,14 +526,14 @@ OCA.External.StatusManager.Utils = { if (filename instanceof $) { //trElementList $.each(filename, function (index) { - route = OCA.External.StatusManager.Utils.getIconRoute($(this)); + route = OCA.Files_External.StatusManager.Utils.getIconRoute($(this)); $(this).attr("data-icon", route); $(this).find('td.filename div.thumbnail').css('background-image', "url(" + route + ")").css('display', 'none').css('display', 'inline'); }); } else { file = $("#fileList tr[data-file=\"" + this.jqSelEscape(filename) + "\"] > td.filename div.thumbnail"); var parentTr = file.parents('tr:first'); - route = OCA.External.StatusManager.Utils.getIconRoute(parentTr); + route = OCA.Files_External.StatusManager.Utils.getIconRoute(parentTr); parentTr.attr("data-icon", route); file.css('background-image', "url(" + route + ")").css('display', 'none').css('display', 'inline'); } diff --git a/apps/files_external/js/templates.js b/apps/files_external/js/templates.js index 067b3f5f5d7..cf1522334c5 100644 --- a/apps/files_external/js/templates.js +++ b/apps/files_external/js/templates.js @@ -1,5 +1,5 @@ (function() { - var template = Handlebars.template, templates = OCA.External.Templates = OCA.External.Templates || {}; + var template = Handlebars.template, templates = OCA.Files_External.Templates = OCA.Files_External.Templates || {}; templates['credentialsDialog'] = template({"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data) { var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression; diff --git a/apps/files_external/tests/appSpec.js b/apps/files_external/tests/appSpec.js index 43902d1c1d0..a834d96e2c0 100644 --- a/apps/files_external/tests/appSpec.js +++ b/apps/files_external/tests/appSpec.js @@ -19,8 +19,8 @@ * */ -describe('OCA.External.App tests', function() { - var App = OCA.External.App; +describe('OCA.Files_External.App tests', function() { + var App = OCA.Files_External.App; var fileList; beforeEach(function() { diff --git a/apps/files_external/tests/js/mountsfilelistSpec.js b/apps/files_external/tests/js/mountsfilelistSpec.js index feea68cf346..fe2fd8dec84 100644 --- a/apps/files_external/tests/js/mountsfilelistSpec.js +++ b/apps/files_external/tests/js/mountsfilelistSpec.js @@ -8,7 +8,7 @@ * */ -describe('OCA.External.FileList tests', function() { +describe('OCA.Files_External.FileList tests', function() { var testFiles, alertStub, notificationStub, fileList; beforeEach(function() { @@ -62,7 +62,7 @@ describe('OCA.External.FileList tests', function() { var ocsResponse; beforeEach(function() { - fileList = new OCA.External.FileList( + fileList = new OCA.Files_External.FileList( $('#app-content-container') ); diff --git a/apps/files_external/tests/js/settingsSpec.js b/apps/files_external/tests/js/settingsSpec.js index 57ad4550993..e004871650c 100644 --- a/apps/files_external/tests/js/settingsSpec.js +++ b/apps/files_external/tests/js/settingsSpec.js @@ -8,7 +8,7 @@ * */ -describe('OCA.External.Settings tests', function() { +describe('OCA.Files_External.Settings tests', function() { var clock; var select2Stub; var select2ApplicableUsers; @@ -156,7 +156,7 @@ describe('OCA.External.Settings tests', function() { beforeEach(function() { var $el = $('#externalStorage'); - view = new OCA.External.Settings.MountConfigListView($el, {encryptionEnabled: false}); + view = new OCA.Files_External.Settings.MountConfigListView($el, {encryptionEnabled: false}); }); afterEach(function() { view = null; diff --git a/apps/files_sharing/css/sharetabview.scss b/apps/files_sharing/css/sharetabview.scss index 14be9562228..0ca99ddc8f7 100644 --- a/apps/files_sharing/css/sharetabview.scss +++ b/apps/files_sharing/css/sharetabview.scss @@ -4,6 +4,10 @@ .share-autocomplete-item { display: flex; + + &.merged { + margin-left: 32px; + } .autocomplete-item-text { margin-left: 10px; margin-right: 10px; @@ -12,6 +16,27 @@ overflow: hidden; line-height: 32px; vertical-align: middle; + flex-grow: 1; + .ui-state-highlight { + border: none; + margin: 0; + } + } + &.with-description { + .autocomplete-item-text { + line-height: 100%; + } + } + .autocomplete-item-details { + display: block; + line-height: 130%; + font-size: 90%; + opacity: 0.7; + } + + .icon { + opacity: .7; + margin-right: 7px; } } @@ -26,7 +51,6 @@ top: 0px; } .shareWithConfirm, - .clipboardButton, .linkPass .icon-loading-small { position: absolute; right: 2px; @@ -49,21 +73,17 @@ .datepicker { margin-left: 35px; } - .clipboardButton { - position: relative; - top: initial; - right: initial; - padding: 0; - } .share-add { input.share-note-delete { - display: none; border: none; background-color: transparent; width: 44px !important; padding: 0; flex: 0 0 44px; margin-left: auto; + &.hidden { + display: none; + } } } // note @@ -98,6 +118,11 @@ margin-bottom: 10px; } } + + /* Border above last entry '+ Add another share' to separate it from current link settings */ + .new-share { + border-top: 1px solid var(--color-border); + } } .linkPass .icon-loading-small { margin-right: 0px; @@ -157,7 +182,7 @@ .avatar { width: 32px; height: 32px; - background-color: var(--color-background-darker); + background-color: var(--color-primary); } } .unshare img { @@ -169,31 +194,29 @@ display: flex; align-items: center; white-space: nowrap; - // can edit label - > .shareOption > label { - padding: 13px; - padding-right: 0; - } - // more menu - > .share-menu { - position: relative; + + // icons + > .icon:not(.hidden), + .share-menu > .icon:not(.hidden) { + padding: 14px; + height: 44px; + width: 44px; + opacity: .5; display: block; - .icon-more { - padding: 14px; - height: 44px; - width: 44px; - opacity: .5; - display: block; - cursor: pointer; - } + cursor: pointer; + &:hover, &:focus, &:active { - .icon-more { - opacity: .7;; - } + opacity: .7;; } } + + // more menu + > .share-menu { + position: relative; + display: block; + } } .username { padding: 0 8px; @@ -204,8 +227,8 @@ } .ui-autocomplete { - /* limit dropdown height to 4 1/2 entries */ - max-height: calc(36px * 4.5);; + /* limit dropdown height to 6 1/2 entries */ + max-height: calc(36px * 6.5); overflow-y: auto; overflow-x: hidden; z-index: 1550 !important; @@ -244,4 +267,4 @@ .resharerInfoView.subView { position: relative; -}
\ No newline at end of file +} diff --git a/apps/files_sharing/js/share.js b/apps/files_sharing/js/share.js index 68529fd882f..b02c6e3d9ee 100644 --- a/apps/files_sharing/js/share.js +++ b/apps/files_sharing/js/share.js @@ -193,15 +193,15 @@ var $tr = fileList.findFileEl(fileInfoModel.get('name')); // We count email shares as link share - var hasLinkShare = shareModel.hasLinkShare(); + var hasLinkShares = shareModel.hasLinkShares(); shareModel.get('shares').forEach(function (share) { if (share.share_type === OC.Share.SHARE_TYPE_EMAIL) { - hasLinkShare = true; + hasLinkShares = true; } }); OCA.Sharing.Util._updateFileListDataAttributes(fileList, $tr, shareModel); - if (!OCA.Sharing.Util._updateFileActionIcon($tr, shareModel.hasUserShares(), hasLinkShare)) { + if (!OCA.Sharing.Util._updateFileActionIcon($tr, shareModel.hasUserShares(), hasLinkShares)) { // remove icon, if applicable OC.Share.markFileAsShared($tr, false, false); } @@ -249,15 +249,15 @@ * * @param $tr file element of the file to update * @param {boolean} hasUserShares true if a user share exists - * @param {boolean} hasLinkShare true if a link share exists + * @param {boolean} hasLinkShares true if a link share exists * * @return {boolean} true if the icon was set, false otherwise */ - _updateFileActionIcon: function($tr, hasUserShares, hasLinkShare) { + _updateFileActionIcon: function($tr, hasUserShares, hasLinkShares) { // if the statuses are loaded already, use them for the icon // (needed when scrolling to the next page) - if (hasUserShares || hasLinkShare || $tr.attr('data-share-recipient-data') || $tr.attr('data-share-owner')) { - OC.Share.markFileAsShared($tr, true, hasLinkShare); + if (hasUserShares || hasLinkShares || $tr.attr('data-share-recipient-data') || $tr.attr('data-share-owner')) { + OC.Share.markFileAsShared($tr, true, hasLinkShares); return true; } return false; diff --git a/apps/files_sharing/l10n/cs.js b/apps/files_sharing/l10n/cs.js index fdc18a2da3f..d1abeb033bc 100644 --- a/apps/files_sharing/l10n/cs.js +++ b/apps/files_sharing/l10n/cs.js @@ -88,9 +88,9 @@ OC.L10N.register( "Public link sharing is disabled by the administrator" : "Sdílení veřejným odkazem je zakázáno správcem", "Public upload disabled by the administrator" : "Nahrávání veřejností zakázáno správcem", "Public upload is only possible for publicly shared folders" : "Veřejné nahrávání je možné pouze do veřejně sdílených šložek", + "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Sdílení %s posláním hesla přes Nextcloud Talk se nezdařilo, protože Nextcloud Talk není zapnutý", "Invalid date, date format must be YYYY-MM-DD" : "Neplatné datum – je třeba, aby jeho formát byl RRRR-MM-DD", "Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Sdílení %1$s se nezdařilo, protože podpůrná vrstva nepodporuje typ sdílení %2$s", - "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Sdílení %s posláním hesla přes Nextcloud Talk se nezdařilo, protože Nextcloud Talk není zapnutý", "You cannot share to a Circle if the app is not enabled" : "Do okruhu nemůžete sdílet, pokud není aplikace zapnuta", "Please specify a valid circle" : "Zadejte platný okruh", "Sharing %s failed because the back end does not support room shares" : "Sdílení %s se nezdařilo protože podpůrná vrstva nepodporuje sdílení místností", diff --git a/apps/files_sharing/l10n/cs.json b/apps/files_sharing/l10n/cs.json index 877742a4a50..415c2b53b5f 100644 --- a/apps/files_sharing/l10n/cs.json +++ b/apps/files_sharing/l10n/cs.json @@ -86,9 +86,9 @@ "Public link sharing is disabled by the administrator" : "Sdílení veřejným odkazem je zakázáno správcem", "Public upload disabled by the administrator" : "Nahrávání veřejností zakázáno správcem", "Public upload is only possible for publicly shared folders" : "Veřejné nahrávání je možné pouze do veřejně sdílených šložek", + "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Sdílení %s posláním hesla přes Nextcloud Talk se nezdařilo, protože Nextcloud Talk není zapnutý", "Invalid date, date format must be YYYY-MM-DD" : "Neplatné datum – je třeba, aby jeho formát byl RRRR-MM-DD", "Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Sdílení %1$s se nezdařilo, protože podpůrná vrstva nepodporuje typ sdílení %2$s", - "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Sdílení %s posláním hesla přes Nextcloud Talk se nezdařilo, protože Nextcloud Talk není zapnutý", "You cannot share to a Circle if the app is not enabled" : "Do okruhu nemůžete sdílet, pokud není aplikace zapnuta", "Please specify a valid circle" : "Zadejte platný okruh", "Sharing %s failed because the back end does not support room shares" : "Sdílení %s se nezdařilo protože podpůrná vrstva nepodporuje sdílení místností", diff --git a/apps/files_sharing/l10n/de.js b/apps/files_sharing/l10n/de.js index acac2952093..84b463aca49 100644 --- a/apps/files_sharing/l10n/de.js +++ b/apps/files_sharing/l10n/de.js @@ -88,9 +88,9 @@ OC.L10N.register( "Public link sharing is disabled by the administrator" : "Die öffentliche Freigabe von Links ist durch den Administrator deaktiviert", "Public upload disabled by the administrator" : "Das öffentliche Hochladen ist durch den Administrator deaktiviert", "Public upload is only possible for publicly shared folders" : "Das öffentliche Hochladen ist nur für öffentlich freigegebene Ordner erlaubt", + "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "%s Freigeben: Senden des Passwortes über Nextcloud Talk gescheitert, da Nextcloud Talk nicht verfügbar ist", "Invalid date, date format must be YYYY-MM-DD" : "Ungültiges Datum, das Datumsformat muss JJJJ-MM-TT sein", "Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Freigabe von %1$s fehlgeschlagen, da das Backend die Freigabe vom Typ %2$s nicht erlaubt", - "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "%s Freigeben: Senden des Passwortes über Nextcloud Talk gescheitert, da Nextcloud Talk nicht verfügbar ist", "You cannot share to a Circle if the app is not enabled" : "Du kannst nichts mit einem Kreis teilen, wenn die App nicht aktiviert ist", "Please specify a valid circle" : "Bitte einen gültigen Kreis angeben", "Sharing %s failed because the back end does not support room shares" : "Freigabe von %s fehlgeschlagen, da das Backend die Freigabe von Räumen nicht unterstützt", diff --git a/apps/files_sharing/l10n/de.json b/apps/files_sharing/l10n/de.json index 97d761b3bd5..5b9a6e862de 100644 --- a/apps/files_sharing/l10n/de.json +++ b/apps/files_sharing/l10n/de.json @@ -86,9 +86,9 @@ "Public link sharing is disabled by the administrator" : "Die öffentliche Freigabe von Links ist durch den Administrator deaktiviert", "Public upload disabled by the administrator" : "Das öffentliche Hochladen ist durch den Administrator deaktiviert", "Public upload is only possible for publicly shared folders" : "Das öffentliche Hochladen ist nur für öffentlich freigegebene Ordner erlaubt", + "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "%s Freigeben: Senden des Passwortes über Nextcloud Talk gescheitert, da Nextcloud Talk nicht verfügbar ist", "Invalid date, date format must be YYYY-MM-DD" : "Ungültiges Datum, das Datumsformat muss JJJJ-MM-TT sein", "Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Freigabe von %1$s fehlgeschlagen, da das Backend die Freigabe vom Typ %2$s nicht erlaubt", - "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "%s Freigeben: Senden des Passwortes über Nextcloud Talk gescheitert, da Nextcloud Talk nicht verfügbar ist", "You cannot share to a Circle if the app is not enabled" : "Du kannst nichts mit einem Kreis teilen, wenn die App nicht aktiviert ist", "Please specify a valid circle" : "Bitte einen gültigen Kreis angeben", "Sharing %s failed because the back end does not support room shares" : "Freigabe von %s fehlgeschlagen, da das Backend die Freigabe von Räumen nicht unterstützt", diff --git a/apps/files_sharing/l10n/de_DE.js b/apps/files_sharing/l10n/de_DE.js index 04090d4fff5..839cfe989e7 100644 --- a/apps/files_sharing/l10n/de_DE.js +++ b/apps/files_sharing/l10n/de_DE.js @@ -88,9 +88,9 @@ OC.L10N.register( "Public link sharing is disabled by the administrator" : "Die öffentliche Freigabe von Links ist durch den Administrator deaktiviert", "Public upload disabled by the administrator" : "Das öffentliche Hochladen ist durch den Administrator deaktiviert", "Public upload is only possible for publicly shared folders" : "Das öffentliche Hochladen ist nur für öffentlich freigegebene Ordner erlaubt", + "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "%s Freigeben: Senden des Passwortes über Nextcloud Talk gescheitert, da Nextcloud Talk nicht verfügbar ist", "Invalid date, date format must be YYYY-MM-DD" : "Ungültiges Datum, das Datumsformat muss JJJJ-MM-TT sein", "Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Freigabe von %1$s fehlgeschlagen, da das Backend die Freigabe vom Typ %2$s nicht erlaubt", - "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "%s Freigeben: Senden des Passwortes über Nextcloud Talk gescheitert, da Nextcloud Talk nicht verfügbar ist", "You cannot share to a Circle if the app is not enabled" : "Sie können nichts mit einem Kreis teilen, wenn die App nicht aktiviert ist", "Please specify a valid circle" : "Bitte einen gültigen Kreis angeben", "Sharing %s failed because the back end does not support room shares" : "Freigabe von %s fehlgeschlagen, da das Backend die Freigabe von Räumen nicht unterstützt", diff --git a/apps/files_sharing/l10n/de_DE.json b/apps/files_sharing/l10n/de_DE.json index 88cbc15a831..15df40394f1 100644 --- a/apps/files_sharing/l10n/de_DE.json +++ b/apps/files_sharing/l10n/de_DE.json @@ -86,9 +86,9 @@ "Public link sharing is disabled by the administrator" : "Die öffentliche Freigabe von Links ist durch den Administrator deaktiviert", "Public upload disabled by the administrator" : "Das öffentliche Hochladen ist durch den Administrator deaktiviert", "Public upload is only possible for publicly shared folders" : "Das öffentliche Hochladen ist nur für öffentlich freigegebene Ordner erlaubt", + "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "%s Freigeben: Senden des Passwortes über Nextcloud Talk gescheitert, da Nextcloud Talk nicht verfügbar ist", "Invalid date, date format must be YYYY-MM-DD" : "Ungültiges Datum, das Datumsformat muss JJJJ-MM-TT sein", "Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Freigabe von %1$s fehlgeschlagen, da das Backend die Freigabe vom Typ %2$s nicht erlaubt", - "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "%s Freigeben: Senden des Passwortes über Nextcloud Talk gescheitert, da Nextcloud Talk nicht verfügbar ist", "You cannot share to a Circle if the app is not enabled" : "Sie können nichts mit einem Kreis teilen, wenn die App nicht aktiviert ist", "Please specify a valid circle" : "Bitte einen gültigen Kreis angeben", "Sharing %s failed because the back end does not support room shares" : "Freigabe von %s fehlgeschlagen, da das Backend die Freigabe von Räumen nicht unterstützt", diff --git a/apps/files_sharing/l10n/es.js b/apps/files_sharing/l10n/es.js index 43f9c5f3ce7..1ddf9f4fb29 100644 --- a/apps/files_sharing/l10n/es.js +++ b/apps/files_sharing/l10n/es.js @@ -88,9 +88,9 @@ OC.L10N.register( "Public link sharing is disabled by the administrator" : "Compartir enlaces de forma pública está deshabilitado por el administrador", "Public upload disabled by the administrator" : "La subida pública está deshabilitado por el administrador", "Public upload is only possible for publicly shared folders" : "La subida publica solo es posible para las carpetas publicas compartidas", - "Invalid date, date format must be YYYY-MM-DD" : "Fecha inválida, el formato de las fechas debe ser YYYY-MM-DD", - "Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Compartir %1$s ha fallado porque el motor no permite compartidos del tipo %2$s", "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Compartir %s enviando la contraseña por Nextcloud Talk ha falllado porque Nextcloud Talk no está activado", + "Invalid date, date format must be YYYY-MM-DD" : "Fecha inválida, el formato de las fechas debe ser YYYY-MM-DD", + "Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Compartir %1$s ha fallado porque el backend no permite compartidos del tipo %2$s", "You cannot share to a Circle if the app is not enabled" : "No puede compartir a un Circulo si la aplicación no esta activada", "Please specify a valid circle" : "Por favor especifique un circulo valido", "Sharing %s failed because the back end does not support room shares" : "Compartir %s ha fallado porque el backend no soporta habitaciones compartidas", diff --git a/apps/files_sharing/l10n/es.json b/apps/files_sharing/l10n/es.json index 00fa8870b72..8a7b4f40259 100644 --- a/apps/files_sharing/l10n/es.json +++ b/apps/files_sharing/l10n/es.json @@ -86,9 +86,9 @@ "Public link sharing is disabled by the administrator" : "Compartir enlaces de forma pública está deshabilitado por el administrador", "Public upload disabled by the administrator" : "La subida pública está deshabilitado por el administrador", "Public upload is only possible for publicly shared folders" : "La subida publica solo es posible para las carpetas publicas compartidas", - "Invalid date, date format must be YYYY-MM-DD" : "Fecha inválida, el formato de las fechas debe ser YYYY-MM-DD", - "Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Compartir %1$s ha fallado porque el motor no permite compartidos del tipo %2$s", "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Compartir %s enviando la contraseña por Nextcloud Talk ha falllado porque Nextcloud Talk no está activado", + "Invalid date, date format must be YYYY-MM-DD" : "Fecha inválida, el formato de las fechas debe ser YYYY-MM-DD", + "Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Compartir %1$s ha fallado porque el backend no permite compartidos del tipo %2$s", "You cannot share to a Circle if the app is not enabled" : "No puede compartir a un Circulo si la aplicación no esta activada", "Please specify a valid circle" : "Por favor especifique un circulo valido", "Sharing %s failed because the back end does not support room shares" : "Compartir %s ha fallado porque el backend no soporta habitaciones compartidas", diff --git a/apps/files_sharing/l10n/fr.js b/apps/files_sharing/l10n/fr.js index e6549b0cdf5..e2976d939f0 100644 --- a/apps/files_sharing/l10n/fr.js +++ b/apps/files_sharing/l10n/fr.js @@ -88,9 +88,9 @@ OC.L10N.register( "Public link sharing is disabled by the administrator" : "Le partage de lien public a été désactivé par l'administrateur", "Public upload disabled by the administrator" : "Téléversement public désactivé par l'administrateur", "Public upload is only possible for publicly shared folders" : "Le téléversement public est possible uniquement pour les dossiers partagés publiquement", + "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Partager %s en envoyant le mot de passe par Nextcloud Talk a échoué car Nextcloud Talk n'est pas activé.", "Invalid date, date format must be YYYY-MM-DD" : "Date non valide, le format doit être DD/MM/YYYY", "Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Le partage %1$s a échoué parce que l'infrastructure n'autorise pas les partages du type %2$s", - "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Partager %s en envoyant le mot de passe par Nextcloud Talk a échoué car Nextcloud Talk n'est pas activé.", "You cannot share to a Circle if the app is not enabled" : "Vous ne pouvez pas partager au Cercle si l'application n'est pas activée", "Please specify a valid circle" : "Veuillez entrer un cercle valide", "Sharing %s failed because the back end does not support room shares" : "Le partage %s a échoué parce que l'arrière-plan ne prend pas en charge les partages.", diff --git a/apps/files_sharing/l10n/fr.json b/apps/files_sharing/l10n/fr.json index da6f68c246a..000b4bac874 100644 --- a/apps/files_sharing/l10n/fr.json +++ b/apps/files_sharing/l10n/fr.json @@ -86,9 +86,9 @@ "Public link sharing is disabled by the administrator" : "Le partage de lien public a été désactivé par l'administrateur", "Public upload disabled by the administrator" : "Téléversement public désactivé par l'administrateur", "Public upload is only possible for publicly shared folders" : "Le téléversement public est possible uniquement pour les dossiers partagés publiquement", + "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Partager %s en envoyant le mot de passe par Nextcloud Talk a échoué car Nextcloud Talk n'est pas activé.", "Invalid date, date format must be YYYY-MM-DD" : "Date non valide, le format doit être DD/MM/YYYY", "Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Le partage %1$s a échoué parce que l'infrastructure n'autorise pas les partages du type %2$s", - "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Partager %s en envoyant le mot de passe par Nextcloud Talk a échoué car Nextcloud Talk n'est pas activé.", "You cannot share to a Circle if the app is not enabled" : "Vous ne pouvez pas partager au Cercle si l'application n'est pas activée", "Please specify a valid circle" : "Veuillez entrer un cercle valide", "Sharing %s failed because the back end does not support room shares" : "Le partage %s a échoué parce que l'arrière-plan ne prend pas en charge les partages.", diff --git a/apps/files_sharing/l10n/is.js b/apps/files_sharing/l10n/is.js index cc1cc03ae39..93c678cdede 100644 --- a/apps/files_sharing/l10n/is.js +++ b/apps/files_sharing/l10n/is.js @@ -88,15 +88,18 @@ OC.L10N.register( "Public link sharing is disabled by the administrator" : "Deiling opinberra sameignartengla hefur verið gerð óvirk af kerfisstjóra.", "Public upload disabled by the administrator" : "Opinber innsending hefur verið gerð óvirk af kerfisstjóra.", "Public upload is only possible for publicly shared folders" : "Opinber innsending er einungis möguleg í möppur sem er deilt opinberlega", + "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Deiling á %s með því að senda lykilorð með Nextcloud Talk mistókst því að Nextcloud Talk er ekki virkt", "Invalid date, date format must be YYYY-MM-DD" : "Ógild dagsetning, dagsetningasniðið verður að vera ÁÁÁÁ-MM-DD", "Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Deiling %1$s mistókst, því bakvinnslukerfið leyfir ekki sameignir af gerðinni %2$s", "You cannot share to a Circle if the app is not enabled" : "Þú getur ekki deilt með hring ef forritið er ekki virkt", "Please specify a valid circle" : "Settu inn gildan hring", + "Sharing %s failed because the back end does not support room shares" : "Deiling %s mistókst því bakvinnslukerfið leyfir ekki spjallsvæðasameignir", "Unknown share type" : "Óþekkt tegund sameignar", "Not a directory" : "Er ekki mappa", "Could not lock path" : "Gat ekki læst slóð", "Wrong or no update parameter given" : "Rangt eða ekkert uppfærsluviðfang gefið", "Can't change permissions for public share links" : "Ekki tókst að breyta aðgangsheimildum fyrir opinbera deilingartengla", + "Sharing sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Deiling með því að senda lykilorð með Nextcloud Talk mistókst því að Nextcloud Talk er ekki virkt", "Cannot increase permissions" : "Get ekki aukið aðgangsheimildir", "shared by %s" : "Deilt af %s", "Direct link" : "Beinn tengill", diff --git a/apps/files_sharing/l10n/is.json b/apps/files_sharing/l10n/is.json index c984df71929..f88598bfa1b 100644 --- a/apps/files_sharing/l10n/is.json +++ b/apps/files_sharing/l10n/is.json @@ -86,15 +86,18 @@ "Public link sharing is disabled by the administrator" : "Deiling opinberra sameignartengla hefur verið gerð óvirk af kerfisstjóra.", "Public upload disabled by the administrator" : "Opinber innsending hefur verið gerð óvirk af kerfisstjóra.", "Public upload is only possible for publicly shared folders" : "Opinber innsending er einungis möguleg í möppur sem er deilt opinberlega", + "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Deiling á %s með því að senda lykilorð með Nextcloud Talk mistókst því að Nextcloud Talk er ekki virkt", "Invalid date, date format must be YYYY-MM-DD" : "Ógild dagsetning, dagsetningasniðið verður að vera ÁÁÁÁ-MM-DD", "Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Deiling %1$s mistókst, því bakvinnslukerfið leyfir ekki sameignir af gerðinni %2$s", "You cannot share to a Circle if the app is not enabled" : "Þú getur ekki deilt með hring ef forritið er ekki virkt", "Please specify a valid circle" : "Settu inn gildan hring", + "Sharing %s failed because the back end does not support room shares" : "Deiling %s mistókst því bakvinnslukerfið leyfir ekki spjallsvæðasameignir", "Unknown share type" : "Óþekkt tegund sameignar", "Not a directory" : "Er ekki mappa", "Could not lock path" : "Gat ekki læst slóð", "Wrong or no update parameter given" : "Rangt eða ekkert uppfærsluviðfang gefið", "Can't change permissions for public share links" : "Ekki tókst að breyta aðgangsheimildum fyrir opinbera deilingartengla", + "Sharing sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Deiling með því að senda lykilorð með Nextcloud Talk mistókst því að Nextcloud Talk er ekki virkt", "Cannot increase permissions" : "Get ekki aukið aðgangsheimildir", "shared by %s" : "Deilt af %s", "Direct link" : "Beinn tengill", diff --git a/apps/files_sharing/l10n/it.js b/apps/files_sharing/l10n/it.js index cbc56eb8fca..0b84f6f25c0 100644 --- a/apps/files_sharing/l10n/it.js +++ b/apps/files_sharing/l10n/it.js @@ -88,9 +88,9 @@ OC.L10N.register( "Public link sharing is disabled by the administrator" : "La condivisione pubblica di collegamenti è disabilitata dall'amministratore", "Public upload disabled by the administrator" : "Caricamento pubblico disabilitato dall'amministratore", "Public upload is only possible for publicly shared folders" : "Il caricamento pubblico è possibile solo per cartelle condivise pubblicamente", + "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "La condivisione di %s tramite invio della password da Nextcloud Talk non è riuscito poiché Nextcloud Talk non è abilitato", "Invalid date, date format must be YYYY-MM-DD" : "Data non valida, il formato della data deve essere YYYY-MM-DD", "Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Condivisione di %1$s non riuscita poiché il motore non consente condivisioni del tipo %2$s", - "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "La condivisione di %s tramite invio della password da Nextcloud Talk non è riuscito poiché Nextcloud Talk non è abilitato", "You cannot share to a Circle if the app is not enabled" : "Non puoi condividere con una cerchia se l'applicazione non è abilitata", "Please specify a valid circle" : "Specifica una cerchia valida", "Sharing %s failed because the back end does not support room shares" : "Condivisione di %s non riuscita poiché il motore non supporta condivisioni di stanza", diff --git a/apps/files_sharing/l10n/it.json b/apps/files_sharing/l10n/it.json index 0a9071fd9ba..a2147b93268 100644 --- a/apps/files_sharing/l10n/it.json +++ b/apps/files_sharing/l10n/it.json @@ -86,9 +86,9 @@ "Public link sharing is disabled by the administrator" : "La condivisione pubblica di collegamenti è disabilitata dall'amministratore", "Public upload disabled by the administrator" : "Caricamento pubblico disabilitato dall'amministratore", "Public upload is only possible for publicly shared folders" : "Il caricamento pubblico è possibile solo per cartelle condivise pubblicamente", + "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "La condivisione di %s tramite invio della password da Nextcloud Talk non è riuscito poiché Nextcloud Talk non è abilitato", "Invalid date, date format must be YYYY-MM-DD" : "Data non valida, il formato della data deve essere YYYY-MM-DD", "Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Condivisione di %1$s non riuscita poiché il motore non consente condivisioni del tipo %2$s", - "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "La condivisione di %s tramite invio della password da Nextcloud Talk non è riuscito poiché Nextcloud Talk non è abilitato", "You cannot share to a Circle if the app is not enabled" : "Non puoi condividere con una cerchia se l'applicazione non è abilitata", "Please specify a valid circle" : "Specifica una cerchia valida", "Sharing %s failed because the back end does not support room shares" : "Condivisione di %s non riuscita poiché il motore non supporta condivisioni di stanza", diff --git a/apps/files_sharing/l10n/nl.js b/apps/files_sharing/l10n/nl.js index 3c2f1aec70c..c9274091686 100644 --- a/apps/files_sharing/l10n/nl.js +++ b/apps/files_sharing/l10n/nl.js @@ -88,9 +88,9 @@ OC.L10N.register( "Public link sharing is disabled by the administrator" : "Delen van openbare links is uitgeschakeld door de beheerder", "Public upload disabled by the administrator" : "Publieke upload uitgeschakeld door de systeembeheerder", "Public upload is only possible for publicly shared folders" : "Publieke upload is alleen mogelijk voor publiek gedeelde mappen", + "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Delen %s versturen van het wachtwoord via Nextcloud Talk is mislukt omdat Nextcloud Talk niet is ingeschakeld", "Invalid date, date format must be YYYY-MM-DD" : "Ongeldige datum, datumnotatie moet in de vorm YYYY-MM-DD", "Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Delen van %1$s mislukte omdat de backend het delen van type %2$s niet ondersteunt", - "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Delen %s versturen van het wachtwoord via Nextcloud Talk is mislukt omdat Nextcloud Talk niet is ingeschakeld", "You cannot share to a Circle if the app is not enabled" : "Je kunt niets met een Kring delen als de app niet is ingeschakeld.", "Please specify a valid circle" : "Geef een geldige kring op", "Sharing %s failed because the back end does not support room shares" : "Delen van %s mislukte omdat de backend het delen in ruimtes niet ondersteunt", diff --git a/apps/files_sharing/l10n/nl.json b/apps/files_sharing/l10n/nl.json index 62816b3edb5..e274526c38a 100644 --- a/apps/files_sharing/l10n/nl.json +++ b/apps/files_sharing/l10n/nl.json @@ -86,9 +86,9 @@ "Public link sharing is disabled by the administrator" : "Delen van openbare links is uitgeschakeld door de beheerder", "Public upload disabled by the administrator" : "Publieke upload uitgeschakeld door de systeembeheerder", "Public upload is only possible for publicly shared folders" : "Publieke upload is alleen mogelijk voor publiek gedeelde mappen", + "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Delen %s versturen van het wachtwoord via Nextcloud Talk is mislukt omdat Nextcloud Talk niet is ingeschakeld", "Invalid date, date format must be YYYY-MM-DD" : "Ongeldige datum, datumnotatie moet in de vorm YYYY-MM-DD", "Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Delen van %1$s mislukte omdat de backend het delen van type %2$s niet ondersteunt", - "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Delen %s versturen van het wachtwoord via Nextcloud Talk is mislukt omdat Nextcloud Talk niet is ingeschakeld", "You cannot share to a Circle if the app is not enabled" : "Je kunt niets met een Kring delen als de app niet is ingeschakeld.", "Please specify a valid circle" : "Geef een geldige kring op", "Sharing %s failed because the back end does not support room shares" : "Delen van %s mislukte omdat de backend het delen in ruimtes niet ondersteunt", diff --git a/apps/files_sharing/l10n/pl.js b/apps/files_sharing/l10n/pl.js index beca0931e1a..ff8571fe55f 100644 --- a/apps/files_sharing/l10n/pl.js +++ b/apps/files_sharing/l10n/pl.js @@ -88,8 +88,8 @@ OC.L10N.register( "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", - "Invalid date, date format must be YYYY-MM-DD" : "Nieprawidłowa data, format daty musi być w formacie YYYY-MM-DD", "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Udostępnianie %s wysyłanie hasła przez Nextcloud Talk nie powiodło się, ponieważ usługa Nextcloud Talk nie jest włączona", + "Invalid date, date format must be YYYY-MM-DD" : "Nieprawidłowa data, format daty musi być w formacie YYYY-MM-DD", "You cannot share to a Circle if the app is not enabled" : "Nie możesz udostępnić do Kręgów jeśli aplikacja jest wyłączona", "Please specify a valid circle" : "Proszę podać właściwy krąg", "Unknown share type" : "Nieznany typ udziału", diff --git a/apps/files_sharing/l10n/pl.json b/apps/files_sharing/l10n/pl.json index 3deb0c8e80a..f8bc173b35c 100644 --- a/apps/files_sharing/l10n/pl.json +++ b/apps/files_sharing/l10n/pl.json @@ -86,8 +86,8 @@ "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", - "Invalid date, date format must be YYYY-MM-DD" : "Nieprawidłowa data, format daty musi być w formacie YYYY-MM-DD", "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Udostępnianie %s wysyłanie hasła przez Nextcloud Talk nie powiodło się, ponieważ usługa Nextcloud Talk nie jest włączona", + "Invalid date, date format must be YYYY-MM-DD" : "Nieprawidłowa data, format daty musi być w formacie YYYY-MM-DD", "You cannot share to a Circle if the app is not enabled" : "Nie możesz udostępnić do Kręgów jeśli aplikacja jest wyłączona", "Please specify a valid circle" : "Proszę podać właściwy krąg", "Unknown share type" : "Nieznany typ udziału", diff --git a/apps/files_sharing/l10n/pt_BR.js b/apps/files_sharing/l10n/pt_BR.js index 52db057cdfc..59b488e5cfb 100644 --- a/apps/files_sharing/l10n/pt_BR.js +++ b/apps/files_sharing/l10n/pt_BR.js @@ -88,9 +88,9 @@ OC.L10N.register( "Public link sharing is disabled by the administrator" : "O compartilhamento por link público foi desativado pelo administrador", "Public upload disabled by the administrator" : "O envio público foi desativado pelo administrador", "Public upload is only possible for publicly shared folders" : "O envio público só é possível para pastas compartilhadas publicamente", + "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "O compartilhamento %s falhou ao enviar a senha ao Nextcloud Talk porque este não está ativado", "Invalid date, date format must be YYYY-MM-DD" : "Data inválida, o formato da data deve ser YYYY-MM-DD", "Sharing %1$s failed because the back end does not allow shares from type %2$s" : "O compartilhamento %1$s falhou porque a infraestrutura não permite compartilhamentos do tipo %2$s", - "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "O compartilhamento %s falhou ao enviar a senha ao Nextcloud Talk porque este não está ativado", "You cannot share to a Circle if the app is not enabled" : "Você não pode compartilhar para um círculo se o aplicativo não está habilitado", "Please specify a valid circle" : "Por favor especifique um círculo válido", "Sharing %s failed because the back end does not support room shares" : "Falhou ao compartilhar %s porque o sistema não suporta compartilhamento de salas", diff --git a/apps/files_sharing/l10n/pt_BR.json b/apps/files_sharing/l10n/pt_BR.json index 78fe47c4ac1..f73dd0f14f9 100644 --- a/apps/files_sharing/l10n/pt_BR.json +++ b/apps/files_sharing/l10n/pt_BR.json @@ -86,9 +86,9 @@ "Public link sharing is disabled by the administrator" : "O compartilhamento por link público foi desativado pelo administrador", "Public upload disabled by the administrator" : "O envio público foi desativado pelo administrador", "Public upload is only possible for publicly shared folders" : "O envio público só é possível para pastas compartilhadas publicamente", + "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "O compartilhamento %s falhou ao enviar a senha ao Nextcloud Talk porque este não está ativado", "Invalid date, date format must be YYYY-MM-DD" : "Data inválida, o formato da data deve ser YYYY-MM-DD", "Sharing %1$s failed because the back end does not allow shares from type %2$s" : "O compartilhamento %1$s falhou porque a infraestrutura não permite compartilhamentos do tipo %2$s", - "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "O compartilhamento %s falhou ao enviar a senha ao Nextcloud Talk porque este não está ativado", "You cannot share to a Circle if the app is not enabled" : "Você não pode compartilhar para um círculo se o aplicativo não está habilitado", "Please specify a valid circle" : "Por favor especifique um círculo válido", "Sharing %s failed because the back end does not support room shares" : "Falhou ao compartilhar %s porque o sistema não suporta compartilhamento de salas", diff --git a/apps/files_sharing/l10n/ru.js b/apps/files_sharing/l10n/ru.js index 35a719de06b..7fcfb301559 100644 --- a/apps/files_sharing/l10n/ru.js +++ b/apps/files_sharing/l10n/ru.js @@ -88,9 +88,9 @@ OC.L10N.register( "Public link sharing is disabled by the administrator" : "Возможность предоставления общего доступа созданием общедоступных ссылок отключена администратором", "Public upload disabled by the administrator" : "Загрузка в общедоступную папку запрещена администратором", "Public upload is only possible for publicly shared folders" : "Общедоступная загрузка возможна только в общедоступные папки", + "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Не удалось отправить пароль для доступа к «%s»: приложение Nextcloud Talk отключено.", "Invalid date, date format must be YYYY-MM-DD" : "Неверная дата, формат даты должен быть ГГГГ-ММ-ДД", "Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Не удалось предоставить общий доступ к «%1$s», поскольку механизм удалённого обмена не разрешает публикации типа %2$s", - "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Не удалось отправить пароль для доступа к «%s»: приложение Nextcloud Talk отключено.", "You cannot share to a Circle if the app is not enabled" : "Вы не можете поделиться с кругом, если приложение «Круг» не включено", "Please specify a valid circle" : "Укажите верный круг", "Sharing %s failed because the back end does not support room shares" : "Не удалось предоставить общий доступ к «%s» поскольку механизм обмена не поддерживает общий доступ такого типа", diff --git a/apps/files_sharing/l10n/ru.json b/apps/files_sharing/l10n/ru.json index fc31b2380d7..47a1289cd42 100644 --- a/apps/files_sharing/l10n/ru.json +++ b/apps/files_sharing/l10n/ru.json @@ -86,9 +86,9 @@ "Public link sharing is disabled by the administrator" : "Возможность предоставления общего доступа созданием общедоступных ссылок отключена администратором", "Public upload disabled by the administrator" : "Загрузка в общедоступную папку запрещена администратором", "Public upload is only possible for publicly shared folders" : "Общедоступная загрузка возможна только в общедоступные папки", + "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Не удалось отправить пароль для доступа к «%s»: приложение Nextcloud Talk отключено.", "Invalid date, date format must be YYYY-MM-DD" : "Неверная дата, формат даты должен быть ГГГГ-ММ-ДД", "Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Не удалось предоставить общий доступ к «%1$s», поскольку механизм удалённого обмена не разрешает публикации типа %2$s", - "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Не удалось отправить пароль для доступа к «%s»: приложение Nextcloud Talk отключено.", "You cannot share to a Circle if the app is not enabled" : "Вы не можете поделиться с кругом, если приложение «Круг» не включено", "Please specify a valid circle" : "Укажите верный круг", "Sharing %s failed because the back end does not support room shares" : "Не удалось предоставить общий доступ к «%s» поскольку механизм обмена не поддерживает общий доступ такого типа", diff --git a/apps/files_sharing/l10n/sk.js b/apps/files_sharing/l10n/sk.js index 84d451cfb94..b09abb71ee6 100644 --- a/apps/files_sharing/l10n/sk.js +++ b/apps/files_sharing/l10n/sk.js @@ -88,9 +88,9 @@ OC.L10N.register( "Public link sharing is disabled by the administrator" : "Sprístupnenie pomocou verejných odkazov je zakázané administrátorom", "Public upload disabled by the administrator" : "Verejné nahrávanie je zakázané administrátorom", "Public upload is only possible for publicly shared folders" : "Verejné nahrávanie je možné len do verejne sprístupnených priečinkov", + "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Zdieľanie %s odoslaním hesla cez Nextcloud Talk zlyhalo, pretože Nextcloud Talk nie je zapnutý", "Invalid date, date format must be YYYY-MM-DD" : "Neplatný dátum, formát musí byť v tvare YYYY-MM-DD", "Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Sprístupnenie %1$s zlyhalo, backend nepodporuje typ sprístupnenia %2$s", - "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Zdieľanie %s odoslaním hesla cez Nextcloud Talk zlyhalo, pretože Nextcloud Talk nie je zapnutý", "You cannot share to a Circle if the app is not enabled" : "Ak aplikácia nie je povolená, nemôžete ju zdieľať s Kruhom", "Please specify a valid circle" : "Zadajte platný kruh", "Sharing %s failed because the back end does not support room shares" : "Zdieľanie %s sa nepodarilo, pretože backend nepodporuje zdieľanie miestností", diff --git a/apps/files_sharing/l10n/sk.json b/apps/files_sharing/l10n/sk.json index 6565bcb7d80..15b7534aa7e 100644 --- a/apps/files_sharing/l10n/sk.json +++ b/apps/files_sharing/l10n/sk.json @@ -86,9 +86,9 @@ "Public link sharing is disabled by the administrator" : "Sprístupnenie pomocou verejných odkazov je zakázané administrátorom", "Public upload disabled by the administrator" : "Verejné nahrávanie je zakázané administrátorom", "Public upload is only possible for publicly shared folders" : "Verejné nahrávanie je možné len do verejne sprístupnených priečinkov", + "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Zdieľanie %s odoslaním hesla cez Nextcloud Talk zlyhalo, pretože Nextcloud Talk nie je zapnutý", "Invalid date, date format must be YYYY-MM-DD" : "Neplatný dátum, formát musí byť v tvare YYYY-MM-DD", "Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Sprístupnenie %1$s zlyhalo, backend nepodporuje typ sprístupnenia %2$s", - "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Zdieľanie %s odoslaním hesla cez Nextcloud Talk zlyhalo, pretože Nextcloud Talk nie je zapnutý", "You cannot share to a Circle if the app is not enabled" : "Ak aplikácia nie je povolená, nemôžete ju zdieľať s Kruhom", "Please specify a valid circle" : "Zadajte platný kruh", "Sharing %s failed because the back end does not support room shares" : "Zdieľanie %s sa nepodarilo, pretože backend nepodporuje zdieľanie miestností", diff --git a/apps/files_sharing/l10n/sr.js b/apps/files_sharing/l10n/sr.js index 2d3ff556ee7..78524b05961 100644 --- a/apps/files_sharing/l10n/sr.js +++ b/apps/files_sharing/l10n/sr.js @@ -88,9 +88,9 @@ OC.L10N.register( "Public link sharing is disabled by the administrator" : "Администратор је забранио дељење јавном везом", "Public upload disabled by the administrator" : "Администратор је забранио отпремања са јавним приступом", "Public upload is only possible for publicly shared folders" : "Отпремања са јавним приступом су могућа само за јавно дељене фасцикле", + "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Дељење %s слањем лозинке преко Nextcloud Talk-а није успело пошто Nextcloud Talk није укључен", "Invalid date, date format must be YYYY-MM-DD" : "Неисправан датим, формат датума мора бити ГГГГ-ММ-ДД", "Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Дељење %1$s није успело зато што позадина не дозвољава дељење које је типа %2$s", - "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Дељење %s слањем лозинке преко Nextcloud Talk-а није успело пошто Nextcloud Talk није укључен", "You cannot share to a Circle if the app is not enabled" : "Не можете делити са Круговима ако та апликација није укључена", "Please specify a valid circle" : "Одаберите исправан круг", "Sharing %s failed because the back end does not support room shares" : "Није успело дељење %s зато што позадински мотор дељења не подржава дељења у соби", diff --git a/apps/files_sharing/l10n/sr.json b/apps/files_sharing/l10n/sr.json index 9eb7d45cf69..e163ef0e32f 100644 --- a/apps/files_sharing/l10n/sr.json +++ b/apps/files_sharing/l10n/sr.json @@ -86,9 +86,9 @@ "Public link sharing is disabled by the administrator" : "Администратор је забранио дељење јавном везом", "Public upload disabled by the administrator" : "Администратор је забранио отпремања са јавним приступом", "Public upload is only possible for publicly shared folders" : "Отпремања са јавним приступом су могућа само за јавно дељене фасцикле", + "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Дељење %s слањем лозинке преко Nextcloud Talk-а није успело пошто Nextcloud Talk није укључен", "Invalid date, date format must be YYYY-MM-DD" : "Неисправан датим, формат датума мора бити ГГГГ-ММ-ДД", "Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Дељење %1$s није успело зато што позадина не дозвољава дељење које је типа %2$s", - "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Дељење %s слањем лозинке преко Nextcloud Talk-а није успело пошто Nextcloud Talk није укључен", "You cannot share to a Circle if the app is not enabled" : "Не можете делити са Круговима ако та апликација није укључена", "Please specify a valid circle" : "Одаберите исправан круг", "Sharing %s failed because the back end does not support room shares" : "Није успело дељење %s зато што позадински мотор дељења не подржава дељења у соби", diff --git a/apps/files_sharing/l10n/sv.js b/apps/files_sharing/l10n/sv.js index 47eb4771378..439ecb2509d 100644 --- a/apps/files_sharing/l10n/sv.js +++ b/apps/files_sharing/l10n/sv.js @@ -88,9 +88,9 @@ OC.L10N.register( "Public link sharing is disabled by the administrator" : "Offentlig delningslänk är avstängt", "Public upload disabled by the administrator" : "Offentlig uppladdning är avstängt", "Public upload is only possible for publicly shared folders" : "Offentlig uppladdning fungerar endast i offentligt delade mappar", + "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Dela %s och skicka lösenordet via Nextcloud Talk går inte eftersom Nextcloud Talk är inte aktiverad", "Invalid date, date format must be YYYY-MM-DD" : "Ogiltigt datum, måste anges: ÅÅÅÅ-MM-DD", "Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Delning %1$s misslyckades. Ej tillåtet med delningar från typ %2$s", - "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Dela %s och skicka lösenordet via Nextcloud Talk går inte eftersom Nextcloud Talk är inte aktiverad", "You cannot share to a Circle if the app is not enabled" : "Du kan inte dela till en cirkel om appen inte är aktiverad", "Please specify a valid circle" : "Vänligen ange en giltig cirkel", "Sharing %s failed because the back end does not support room shares" : "Dela %s misslyckades eftersom systemet inte stödjer rum-delningar", diff --git a/apps/files_sharing/l10n/sv.json b/apps/files_sharing/l10n/sv.json index a68cb493b17..ffccde59908 100644 --- a/apps/files_sharing/l10n/sv.json +++ b/apps/files_sharing/l10n/sv.json @@ -86,9 +86,9 @@ "Public link sharing is disabled by the administrator" : "Offentlig delningslänk är avstängt", "Public upload disabled by the administrator" : "Offentlig uppladdning är avstängt", "Public upload is only possible for publicly shared folders" : "Offentlig uppladdning fungerar endast i offentligt delade mappar", + "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Dela %s och skicka lösenordet via Nextcloud Talk går inte eftersom Nextcloud Talk är inte aktiverad", "Invalid date, date format must be YYYY-MM-DD" : "Ogiltigt datum, måste anges: ÅÅÅÅ-MM-DD", "Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Delning %1$s misslyckades. Ej tillåtet med delningar från typ %2$s", - "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Dela %s och skicka lösenordet via Nextcloud Talk går inte eftersom Nextcloud Talk är inte aktiverad", "You cannot share to a Circle if the app is not enabled" : "Du kan inte dela till en cirkel om appen inte är aktiverad", "Please specify a valid circle" : "Vänligen ange en giltig cirkel", "Sharing %s failed because the back end does not support room shares" : "Dela %s misslyckades eftersom systemet inte stödjer rum-delningar", diff --git a/apps/files_sharing/l10n/tr.js b/apps/files_sharing/l10n/tr.js index 11f5d833fac..c0926ba1274 100644 --- a/apps/files_sharing/l10n/tr.js +++ b/apps/files_sharing/l10n/tr.js @@ -88,9 +88,9 @@ OC.L10N.register( "Public link sharing is disabled by the administrator" : "Herkese açık bağlantı paylaşımı yönetici tarafından devre dışı bırakılmış", "Public upload disabled by the administrator" : "Herkese açık yükleme yönetici tarafından devre dışı bırakılmış", "Public upload is only possible for publicly shared folders" : "Herkese açık yükleme ancak herkese açık paylaşılmış klasörlere yapılabilir", + "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Nextcloud Talk etkinleştirilmemiş olduğundan %sNextcloud Talk ile parola gönderilerek paylaşılamadı", "Invalid date, date format must be YYYY-MM-DD" : "Tarih geçersiz. Tarih biçimi YYYY-AA-GG olmalıdır", "Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Yönetim bölümünden %2$s türündeki paylaşımlar yapılamadığından %1$s paylaşılamadı", - "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Nextcloud Talk etkinleştirilmemiş olduğundan %sNextcloud Talk ile parola gönderilerek paylaşılamadı", "You cannot share to a Circle if the app is not enabled" : "Uygulama etkinleştirilmemiş ise bir Çevre ile paylaşamazsınız", "Please specify a valid circle" : "Lütfen geçerli bir çevre belirtin", "Sharing %s failed because the back end does not support room shares" : "Arka uç oda paylaşımlarına izin vermediğinden %s paylaşılamadı", diff --git a/apps/files_sharing/l10n/tr.json b/apps/files_sharing/l10n/tr.json index dff28b90a41..55f6821339c 100644 --- a/apps/files_sharing/l10n/tr.json +++ b/apps/files_sharing/l10n/tr.json @@ -86,9 +86,9 @@ "Public link sharing is disabled by the administrator" : "Herkese açık bağlantı paylaşımı yönetici tarafından devre dışı bırakılmış", "Public upload disabled by the administrator" : "Herkese açık yükleme yönetici tarafından devre dışı bırakılmış", "Public upload is only possible for publicly shared folders" : "Herkese açık yükleme ancak herkese açık paylaşılmış klasörlere yapılabilir", + "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Nextcloud Talk etkinleştirilmemiş olduğundan %sNextcloud Talk ile parola gönderilerek paylaşılamadı", "Invalid date, date format must be YYYY-MM-DD" : "Tarih geçersiz. Tarih biçimi YYYY-AA-GG olmalıdır", "Sharing %1$s failed because the back end does not allow shares from type %2$s" : "Yönetim bölümünden %2$s türündeki paylaşımlar yapılamadığından %1$s paylaşılamadı", - "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "Nextcloud Talk etkinleştirilmemiş olduğundan %sNextcloud Talk ile parola gönderilerek paylaşılamadı", "You cannot share to a Circle if the app is not enabled" : "Uygulama etkinleştirilmemiş ise bir Çevre ile paylaşamazsınız", "Please specify a valid circle" : "Lütfen geçerli bir çevre belirtin", "Sharing %s failed because the back end does not support room shares" : "Arka uç oda paylaşımlarına izin vermediğinden %s paylaşılamadı", diff --git a/apps/files_sharing/l10n/zh_CN.js b/apps/files_sharing/l10n/zh_CN.js index dbed6a651ca..48119ecd7ad 100644 --- a/apps/files_sharing/l10n/zh_CN.js +++ b/apps/files_sharing/l10n/zh_CN.js @@ -87,8 +87,8 @@ OC.L10N.register( "Public link sharing is disabled by the administrator" : "公共链接共享已被管理员禁用", "Public upload disabled by the administrator" : "公共上传已被管理员禁用", "Public upload is only possible for publicly shared folders" : "公共上传仅适用于公共共享文件夹", - "Invalid date, date format must be YYYY-MM-DD" : "无效的日期,日期格式必须是 YYYY-MM-DD", "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "由于 Nextcloud Talk 没有启用, 所以通过 Nextcloud Talk 发送 %s 共享密码失败.", + "Invalid date, date format must be YYYY-MM-DD" : "无效的日期,日期格式必须是 YYYY-MM-DD", "You cannot share to a Circle if the app is not enabled" : "如果这个应用程序不可用,你不能共享到圈子", "Please specify a valid circle" : "请指明一个可用圈子", "Sharing %s failed because the back end does not support room shares" : "由于后端不支持房间共享, 所以共享 %s 失败.", diff --git a/apps/files_sharing/l10n/zh_CN.json b/apps/files_sharing/l10n/zh_CN.json index 47c506af8a1..8bd4c81b4de 100644 --- a/apps/files_sharing/l10n/zh_CN.json +++ b/apps/files_sharing/l10n/zh_CN.json @@ -85,8 +85,8 @@ "Public link sharing is disabled by the administrator" : "公共链接共享已被管理员禁用", "Public upload disabled by the administrator" : "公共上传已被管理员禁用", "Public upload is only possible for publicly shared folders" : "公共上传仅适用于公共共享文件夹", - "Invalid date, date format must be YYYY-MM-DD" : "无效的日期,日期格式必须是 YYYY-MM-DD", "Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled" : "由于 Nextcloud Talk 没有启用, 所以通过 Nextcloud Talk 发送 %s 共享密码失败.", + "Invalid date, date format must be YYYY-MM-DD" : "无效的日期,日期格式必须是 YYYY-MM-DD", "You cannot share to a Circle if the app is not enabled" : "如果这个应用程序不可用,你不能共享到圈子", "Please specify a valid circle" : "请指明一个可用圈子", "Sharing %s failed because the back end does not support room shares" : "由于后端不支持房间共享, 所以共享 %s 失败.", diff --git a/apps/files_sharing/lib/Controller/ShareAPIController.php b/apps/files_sharing/lib/Controller/ShareAPIController.php index ff19c35e2b5..42d0218de8c 100644 --- a/apps/files_sharing/lib/Controller/ShareAPIController.php +++ b/apps/files_sharing/lib/Controller/ShareAPIController.php @@ -160,6 +160,7 @@ class ShareAPIController extends OCSController { 'token' => null, 'uid_file_owner' => $share->getShareOwner(), 'note' => $share->getNote(), + 'label' => $share->getLabel(), 'displayname_file_owner' => $shareOwner !== null ? $shareOwner->getDisplayName() : $share->getShareOwner(), ]; @@ -211,6 +212,8 @@ class ShareAPIController extends OCSController { $result['share_with'] = $share->getPassword(); $result['share_with_displayname'] = $share->getPassword(); + $result['send_password_by_talk'] = $share->getSendPasswordByTalk(); + $result['token'] = $share->getToken(); $result['url'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare', ['token' => $share->getToken()]); @@ -252,6 +255,7 @@ class ShareAPIController extends OCSController { $result['mail_send'] = $share->getMailSend() ? 1 : 0; + $result['hide_download'] = $share->getHideDownload() ? 1 : 0; return $result; } @@ -353,15 +357,17 @@ class ShareAPIController extends OCSController { * @param string $shareWith * @param string $publicUpload * @param string $password - * @param bool $sendPasswordByTalk + * @param string $sendPasswordByTalk * @param string $expireDate + * @param string $label * * @return DataResponse - * @throws OCSNotFoundException - * @throws OCSForbiddenException + * @throws NotFoundException * @throws OCSBadRequestException * @throws OCSException - * + * @throws OCSForbiddenException + * @throws OCSNotFoundException + * @throws \OCP\Files\InvalidPathException * @suppress PhanUndeclaredClassMethod */ public function createShare( @@ -372,7 +378,8 @@ class ShareAPIController extends OCSController { string $publicUpload = 'false', string $password = '', string $sendPasswordByTalk = null, - string $expireDate = '' + string $expireDate = '', + string $label = '' ): DataResponse { $share = $this->shareManager->newShare(); @@ -446,15 +453,6 @@ class ShareAPIController extends OCSController { throw new OCSNotFoundException($this->l->t('Public link sharing is disabled by the administrator')); } - /* - * For now we only allow 1 link share. - * Return the existing link share if this is a duplicate - */ - $existingShares = $this->shareManager->getSharesBy($this->currentUser, Share::SHARE_TYPE_LINK, $path, false, 1, 0); - if (!empty($existingShares)) { - return new DataResponse($this->formatShare($existingShares[0])); - } - if ($publicUpload === 'true') { // Check if public upload is allowed if (!$this->shareManager->shareApiLinkAllowPublicUpload()) { @@ -481,6 +479,19 @@ class ShareAPIController extends OCSController { $share->setPassword($password); } + + if (!empty($label)) { + $share->setLabel($label); + } + + if ($sendPasswordByTalk === 'true') { + if (!$this->appManager->isEnabledForUser('spreed')) { + throw new OCSForbiddenException($this->l->t('Sharing %s sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled', [$path->getPath()])); + } + + $share->setSendPasswordByTalk(true); + } + //Expire date if ($expireDate !== '') { try { @@ -745,6 +756,8 @@ class ShareAPIController extends OCSController { * @param string $publicUpload * @param string $expireDate * @param string $note + * @param string $label + * @param string $hideDownload * @return DataResponse * @throws LockedException * @throws NotFoundException @@ -759,7 +772,9 @@ class ShareAPIController extends OCSController { string $sendPasswordByTalk = null, string $publicUpload = null, string $expireDate = null, - string $note = null + string $note = null, + string $label = null, + string $hideDownload = null ): DataResponse { try { $share = $this->getShareById($id); @@ -773,7 +788,15 @@ class ShareAPIController extends OCSController { throw new OCSNotFoundException($this->l->t('Wrong share ID, share doesn\'t exist')); } - if ($permissions === null && $password === null && $sendPasswordByTalk === null && $publicUpload === null && $expireDate === null && $note === null) { + if ($permissions === null && + $password === null && + $sendPasswordByTalk === null && + $publicUpload === null && + $expireDate === null && + $note === null && + $label === null && + $hideDownload === null + ) { throw new OCSBadRequestException($this->l->t('Wrong or no update parameter given')); } @@ -786,6 +809,13 @@ class ShareAPIController extends OCSController { */ if ($share->getShareType() === Share::SHARE_TYPE_LINK) { + // Update hide download state + if ($hideDownload === 'true') { + $share->setHideDownload(true); + } else if ($hideDownload === 'false') { + $share->setHideDownload(false); + } + $newPermissions = null; if ($publicUpload === 'true') { $newPermissions = Constants::PERMISSION_READ | Constants::PERMISSION_CREATE | Constants::PERMISSION_UPDATE | Constants::PERMISSION_DELETE; @@ -850,6 +880,19 @@ class ShareAPIController extends OCSController { $share->setPassword($password); } + if ($label !== null) { + $share->setLabel($label); + } + + if ($sendPasswordByTalk === 'true') { + if (!$this->appManager->isEnabledForUser('spreed')) { + throw new OCSForbiddenException($this->l->t('Sharing sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled')); + } + + $share->setSendPasswordByTalk(true); + } else if ($sendPasswordByTalk !== null) { + $share->setSendPasswordByTalk(false); + } } else { if ($permissions !== null) { $permissions = (int)$permissions; diff --git a/apps/files_sharing/lib/Controller/ShareController.php b/apps/files_sharing/lib/Controller/ShareController.php index 1e3cbb51028..1a92000a5f6 100644 --- a/apps/files_sharing/lib/Controller/ShareController.php +++ b/apps/files_sharing/lib/Controller/ShareController.php @@ -321,6 +321,7 @@ class ShareController extends AuthPublicShareController { $shareTmpl['dir'] = ''; $shareTmpl['nonHumanFileSize'] = $share->getNode()->getSize(); $shareTmpl['fileSize'] = \OCP\Util::humanFileSize($share->getNode()->getSize()); + $shareTmpl['hideDownload'] = $share->getHideDownload(); // Show file list $hideFileList = false; @@ -444,12 +445,14 @@ class ShareController extends AuthPublicShareController { $response = new PublicTemplateResponse($this->appName, 'public', $shareTmpl); $response->setHeaderTitle($shareTmpl['filename']); $response->setHeaderDetails($this->l10n->t('shared by %s', [$shareTmpl['displayName']])); - $response->setHeaderActions([ - new SimpleMenuAction('download', $this->l10n->t('Download'), 'icon-download-white', $shareTmpl['downloadURL'], 0), - new SimpleMenuAction('download', $this->l10n->t('Download'), 'icon-download', $shareTmpl['downloadURL'], 10, $shareTmpl['fileSize']), - new LinkMenuAction($this->l10n->t('Direct link'), 'icon-public', $shareTmpl['previewURL']), - new ExternalShareMenuAction($this->l10n->t('Add to your Nextcloud'), 'icon-external', $shareTmpl['owner'], $shareTmpl['displayName'], $shareTmpl['filename']), - ]); + if (!$share->getHideDownload()) { + $response->setHeaderActions([ + new SimpleMenuAction('download', $this->l10n->t('Download'), 'icon-download-white', $shareTmpl['downloadURL'], 0), + new SimpleMenuAction('download', $this->l10n->t('Download'), 'icon-download', $shareTmpl['downloadURL'], 10, $shareTmpl['fileSize']), + new LinkMenuAction($this->l10n->t('Direct link'), 'icon-public', $shareTmpl['previewURL']), + new ExternalShareMenuAction($this->l10n->t('Add to your Nextcloud'), 'icon-external', $shareTmpl['owner'], $shareTmpl['displayName'], $shareTmpl['filename']), + ]); + } $response->setContentSecurityPolicy($csp); diff --git a/apps/files_sharing/templates/public.php b/apps/files_sharing/templates/public.php index da80f8d1377..4487e63f2de 100644 --- a/apps/files_sharing/templates/public.php +++ b/apps/files_sharing/templates/public.php @@ -11,13 +11,16 @@ <input type="hidden" id="filesApp" name="filesApp" value="1"> <input type="hidden" id="isPublic" name="isPublic" value="1"> <input type="hidden" name="dir" value="<?php p($_['dir']) ?>" id="dir"> -<input type="hidden" name="downloadURL" value="<?php p($_['downloadURL']) ?>" id="downloadURL"> +<?php if (!$_['hideDownload']): ?> + <input type="hidden" name="downloadURL" value="<?php p($_['downloadURL']) ?>" id="downloadURL"> +<?php endif; ?> <input type="hidden" name="previewURL" value="<?php p($_['previewURL']) ?>" id="previewURL"> <input type="hidden" name="sharingToken" value="<?php p($_['sharingToken']) ?>" id="sharingToken"> <input type="hidden" name="filename" value="<?php p($_['filename']) ?>" id="filename"> <input type="hidden" name="mimetype" value="<?php p($_['mimetype']) ?>" id="mimetype"> <input type="hidden" name="previewSupported" value="<?php p($_['previewSupported'] ? 'true' : 'false'); ?>" id="previewSupported"> <input type="hidden" name="mimetypeIcon" value="<?php p(\OC::$server->getMimeTypeDetector()->mimeTypeIcon($_['mimetype'])); ?>" id="mimetypeIcon"> +<input type="hidden" name="hideDownload" value="<?php p($_['hideDownload'] ? 'true' : 'false'); ?>" id="hideDownload"> <?php $upload_max_filesize = OC::$server->getIniWrapper()->getBytes('upload_max_filesize'); $post_max_size = OC::$server->getIniWrapper()->getBytes('post_max_size'); @@ -58,7 +61,7 @@ $maxUploadFilesize = min($upload_max_filesize, $post_max_size); <!-- Preview frame is filled via JS to support SVG images for modern browsers --> <div id="imgframe"></div> <?php endif; ?> - <?php if ($_['previewURL'] === $_['downloadURL']): ?> + <?php if ($_['previewURL'] === $_['downloadURL'] && !$_['hideDownload']): ?> <div class="directDownload"> <a href="<?php p($_['downloadURL']); ?>" id="downloadFile" class="button"> <span class="icon icon-download"></span> @@ -97,4 +100,4 @@ $maxUploadFilesize = min($upload_max_filesize, $post_max_size); data-url="<?php p(\OC::$server->getURLGenerator()->linkTo('files', 'ajax/upload.php')); ?>" /> </div> <?php endif; ?> -</div>
\ No newline at end of file +</div> diff --git a/apps/files_sharing/tests/Controller/ShareAPIControllerTest.php b/apps/files_sharing/tests/Controller/ShareAPIControllerTest.php index 15c4071bc46..efc252d49d7 100644 --- a/apps/files_sharing/tests/Controller/ShareAPIControllerTest.php +++ b/apps/files_sharing/tests/Controller/ShareAPIControllerTest.php @@ -253,7 +253,7 @@ class ShareAPIControllerTest extends TestCase { public function createShare($id, $shareType, $sharedWith, $sharedBy, $shareOwner, $path, $permissions, $shareTime, $expiration, $parent, $target, $mail_send, $note = '', $token=null, - $password=null) { + $password=null, $label = '') { $share = $this->getMockBuilder(IShare::class)->getMock(); $share->method('getId')->willReturn($id); $share->method('getShareType')->willReturn($shareType); @@ -263,6 +263,7 @@ class ShareAPIControllerTest extends TestCase { $share->method('getNode')->willReturn($path); $share->method('getPermissions')->willReturn($permissions); $share->method('getNote')->willReturn($note); + $share->method('getLabel')->willReturn($label); $time = new \DateTime(); $time->setTimestamp($shareTime); $share->method('getShareTime')->willReturn($time); @@ -351,8 +352,10 @@ class ShareAPIControllerTest extends TestCase { 'mail_send' => 0, 'uid_file_owner' => 'ownerId', 'note' => 'personal note', + 'label' => '', 'displayname_file_owner' => 'ownerDisplay', 'mimetype' => 'myMimeType', + 'hide_download' => 0, ]; $data[] = [$share, $expected]; @@ -395,8 +398,10 @@ class ShareAPIControllerTest extends TestCase { 'mail_send' => 0, 'uid_file_owner' => 'ownerId', 'note' => 'personal note', + 'label' => '', 'displayname_file_owner' => 'ownerDisplay', 'mimetype' => 'myFolderMimeType', + 'hide_download' => 0, ]; $data[] = [$share, $expected]; @@ -417,13 +422,15 @@ class ShareAPIControllerTest extends TestCase { 0, 'personal note', 'token', - 'password' + 'password', + 'first link share' ); $expected = [ 'id' => 101, 'share_type' => \OCP\Share::SHARE_TYPE_LINK, 'share_with' => 'password', 'share_with_displayname' => 'password', + 'send_password_by_talk' => false, 'uid_owner' => 'initiatorId', 'displayname_owner' => 'initiatorDisplay', 'item_type' => 'folder', @@ -443,8 +450,10 @@ class ShareAPIControllerTest extends TestCase { 'url' => 'url', 'uid_file_owner' => 'ownerId', 'note' => 'personal note', + 'label' => 'first link share', 'displayname_file_owner' => 'ownerDisplay', 'mimetype' => 'myFolderMimeType', + 'hide_download' => 0, ]; $data[] = [$share, $expected]; @@ -1127,6 +1136,71 @@ class ShareAPIControllerTest extends TestCase { $this->assertEquals($expected->getData(), $result->getData()); } + public function testCreateShareLinkSendPasswordByTalk() { + $ocs = $this->mockFormatShare(); + + $path = $this->getMockBuilder(Folder::class)->getMock(); + $storage = $this->getMockBuilder(Storage::class)->getMock(); + $storage->method('instanceOfStorage') + ->with('OCA\Files_Sharing\External\Storage') + ->willReturn(false); + $path->method('getStorage')->willReturn($storage); + $this->rootFolder->method('getUserFolder')->with($this->currentUser)->will($this->returnSelf()); + $this->rootFolder->method('get')->with('valid-path')->willReturn($path); + + $this->shareManager->method('newShare')->willReturn(\OC::$server->getShareManager()->newShare()); + $this->shareManager->method('shareApiAllowLinks')->willReturn(true); + $this->shareManager->method('shareApiLinkAllowPublicUpload')->willReturn(true); + + $this->appManager->method('isEnabledForUser')->with('spreed')->willReturn(true); + + $this->shareManager->expects($this->once())->method('createShare')->with( + $this->callback(function (\OCP\Share\IShare $share) use ($path) { + return $share->getNode() === $path && + $share->getShareType() === \OCP\Share::SHARE_TYPE_LINK && + $share->getPermissions() === \OCP\Constants::PERMISSION_READ && + $share->getSharedBy() === 'currentUser' && + $share->getPassword() === 'password' && + $share->getSendPasswordByTalk() === true && + $share->getExpirationDate() === null; + }) + )->will($this->returnArgument(0)); + + $expected = new DataResponse([]); + $result = $ocs->createShare('valid-path', \OCP\Constants::PERMISSION_ALL, \OCP\Share::SHARE_TYPE_LINK, null, 'false', 'password', 'true', ''); + + $this->assertInstanceOf(get_class($expected), $result); + $this->assertEquals($expected->getData(), $result->getData()); + } + + /** + * @expectedException \OCP\AppFramework\OCS\OCSForbiddenException + * @expectedExceptionMessage Sharing valid-path sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled + */ + public function testCreateShareLinkSendPasswordByTalkWithTalkDisabled() { + $ocs = $this->mockFormatShare(); + + $path = $this->getMockBuilder(Folder::class)->getMock(); + $storage = $this->getMockBuilder(Storage::class)->getMock(); + $storage->method('instanceOfStorage') + ->with('OCA\Files_Sharing\External\Storage') + ->willReturn(false); + $path->method('getStorage')->willReturn($storage); + $path->method('getPath')->willReturn('valid-path'); + $this->rootFolder->method('getUserFolder')->with($this->currentUser)->will($this->returnSelf()); + $this->rootFolder->method('get')->with('valid-path')->willReturn($path); + + $this->shareManager->method('newShare')->willReturn(\OC::$server->getShareManager()->newShare()); + $this->shareManager->method('shareApiAllowLinks')->willReturn(true); + $this->shareManager->method('shareApiLinkAllowPublicUpload')->willReturn(true); + + $this->appManager->method('isEnabledForUser')->with('spreed')->willReturn(false); + + $this->shareManager->expects($this->never())->method('createShare'); + + $ocs->createShare('valid-path', \OCP\Constants::PERMISSION_ALL, \OCP\Share::SHARE_TYPE_LINK, null, 'false', 'password', 'true', ''); + } + public function testCreateShareValidExpireDate() { $ocs = $this->mockFormatShare(); @@ -1504,6 +1578,9 @@ class ShareAPIControllerTest extends TestCase { ->setShareType(\OCP\Share::SHARE_TYPE_LINK) ->setPassword('password') ->setExpirationDate(new \DateTime()) + ->setNote('note') + ->setLabel('label') + ->setHideDownload(true) ->setPermissions(\OCP\Constants::PERMISSION_ALL) ->setNode($node); @@ -1517,7 +1594,12 @@ class ShareAPIControllerTest extends TestCase { $this->callback(function (\OCP\Share\IShare $share) { return $share->getPermissions() === \OCP\Constants::PERMISSION_READ && $share->getPassword() === null && - $share->getExpirationDate() === null; + $share->getExpirationDate() === null && + // Once set a note or a label are never back to null, only to an + // empty string. + $share->getNote() === '' && + $share->getLabel() === '' && + $share->getHideDownload() === false; }) )->will($this->returnArgument(0)); @@ -1525,7 +1607,7 @@ class ShareAPIControllerTest extends TestCase { ->willReturn([]); $expected = new DataResponse([]); - $result = $ocs->updateShare(42, null, '', null, 'false', ''); + $result = $ocs->updateShare(42, null, '', null, 'false', '', '', '', 'false'); $this->assertInstanceOf(get_class($expected), $result); $this->assertEquals($expected->getData(), $result->getData()); @@ -1552,7 +1634,10 @@ class ShareAPIControllerTest extends TestCase { return $share->getPermissions() === (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE) && $share->getPassword() === 'password' && - $share->getExpirationDate() == $date; + $share->getExpirationDate() == $date && + $share->getNote() === 'note' && + $share->getLabel() === 'label' && + $share->getHideDownload() === true; }) )->will($this->returnArgument(0)); @@ -1560,7 +1645,7 @@ class ShareAPIControllerTest extends TestCase { ->willReturn([]); $expected = new DataResponse([]); - $result = $ocs->updateShare(42, null, 'password', null, 'true', '2000-01-01'); + $result = $ocs->updateShare(42, null, 'password', null, 'true', '2000-01-01', 'note', 'label', 'true'); $this->assertInstanceOf(get_class($expected), $result); $this->assertEquals($expected->getData(), $result->getData()); @@ -1692,7 +1777,11 @@ class ShareAPIControllerTest extends TestCase { ->setSharedBy($this->currentUser) ->setShareType(\OCP\Share::SHARE_TYPE_LINK) ->setPassword('password') + ->setSendPasswordByTalk(true) ->setExpirationDate($date) + ->setNote('note') + ->setLabel('label') + ->setHideDownload(true) ->setPermissions(\OCP\Constants::PERMISSION_ALL) ->setNode($node); @@ -1706,12 +1795,194 @@ class ShareAPIControllerTest extends TestCase { $this->callback(function (\OCP\Share\IShare $share) use ($date) { return $share->getPermissions() === \OCP\Constants::PERMISSION_ALL && $share->getPassword() === 'newpassword' && - $share->getExpirationDate() === $date; + $share->getSendPasswordByTalk() === true && + $share->getExpirationDate() === $date && + $share->getNote() === 'note' && + $share->getLabel() === 'label' && + $share->getHideDownload() === true; }) )->will($this->returnArgument(0)); $expected = new DataResponse([]); - $result = $ocs->updateShare(42, null, 'newpassword', null, null, null); + $result = $ocs->updateShare(42, null, 'newpassword', null, null, null, null, null, null); + + $this->assertInstanceOf(get_class($expected), $result); + $this->assertEquals($expected->getData(), $result->getData()); + } + + public function testUpdateLinkShareSendPasswordByTalkDoesNotChangeOther() { + $ocs = $this->mockFormatShare(); + + $date = new \DateTime('2000-01-01'); + $date->setTime(0,0,0); + + $node = $this->getMockBuilder(File::class)->getMock(); + $share = $this->newShare(); + $share->setPermissions(\OCP\Constants::PERMISSION_ALL) + ->setSharedBy($this->currentUser) + ->setShareType(\OCP\Share::SHARE_TYPE_LINK) + ->setPassword('password') + ->setSendPasswordByTalk(false) + ->setExpirationDate($date) + ->setNote('note') + ->setLabel('label') + ->setHideDownload(true) + ->setPermissions(\OCP\Constants::PERMISSION_ALL) + ->setNode($node); + + $node->expects($this->once()) + ->method('lock') + ->with(\OCP\Lock\ILockingProvider::LOCK_SHARED); + + $this->shareManager->method('getShareById')->with('ocinternal:42')->willReturn($share); + + $this->appManager->method('isEnabledForUser')->with('spreed')->willReturn(true); + + $this->shareManager->expects($this->once())->method('updateShare')->with( + $this->callback(function (\OCP\Share\IShare $share) use ($date) { + return $share->getPermissions() === \OCP\Constants::PERMISSION_ALL && + $share->getPassword() === 'password' && + $share->getSendPasswordByTalk() === true && + $share->getExpirationDate() === $date && + $share->getNote() === 'note' && + $share->getLabel() === 'label' && + $share->getHideDownload() === true; + }) + )->will($this->returnArgument(0)); + + $expected = new DataResponse([]); + $result = $ocs->updateShare(42, null, null, 'true', null, null, null, null, null); + + $this->assertInstanceOf(get_class($expected), $result); + $this->assertEquals($expected->getData(), $result->getData()); + } + + /** + * @expectedException \OCP\AppFramework\OCS\OCSForbiddenException + * @expectedExceptionMessage Sharing sending the password by Nextcloud Talk failed because Nextcloud Talk is not enabled + */ + public function testUpdateLinkShareSendPasswordByTalkWithTalkDisabledDoesNotChangeOther() { + $ocs = $this->mockFormatShare(); + + $date = new \DateTime('2000-01-01'); + $date->setTime(0,0,0); + + $node = $this->getMockBuilder(File::class)->getMock(); + $share = $this->newShare(); + $share->setPermissions(\OCP\Constants::PERMISSION_ALL) + ->setSharedBy($this->currentUser) + ->setShareType(\OCP\Share::SHARE_TYPE_LINK) + ->setPassword('password') + ->setSendPasswordByTalk(false) + ->setExpirationDate($date) + ->setNote('note') + ->setLabel('label') + ->setHideDownload(true) + ->setPermissions(\OCP\Constants::PERMISSION_ALL) + ->setNode($node); + + $node->expects($this->once()) + ->method('lock') + ->with(\OCP\Lock\ILockingProvider::LOCK_SHARED); + + $this->shareManager->method('getShareById')->with('ocinternal:42')->willReturn($share); + + $this->appManager->method('isEnabledForUser')->with('spreed')->willReturn(false); + + $this->shareManager->expects($this->never())->method('updateShare'); + + $ocs->updateShare(42, null, null, 'true', null, null, null, null, null); + } + + public function testUpdateLinkShareDoNotSendPasswordByTalkDoesNotChangeOther() { + $ocs = $this->mockFormatShare(); + + $date = new \DateTime('2000-01-01'); + $date->setTime(0,0,0); + + $node = $this->getMockBuilder(File::class)->getMock(); + $share = $this->newShare(); + $share->setPermissions(\OCP\Constants::PERMISSION_ALL) + ->setSharedBy($this->currentUser) + ->setShareType(\OCP\Share::SHARE_TYPE_LINK) + ->setPassword('password') + ->setSendPasswordByTalk(true) + ->setExpirationDate($date) + ->setNote('note') + ->setLabel('label') + ->setHideDownload(true) + ->setPermissions(\OCP\Constants::PERMISSION_ALL) + ->setNode($node); + + $node->expects($this->once()) + ->method('lock') + ->with(\OCP\Lock\ILockingProvider::LOCK_SHARED); + + $this->shareManager->method('getShareById')->with('ocinternal:42')->willReturn($share); + + $this->appManager->method('isEnabledForUser')->with('spreed')->willReturn(true); + + $this->shareManager->expects($this->once())->method('updateShare')->with( + $this->callback(function (\OCP\Share\IShare $share) use ($date) { + return $share->getPermissions() === \OCP\Constants::PERMISSION_ALL && + $share->getPassword() === 'password' && + $share->getSendPasswordByTalk() === false && + $share->getExpirationDate() === $date && + $share->getNote() === 'note' && + $share->getLabel() === 'label' && + $share->getHideDownload() === true; + }) + )->will($this->returnArgument(0)); + + $expected = new DataResponse([]); + $result = $ocs->updateShare(42, null, null, 'false', null, null, null, null, null); + + $this->assertInstanceOf(get_class($expected), $result); + $this->assertEquals($expected->getData(), $result->getData()); + } + + public function testUpdateLinkShareDoNotSendPasswordByTalkWithTalkDisabledDoesNotChangeOther() { + $ocs = $this->mockFormatShare(); + + $date = new \DateTime('2000-01-01'); + $date->setTime(0,0,0); + + $node = $this->getMockBuilder(File::class)->getMock(); + $share = $this->newShare(); + $share->setPermissions(\OCP\Constants::PERMISSION_ALL) + ->setSharedBy($this->currentUser) + ->setShareType(\OCP\Share::SHARE_TYPE_LINK) + ->setPassword('password') + ->setSendPasswordByTalk(true) + ->setExpirationDate($date) + ->setNote('note') + ->setLabel('label') + ->setHideDownload(true) + ->setPermissions(\OCP\Constants::PERMISSION_ALL) + ->setNode($node); + + $node->expects($this->once()) + ->method('lock') + ->with(\OCP\Lock\ILockingProvider::LOCK_SHARED); + + $this->shareManager->method('getShareById')->with('ocinternal:42')->willReturn($share); + + $this->appManager->method('isEnabledForUser')->with('spreed')->willReturn(false); + + $this->shareManager->expects($this->once())->method('updateShare')->with( + $this->callback(function (\OCP\Share\IShare $share) use ($date) { + return $share->getPermissions() === \OCP\Constants::PERMISSION_ALL && + $share->getPassword() === 'password' && + $share->getSendPasswordByTalk() === false && + $share->getExpirationDate() === $date && + $share->getNote() === 'note' && + $share->getLabel() === 'label' && + $share->getHideDownload() === true; + }) + )->will($this->returnArgument(0)); + + $expected = new DataResponse([]); + $result = $ocs->updateShare(42, null, null, 'false', null, null, null, null, null); $this->assertInstanceOf(get_class($expected), $result); $this->assertEquals($expected->getData(), $result->getData()); @@ -1726,7 +1997,11 @@ class ShareAPIControllerTest extends TestCase { ->setSharedBy($this->currentUser) ->setShareType(\OCP\Share::SHARE_TYPE_LINK) ->setPassword('password') + ->setSendPasswordByTalk(true) ->setExpirationDate(new \DateTime()) + ->setNote('note') + ->setLabel('label') + ->setHideDownload(true) ->setPermissions(\OCP\Constants::PERMISSION_ALL) ->setNode($node); @@ -1743,12 +2018,16 @@ class ShareAPIControllerTest extends TestCase { return $share->getPermissions() === \OCP\Constants::PERMISSION_ALL && $share->getPassword() === 'password' && - $share->getExpirationDate() == $date; + $share->getSendPasswordByTalk() === true && + $share->getExpirationDate() == $date && + $share->getNote() === 'note' && + $share->getLabel() === 'label' && + $share->getHideDownload() === true; }) )->will($this->returnArgument(0)); $expected = new DataResponse([]); - $result = $ocs->updateShare(42, null, null, null, null, '2010-12-23'); + $result = $ocs->updateShare(42, null, null, null, null, '2010-12-23', null, null, null); $this->assertInstanceOf(get_class($expected), $result); $this->assertEquals($expected->getData(), $result->getData()); @@ -1766,7 +2045,11 @@ class ShareAPIControllerTest extends TestCase { ->setSharedBy($this->currentUser) ->setShareType(\OCP\Share::SHARE_TYPE_LINK) ->setPassword('password') + ->setSendPasswordByTalk(true) ->setExpirationDate($date) + ->setNote('note') + ->setLabel('label') + ->setHideDownload(true) ->setPermissions(\OCP\Constants::PERMISSION_ALL) ->setNode($folder); @@ -1777,7 +2060,11 @@ class ShareAPIControllerTest extends TestCase { $this->callback(function (\OCP\Share\IShare $share) use ($date) { return $share->getPermissions() === (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE) && $share->getPassword() === 'password' && - $share->getExpirationDate() === $date; + $share->getSendPasswordByTalk() === true && + $share->getExpirationDate() === $date && + $share->getNote() === 'note' && + $share->getLabel() === 'label' && + $share->getHideDownload() === true; }) )->will($this->returnArgument(0)); @@ -1785,7 +2072,7 @@ class ShareAPIControllerTest extends TestCase { ->willReturn([]); $expected = new DataResponse([]); - $result = $ocs->updateShare(42, null, null, null, 'true', null); + $result = $ocs->updateShare(42, null, null, null, 'true', null, null, null, null); $this->assertInstanceOf(get_class($expected), $result); $this->assertEquals($expected->getData(), $result->getData()); @@ -1803,7 +2090,11 @@ class ShareAPIControllerTest extends TestCase { ->setSharedBy($this->currentUser) ->setShareType(\OCP\Share::SHARE_TYPE_LINK) ->setPassword('password') + ->setSendPasswordByTalk(true) ->setExpirationDate($date) + ->setNote('note') + ->setLabel('label') + ->setHideDownload(true) ->setPermissions(\OCP\Constants::PERMISSION_ALL) ->setNode($folder); @@ -1814,14 +2105,18 @@ class ShareAPIControllerTest extends TestCase { $this->callback(function (\OCP\Share\IShare $share) use ($date) { return $share->getPermissions() === (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE) && $share->getPassword() === 'password' && - $share->getExpirationDate() === $date; + $share->getSendPasswordByTalk() === true && + $share->getExpirationDate() === $date && + $share->getNote() === 'note' && + $share->getLabel() === 'label' && + $share->getHideDownload() === true; }) )->will($this->returnArgument(0)); $this->shareManager->method('getSharedWith')->willReturn([]); $expected = new DataResponse([]); - $result = $ocs->updateShare(42, 7, null, null, null, null); + $result = $ocs->updateShare(42, 7, null, null, null, null, null, null, null); $this->assertInstanceOf(get_class($expected), $result); $this->assertEquals($expected->getData(), $result->getData()); @@ -1839,7 +2134,11 @@ class ShareAPIControllerTest extends TestCase { ->setSharedBy($this->currentUser) ->setShareType(\OCP\Share::SHARE_TYPE_LINK) ->setPassword('password') + ->setSendPasswordByTalk(true) ->setExpirationDate($date) + ->setNote('note') + ->setLabel('label') + ->setHideDownload(true) ->setPermissions(\OCP\Constants::PERMISSION_READ) ->setNode($folder); @@ -1850,14 +2149,18 @@ class ShareAPIControllerTest extends TestCase { $this->callback(function (\OCP\Share\IShare $share) use ($date) { return $share->getPermissions() === (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE | \OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_DELETE) && $share->getPassword() === 'password' && - $share->getExpirationDate() === $date; + $share->getSendPasswordByTalk() === true && + $share->getExpirationDate() === $date && + $share->getNote() === 'note' && + $share->getLabel() === 'label' && + $share->getHideDownload() === true; }) )->will($this->returnArgument(0)); $this->shareManager->method('getSharedWith')->willReturn([]); $expected = new DataResponse([]); - $result = $ocs->updateShare(42, 31, null, null, null, null); + $result = $ocs->updateShare(42, 31, null, null, null, null, null, null, null); $this->assertInstanceOf(get_class($expected), $result); $this->assertEquals($expected->getData(), $result->getData()); @@ -2173,8 +2476,10 @@ class ShareAPIControllerTest extends TestCase { 'share_with' => 'recipient', 'share_with_displayname' => 'recipient', 'note' => 'personal note', + 'label' => null, 'mail_send' => 0, 'mimetype' => 'myMimeType', + 'hide_download' => 0, ], $share, [], false ]; // User backend up @@ -2192,6 +2497,7 @@ class ShareAPIControllerTest extends TestCase { 'uid_file_owner' => 'owner', 'displayname_file_owner' => 'ownerDN', 'note' => 'personal note', + 'label' => null, 'path' => 'file', 'item_type' => 'file', 'storage_id' => 'storageId', @@ -2204,6 +2510,7 @@ class ShareAPIControllerTest extends TestCase { 'share_with_displayname' => 'recipientDN', 'mail_send' => 0, 'mimetype' => 'myMimeType', + 'hide_download' => 0, ], $share, [ ['owner', $owner], ['initiator', $initiator], @@ -2237,6 +2544,7 @@ class ShareAPIControllerTest extends TestCase { 'uid_file_owner' => 'owner', 'displayname_file_owner' => 'owner', 'note' => 'personal note', + 'label' => null, 'path' => 'file', 'item_type' => 'file', 'storage_id' => 'storageId', @@ -2249,6 +2557,7 @@ class ShareAPIControllerTest extends TestCase { 'share_with_displayname' => 'recipient', 'mail_send' => 0, 'mimetype' => 'myMimeType', + 'hide_download' => 0, ], $share, [], false ]; @@ -2280,6 +2589,7 @@ class ShareAPIControllerTest extends TestCase { 'uid_file_owner' => 'owner', 'displayname_file_owner' => 'owner', 'note' => 'personal note', + 'label' => null, 'path' => 'file', 'item_type' => 'file', 'storage_id' => 'storageId', @@ -2292,6 +2602,7 @@ class ShareAPIControllerTest extends TestCase { 'share_with_displayname' => 'recipientGroupDisplayName', 'mail_send' => 0, 'mimetype' => 'myMimeType', + 'hide_download' => 0, ], $share, [], false ]; @@ -2321,6 +2632,7 @@ class ShareAPIControllerTest extends TestCase { 'uid_file_owner' => 'owner', 'displayname_file_owner' => 'owner', 'note' => 'personal note', + 'label' => null, 'path' => 'file', 'item_type' => 'file', 'storage_id' => 'storageId', @@ -2333,6 +2645,55 @@ class ShareAPIControllerTest extends TestCase { 'share_with_displayname' => 'recipientGroup2', 'mail_send' => 0, 'mimetype' => 'myMimeType', + 'hide_download' => 0, + ], $share, [], false + ]; + + $share = \OC::$server->getShareManager()->newShare(); + $share->setShareType(\OCP\Share::SHARE_TYPE_LINK) + ->setSharedBy('initiator') + ->setShareOwner('owner') + ->setPermissions(\OCP\Constants::PERMISSION_READ) + ->setNode($file) + ->setShareTime(new \DateTime('2000-01-01T00:01:02')) + ->setTarget('myTarget') + ->setPassword('mypassword') + ->setExpirationDate(new \DateTime('2001-01-02T00:00:00')) + ->setToken('myToken') + ->setNote('personal note') + ->setLabel('new link share') + ->setId(42); + + $result[] = [ + [ + 'id' => 42, + 'share_type' => \OCP\Share::SHARE_TYPE_LINK, + 'uid_owner' => 'initiator', + 'displayname_owner' => 'initiator', + 'permissions' => 1, + 'stime' => 946684862, + 'parent' => null, + 'expiration' => '2001-01-02 00:00:00', + 'token' => 'myToken', + 'uid_file_owner' => 'owner', + 'displayname_file_owner' => 'owner', + 'note' => 'personal note', + 'label' => 'new link share', + 'path' => 'file', + 'item_type' => 'file', + 'storage_id' => 'storageId', + 'storage' => 100, + 'item_source' => 3, + 'file_source' => 3, + 'file_parent' => 1, + 'file_target' => 'myTarget', + 'share_with' => 'mypassword', + 'share_with_displayname' => 'mypassword', + 'send_password_by_talk' => false, + 'mail_send' => 0, + 'url' => 'myLink', + 'mimetype' => 'myMimeType', + 'hide_download' => 0, ], $share, [], false ]; @@ -2345,9 +2706,11 @@ class ShareAPIControllerTest extends TestCase { ->setShareTime(new \DateTime('2000-01-01T00:01:02')) ->setTarget('myTarget') ->setPassword('mypassword') + ->setSendPasswordByTalk(true) ->setExpirationDate(new \DateTime('2001-01-02T00:00:00')) ->setToken('myToken') ->setNote('personal note') + ->setLabel('new link share') ->setId(42); $result[] = [ @@ -2364,6 +2727,7 @@ class ShareAPIControllerTest extends TestCase { 'uid_file_owner' => 'owner', 'displayname_file_owner' => 'owner', 'note' => 'personal note', + 'label' => 'new link share', 'path' => 'file', 'item_type' => 'file', 'storage_id' => 'storageId', @@ -2374,9 +2738,11 @@ class ShareAPIControllerTest extends TestCase { 'file_target' => 'myTarget', 'share_with' => 'mypassword', 'share_with_displayname' => 'mypassword', + 'send_password_by_talk' => true, 'mail_send' => 0, 'url' => 'myLink', 'mimetype' => 'myMimeType', + 'hide_download' => 0, ], $share, [], false ]; @@ -2406,6 +2772,7 @@ class ShareAPIControllerTest extends TestCase { 'uid_file_owner' => 'owner', 'displayname_file_owner' => 'owner', 'note' => 'personal note', + 'label' => null, 'path' => 'folder', 'item_type' => 'folder', 'storage_id' => 'storageId', @@ -2418,6 +2785,7 @@ class ShareAPIControllerTest extends TestCase { 'share_with_displayname' => 'foobar', 'mail_send' => 0, 'mimetype' => 'myFolderMimeType', + 'hide_download' => 0, ], $share, [], false ]; @@ -2449,6 +2817,7 @@ class ShareAPIControllerTest extends TestCase { 'uid_file_owner' => 'owner', 'displayname_file_owner' => 'owner', 'note' => '', + 'label' => null, 'path' => 'folder', 'item_type' => 'folder', 'storage_id' => 'storageId', @@ -2462,6 +2831,7 @@ class ShareAPIControllerTest extends TestCase { 'share_with_avatar' => 'path/to/the/avatar', 'mail_send' => 0, 'mimetype' => 'myFolderMimeType', + 'hide_download' => 0, ], $share, [], false ]; @@ -2491,6 +2861,7 @@ class ShareAPIControllerTest extends TestCase { 'uid_file_owner' => 'owner', 'displayname_file_owner' => 'owner', 'note' => '', + 'label' => null, 'path' => 'folder', 'item_type' => 'folder', 'storage_id' => 'storageId', @@ -2504,6 +2875,7 @@ class ShareAPIControllerTest extends TestCase { 'share_with_avatar' => '', 'mail_send' => 0, 'mimetype' => 'myFolderMimeType', + 'hide_download' => 0, ], $share, [], false ]; @@ -2533,6 +2905,7 @@ class ShareAPIControllerTest extends TestCase { 'uid_file_owner' => 'owner', 'displayname_file_owner' => 'owner', 'note' => '', + 'label' => null, 'path' => 'folder', 'item_type' => 'folder', 'storage_id' => 'storageId', @@ -2546,6 +2919,7 @@ class ShareAPIControllerTest extends TestCase { 'share_with_avatar' => '', 'mail_send' => 0, 'mimetype' => 'myFolderMimeType', + 'hide_download' => 0, ], $share, [], false ]; @@ -2590,6 +2964,7 @@ class ShareAPIControllerTest extends TestCase { 'uid_file_owner' => 'owner', 'displayname_file_owner' => 'owner', 'note' => '', + 'label' => null, 'path' => 'folder', 'item_type' => 'folder', 'storage_id' => 'storageId', @@ -2603,7 +2978,8 @@ class ShareAPIControllerTest extends TestCase { 'mail_send' => 0, 'mimetype' => 'myFolderMimeType', 'password' => 'password', - 'send_password_by_talk' => false + 'send_password_by_talk' => false, + 'hide_download' => 0, ], $share, [], false ]; @@ -2634,6 +3010,7 @@ class ShareAPIControllerTest extends TestCase { 'uid_file_owner' => 'owner', 'displayname_file_owner' => 'owner', 'note' => '', + 'label' => null, 'path' => 'folder', 'item_type' => 'folder', 'storage_id' => 'storageId', @@ -2647,7 +3024,8 @@ class ShareAPIControllerTest extends TestCase { 'mail_send' => 0, 'mimetype' => 'myFolderMimeType', 'password' => 'password', - 'send_password_by_talk' => true + 'send_password_by_talk' => true, + 'hide_download' => 0, ], $share, [], false ]; @@ -2787,6 +3165,8 @@ class ShareAPIControllerTest extends TestCase { 'share_with_displayname' => '', 'mail_send' => 0, 'mimetype' => 'myMimeType', + 'hide_download' => 0, + 'label' => '', ], $share, false, [] ]; @@ -2828,6 +3208,8 @@ class ShareAPIControllerTest extends TestCase { 'share_with_displayname' => 'recipientRoomName', 'mail_send' => 0, 'mimetype' => 'myMimeType', + 'hide_download' => 0, + 'label' => '', ], $share, true, [ 'share_with_displayname' => 'recipientRoomName' ] diff --git a/apps/files_sharing/tests/Controller/ShareControllerTest.php b/apps/files_sharing/tests/Controller/ShareControllerTest.php index a01560d0288..c5306cbc0ce 100644 --- a/apps/files_sharing/tests/Controller/ShareControllerTest.php +++ b/apps/files_sharing/tests/Controller/ShareControllerTest.php @@ -287,7 +287,8 @@ class ShareControllerTest extends \Test\TestCase { 'shareUrl' => null, 'previewImage' => null, 'previewURL' => 'downloadURL', - 'note' => $note + 'note' => $note, + 'hideDownload' => false ); $csp = new \OCP\AppFramework\Http\ContentSecurityPolicy(); @@ -306,6 +307,120 @@ class ShareControllerTest extends \Test\TestCase { $this->assertEquals($expectedResponse, $response); } + public function testShowShareHideDownload() { + $note = 'personal note'; + + $this->shareController->setToken('token'); + + $owner = $this->getMockBuilder(IUser::class)->getMock(); + $owner->method('getDisplayName')->willReturn('ownerDisplay'); + $owner->method('getUID')->willReturn('ownerUID'); + + $file = $this->getMockBuilder('OCP\Files\File')->getMock(); + $file->method('getName')->willReturn('file1.txt'); + $file->method('getMimetype')->willReturn('text/plain'); + $file->method('getSize')->willReturn(33); + $file->method('isReadable')->willReturn(true); + $file->method('isShareable')->willReturn(true); + + $share = \OC::$server->getShareManager()->newShare(); + $share->setId(42); + $share->setPassword('password') + ->setShareOwner('ownerUID') + ->setNode($file) + ->setNote($note) + ->setTarget('/file1.txt') + ->setHideDownload(true); + + $this->session->method('exists')->with('public_link_authenticated')->willReturn(true); + $this->session->method('get')->with('public_link_authenticated')->willReturn('42'); + + // Even if downloads are disabled the "downloadURL" parameter is + // provided to the template, as it is needed to preview audio and GIF + // files. + $this->urlGenerator->expects($this->at(0)) + ->method('linkToRouteAbsolute') + ->with('files_sharing.sharecontroller.downloadShare', ['token' => 'token']) + ->willReturn('downloadURL'); + + $this->previewManager->method('isMimeSupported')->with('text/plain')->willReturn(true); + + $this->config->method('getSystemValue') + ->willReturnMap( + [ + ['max_filesize_animated_gifs_public_sharing', 10, 10], + ['enable_previews', true, true], + ['preview_max_x', 1024, 1024], + ['preview_max_y', 1024, 1024], + ] + ); + $shareTmpl['maxSizeAnimateGif'] = $this->config->getSystemValue('max_filesize_animated_gifs_public_sharing', 10); + $shareTmpl['previewEnabled'] = $this->config->getSystemValue('enable_previews', true); + + $this->shareManager + ->expects($this->once()) + ->method('getShareByToken') + ->with('token') + ->willReturn($share); + $this->config + ->expects($this->once()) + ->method('getAppValue') + ->with('core', 'shareapi_public_link_disclaimertext', null) + ->willReturn('My disclaimer text'); + + $this->userManager->method('get')->with('ownerUID')->willReturn($owner); + + $this->eventDispatcher->expects($this->once()) + ->method('dispatch') + ->with('OCA\Files_Sharing::loadAdditionalScripts'); + + $this->l10n->expects($this->any()) + ->method('t') + ->will($this->returnCallback(function($text, $parameters) { + return vsprintf($text, $parameters); + })); + + $response = $this->shareController->showShare(); + $sharedTmplParams = array( + 'displayName' => 'ownerDisplay', + 'owner' => 'ownerUID', + 'filename' => 'file1.txt', + 'directory_path' => '/file1.txt', + 'mimetype' => 'text/plain', + 'dirToken' => 'token', + 'sharingToken' => 'token', + 'server2serversharing' => true, + 'protected' => 'true', + 'dir' => '', + 'downloadURL' => 'downloadURL', + 'fileSize' => '33 B', + 'nonHumanFileSize' => 33, + 'maxSizeAnimateGif' => 10, + 'previewSupported' => true, + 'previewEnabled' => true, + 'previewMaxX' => 1024, + 'previewMaxY' => 1024, + 'hideFileList' => false, + 'shareOwner' => 'ownerDisplay', + 'disclaimer' => 'My disclaimer text', + 'shareUrl' => null, + 'previewImage' => null, + 'previewURL' => 'downloadURL', + 'note' => $note, + 'hideDownload' => true + ); + + $csp = new \OCP\AppFramework\Http\ContentSecurityPolicy(); + $csp->addAllowedFrameDomain('\'self\''); + $expectedResponse = new PublicTemplateResponse($this->appName, 'public', $sharedTmplParams); + $expectedResponse->setContentSecurityPolicy($csp); + $expectedResponse->setHeaderTitle($sharedTmplParams['filename']); + $expectedResponse->setHeaderDetails('shared by ' . $sharedTmplParams['displayName']); + $expectedResponse->setHeaderActions([]); + + $this->assertEquals($expectedResponse, $response); + } + /** * @expectedException \OCP\Files\NotFoundException */ diff --git a/apps/files_trashbin/tests/CapabilitiesTest.php b/apps/files_trashbin/tests/CapabilitiesTest.php new file mode 100644 index 00000000000..2a3e89bf414 --- /dev/null +++ b/apps/files_trashbin/tests/CapabilitiesTest.php @@ -0,0 +1,44 @@ +<?php +/** + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\Files_Trashbin\Tests; + +use OCA\Files_Trashbin\Capabilities; +use Test\TestCase; + +class CapabilitiesTest extends TestCase { + + /** @var Capabilities */ + private $capabilities; + + public function setUp() { + parent::setUp(); + $this->capabilities = new Capabilities(); + } + + public function testGetCapabilities() { + $capabilities = [ + 'files' => [ + 'undelete' => true + ] + ]; + + $this->assertSame($capabilities, $this->capabilities->getCapabilities()); + } +}
\ No newline at end of file diff --git a/apps/files_versions/appinfo/info.xml b/apps/files_versions/appinfo/info.xml index d2f873edb07..6d1b3085f80 100644 --- a/apps/files_versions/appinfo/info.xml +++ b/apps/files_versions/appinfo/info.xml @@ -41,4 +41,8 @@ <collection>OCA\Files_Versions\Sabre\RootCollection</collection> </collections> </sabre> + + <versions> + <backend for="OCP\Files\Storage\IStorage">OCA\Files_Versions\Versions\LegacyVersionsBackend</backend> + </versions> </info> diff --git a/apps/files_versions/composer/composer/autoload_classmap.php b/apps/files_versions/composer/composer/autoload_classmap.php index 4bb112b4f11..1283e533914 100644 --- a/apps/files_versions/composer/composer/autoload_classmap.php +++ b/apps/files_versions/composer/composer/autoload_classmap.php @@ -23,4 +23,11 @@ return array( 'OCA\\Files_Versions\\Sabre\\VersionHome' => $baseDir . '/../lib/Sabre/VersionHome.php', 'OCA\\Files_Versions\\Sabre\\VersionRoot' => $baseDir . '/../lib/Sabre/VersionRoot.php', 'OCA\\Files_Versions\\Storage' => $baseDir . '/../lib/Storage.php', + 'OCA\\Files_Versions\\Versions\\BackendNotFoundException' => $baseDir . '/../lib/Versions/BackendNotFoundException.php', + 'OCA\\Files_Versions\\Versions\\IVersion' => $baseDir . '/../lib/Versions/IVersion.php', + 'OCA\\Files_Versions\\Versions\\IVersionBackend' => $baseDir . '/../lib/Versions/IVersionBackend.php', + 'OCA\\Files_Versions\\Versions\\IVersionManager' => $baseDir . '/../lib/Versions/IVersionManager.php', + 'OCA\\Files_Versions\\Versions\\LegacyVersionsBackend' => $baseDir . '/../lib/Versions/LegacyVersionsBackend.php', + 'OCA\\Files_Versions\\Versions\\Version' => $baseDir . '/../lib/Versions/Version.php', + 'OCA\\Files_Versions\\Versions\\VersionManager' => $baseDir . '/../lib/Versions/VersionManager.php', ); diff --git a/apps/files_versions/composer/composer/autoload_static.php b/apps/files_versions/composer/composer/autoload_static.php index 29bc592b41c..6a6b753c2e5 100644 --- a/apps/files_versions/composer/composer/autoload_static.php +++ b/apps/files_versions/composer/composer/autoload_static.php @@ -38,6 +38,13 @@ class ComposerStaticInitFiles_Versions 'OCA\\Files_Versions\\Sabre\\VersionHome' => __DIR__ . '/..' . '/../lib/Sabre/VersionHome.php', 'OCA\\Files_Versions\\Sabre\\VersionRoot' => __DIR__ . '/..' . '/../lib/Sabre/VersionRoot.php', 'OCA\\Files_Versions\\Storage' => __DIR__ . '/..' . '/../lib/Storage.php', + 'OCA\\Files_Versions\\Versions\\BackendNotFoundException' => __DIR__ . '/..' . '/../lib/Versions/BackendNotFoundException.php', + 'OCA\\Files_Versions\\Versions\\IVersion' => __DIR__ . '/..' . '/../lib/Versions/IVersion.php', + 'OCA\\Files_Versions\\Versions\\IVersionBackend' => __DIR__ . '/..' . '/../lib/Versions/IVersionBackend.php', + 'OCA\\Files_Versions\\Versions\\IVersionManager' => __DIR__ . '/..' . '/../lib/Versions/IVersionManager.php', + 'OCA\\Files_Versions\\Versions\\LegacyVersionsBackend' => __DIR__ . '/..' . '/../lib/Versions/LegacyVersionsBackend.php', + 'OCA\\Files_Versions\\Versions\\Version' => __DIR__ . '/..' . '/../lib/Versions/Version.php', + 'OCA\\Files_Versions\\Versions\\VersionManager' => __DIR__ . '/..' . '/../lib/Versions/VersionManager.php', ); public static function getInitializer(ClassLoader $loader) diff --git a/apps/files_versions/js/versionstabview.js b/apps/files_versions/js/versionstabview.js index 61309a30d50..213ee1ae83c 100644 --- a/apps/files_versions/js/versionstabview.js +++ b/apps/files_versions/js/versionstabview.js @@ -38,6 +38,10 @@ return t('files_versions', 'Versions'); }, + getIcon: function() { + return 'icon-history'; + }, + nextPage: function() { if (this._loading) { return; diff --git a/apps/files_versions/l10n/es.js b/apps/files_versions/l10n/es.js index 9a18c7a7abd..13e46f592ba 100644 --- a/apps/files_versions/l10n/es.js +++ b/apps/files_versions/l10n/es.js @@ -5,12 +5,12 @@ OC.L10N.register( "Failed to revert {file} to revision {timestamp}." : "No se ha podido restaurar {file} a versión {timestamp}.", "_%n byte_::_%n bytes_" : ["%n byte","%n bytes"], "Restore" : "Recuperar", - "No other versions available" : "No hay otras versiones disponibles", + "No other versions available" : "No hay más versiones disponibles", "This application automatically maintains older versions of files that are changed." : "Esta aplicación mantiene automáticamente versiones antiguas de los archivos que cambian.", - "This application automatically maintains older versions of files that are changed. When enabled, a hidden versions folder is provisioned in every user’s directory and is used to store old file versions. A user can revert to an older version through the web interface at any time, with the replaced file becoming a version. The app automatically manages the versions folder to ensure the user doesn’t run out of Quota because of versions.\n\t\tIn addition to the expiry of versions, the versions app makes certain never to use more than 50% of the user’s currently available free space. If stored versions exceed this limit, the app will delete the oldest versions first until it meets this limit. More information is available in the Versions documentation." : "Esta aplicación mantiene automáticamente versiones antiguas de los archivos que cambian. Al activarse, se crea una carpeta oculta de versiones que se usa para almacenar versiones antiguas de archivos. Un usuario puede volver a una versión anterior a través de la interfaz web en cualquier momento, con el archivo reemplazado convirtiéndose en una versión. La app maneja automáticamente la carpeta de versiones para asegurarse de que el usuario no se queda sin espacio debido a las versiones.\n\nAdemás de la expiración de versiones, la app de versiones se asegura de no usar nunca más del 50% del espacio libre actualmente disponible para un usuario. Si las versiones almacenadas exceden este límite, la app borrará las versiones más antiguas hasta alcanzar este límite. Más información disponible en la documentación de Versiones.", + "This application automatically maintains older versions of files that are changed. When enabled, a hidden versions folder is provisioned in every user’s directory and is used to store old file versions. A user can revert to an older version through the web interface at any time, with the replaced file becoming a version. The app automatically manages the versions folder to ensure the user doesn’t run out of Quota because of versions.\n\t\tIn addition to the expiry of versions, the versions app makes certain never to use more than 50% of the user’s currently available free space. If stored versions exceed this limit, the app will delete the oldest versions first until it meets this limit. More information is available in the Versions documentation." : "Esta aplicación mantiene automáticamente versiones antiguas de los archivos que cambian. Al activarse, se crea una carpeta oculta de versiones que se usa para almacenar versiones antiguas de archivos. Un usuario puede volver a una versión anterior a través de la interfaz web en cualquier momento, con el archivo reemplazado convirtiéndose en una versión. La app maneja automáticamente la carpeta de versiones para asegurarse de que el usuario no se queda sin espacio debido a las versiones.\n\n\t\tAdemás de la caducidad de versiones, la app de versiones se asegura de no usar nunca más del 50% del espacio libre actualmente disponible para un usuario. Si las versiones almacenadas exceden este límite, la app borrará las versiones más antiguas hasta alcanzar este límite. Más información disponible en la documentación de Versiones.", "Could not revert: %s" : "No se puede restaurar: %s", "No earlier versions available" : "No hay versiones previas disponibles", "More versions …" : "Más versiones ...", - "This application automatically maintains older versions of files that are changed. When enabled, a hidden versions folder is provisioned in every user’s directory and is used to store old file versions. A user can revert to an older version through the web interface at any time, with the replaced file becoming a version. The app automatically manages the versions folder to ensure the user doesn’t run out of Quota because of versions.\nIn addition to the expiry of versions, the versions app makes certain never to use more than 50% of the user’s currently available free space. If stored versions exceed this limit, the app will delete the oldest versions first until it meets this limit. More information is available in the Versions documentation." : "Esta aplicación mantiene automáticamente versiones antiguas de los archivos que cambian. Al activarse, se crea una carpeta oculta de versiones que se usa para almacenar versiones antiguas de archivos. Un usuario puede volver a una versión anterior a través de la interfaz web en cualquier momento, con el archivo reemplazado convirtiéndose en una versión. La app maneja automáticamente la carpeta de versiones para asegurarse de que el usuario no se queda sin espacio debido a las versiones.Además de la expiración de versiones, la app de versiones se asegura de no usar nunca más del 50% del espacio libre actualmente disponible para un usuario. Si las versiones almacenadas exceden este límite, la app borrará las versiones más antiguas hasta alcanzar este límite. Más información disponible en la documentación de Versiones." + "This application automatically maintains older versions of files that are changed. When enabled, a hidden versions folder is provisioned in every user’s directory and is used to store old file versions. A user can revert to an older version through the web interface at any time, with the replaced file becoming a version. The app automatically manages the versions folder to ensure the user doesn’t run out of Quota because of versions.\nIn addition to the expiry of versions, the versions app makes certain never to use more than 50% of the user’s currently available free space. If stored versions exceed this limit, the app will delete the oldest versions first until it meets this limit. More information is available in the Versions documentation." : "Esta aplicación mantiene automáticamente versiones antiguas de los archivos que cambian. Al activarse, se crea una carpeta oculta de versiones que se usa para almacenar versiones antiguas de archivos. Un usuario puede volver a una versión anterior a través de la interfaz web en cualquier momento, con el archivo reemplazado convirtiéndose en una versión. La app maneja automáticamente la carpeta de versiones para asegurarse de que el usuario no se queda sin espacio debido a las versiones.\nAdemás de la caducidad de versiones, la app de versiones se asegura de no usar nunca más del 50% del espacio libre actualmente disponible para un usuario. Si las versiones almacenadas exceden este límite, la app borrará las versiones más antiguas hasta alcanzar este límite. Más información disponible en la documentación de Versiones." }, "nplurals=2; plural=(n != 1);"); diff --git a/apps/files_versions/l10n/es.json b/apps/files_versions/l10n/es.json index 347002f6d19..667eec5a9a2 100644 --- a/apps/files_versions/l10n/es.json +++ b/apps/files_versions/l10n/es.json @@ -3,12 +3,12 @@ "Failed to revert {file} to revision {timestamp}." : "No se ha podido restaurar {file} a versión {timestamp}.", "_%n byte_::_%n bytes_" : ["%n byte","%n bytes"], "Restore" : "Recuperar", - "No other versions available" : "No hay otras versiones disponibles", + "No other versions available" : "No hay más versiones disponibles", "This application automatically maintains older versions of files that are changed." : "Esta aplicación mantiene automáticamente versiones antiguas de los archivos que cambian.", - "This application automatically maintains older versions of files that are changed. When enabled, a hidden versions folder is provisioned in every user’s directory and is used to store old file versions. A user can revert to an older version through the web interface at any time, with the replaced file becoming a version. The app automatically manages the versions folder to ensure the user doesn’t run out of Quota because of versions.\n\t\tIn addition to the expiry of versions, the versions app makes certain never to use more than 50% of the user’s currently available free space. If stored versions exceed this limit, the app will delete the oldest versions first until it meets this limit. More information is available in the Versions documentation." : "Esta aplicación mantiene automáticamente versiones antiguas de los archivos que cambian. Al activarse, se crea una carpeta oculta de versiones que se usa para almacenar versiones antiguas de archivos. Un usuario puede volver a una versión anterior a través de la interfaz web en cualquier momento, con el archivo reemplazado convirtiéndose en una versión. La app maneja automáticamente la carpeta de versiones para asegurarse de que el usuario no se queda sin espacio debido a las versiones.\n\nAdemás de la expiración de versiones, la app de versiones se asegura de no usar nunca más del 50% del espacio libre actualmente disponible para un usuario. Si las versiones almacenadas exceden este límite, la app borrará las versiones más antiguas hasta alcanzar este límite. Más información disponible en la documentación de Versiones.", + "This application automatically maintains older versions of files that are changed. When enabled, a hidden versions folder is provisioned in every user’s directory and is used to store old file versions. A user can revert to an older version through the web interface at any time, with the replaced file becoming a version. The app automatically manages the versions folder to ensure the user doesn’t run out of Quota because of versions.\n\t\tIn addition to the expiry of versions, the versions app makes certain never to use more than 50% of the user’s currently available free space. If stored versions exceed this limit, the app will delete the oldest versions first until it meets this limit. More information is available in the Versions documentation." : "Esta aplicación mantiene automáticamente versiones antiguas de los archivos que cambian. Al activarse, se crea una carpeta oculta de versiones que se usa para almacenar versiones antiguas de archivos. Un usuario puede volver a una versión anterior a través de la interfaz web en cualquier momento, con el archivo reemplazado convirtiéndose en una versión. La app maneja automáticamente la carpeta de versiones para asegurarse de que el usuario no se queda sin espacio debido a las versiones.\n\n\t\tAdemás de la caducidad de versiones, la app de versiones se asegura de no usar nunca más del 50% del espacio libre actualmente disponible para un usuario. Si las versiones almacenadas exceden este límite, la app borrará las versiones más antiguas hasta alcanzar este límite. Más información disponible en la documentación de Versiones.", "Could not revert: %s" : "No se puede restaurar: %s", "No earlier versions available" : "No hay versiones previas disponibles", "More versions …" : "Más versiones ...", - "This application automatically maintains older versions of files that are changed. When enabled, a hidden versions folder is provisioned in every user’s directory and is used to store old file versions. A user can revert to an older version through the web interface at any time, with the replaced file becoming a version. The app automatically manages the versions folder to ensure the user doesn’t run out of Quota because of versions.\nIn addition to the expiry of versions, the versions app makes certain never to use more than 50% of the user’s currently available free space. If stored versions exceed this limit, the app will delete the oldest versions first until it meets this limit. More information is available in the Versions documentation." : "Esta aplicación mantiene automáticamente versiones antiguas de los archivos que cambian. Al activarse, se crea una carpeta oculta de versiones que se usa para almacenar versiones antiguas de archivos. Un usuario puede volver a una versión anterior a través de la interfaz web en cualquier momento, con el archivo reemplazado convirtiéndose en una versión. La app maneja automáticamente la carpeta de versiones para asegurarse de que el usuario no se queda sin espacio debido a las versiones.Además de la expiración de versiones, la app de versiones se asegura de no usar nunca más del 50% del espacio libre actualmente disponible para un usuario. Si las versiones almacenadas exceden este límite, la app borrará las versiones más antiguas hasta alcanzar este límite. Más información disponible en la documentación de Versiones." + "This application automatically maintains older versions of files that are changed. When enabled, a hidden versions folder is provisioned in every user’s directory and is used to store old file versions. A user can revert to an older version through the web interface at any time, with the replaced file becoming a version. The app automatically manages the versions folder to ensure the user doesn’t run out of Quota because of versions.\nIn addition to the expiry of versions, the versions app makes certain never to use more than 50% of the user’s currently available free space. If stored versions exceed this limit, the app will delete the oldest versions first until it meets this limit. More information is available in the Versions documentation." : "Esta aplicación mantiene automáticamente versiones antiguas de los archivos que cambian. Al activarse, se crea una carpeta oculta de versiones que se usa para almacenar versiones antiguas de archivos. Un usuario puede volver a una versión anterior a través de la interfaz web en cualquier momento, con el archivo reemplazado convirtiéndose en una versión. La app maneja automáticamente la carpeta de versiones para asegurarse de que el usuario no se queda sin espacio debido a las versiones.\nAdemás de la caducidad de versiones, la app de versiones se asegura de no usar nunca más del 50% del espacio libre actualmente disponible para un usuario. Si las versiones almacenadas exceden este límite, la app borrará las versiones más antiguas hasta alcanzar este límite. Más información disponible en la documentación de Versiones." },"pluralForm" :"nplurals=2; plural=(n != 1);" }
\ No newline at end of file diff --git a/apps/files_versions/lib/AppInfo/Application.php b/apps/files_versions/lib/AppInfo/Application.php index 340b5ab5cbd..935556221fa 100644 --- a/apps/files_versions/lib/AppInfo/Application.php +++ b/apps/files_versions/lib/AppInfo/Application.php @@ -24,9 +24,10 @@ namespace OCA\Files_Versions\AppInfo; use OCA\DAV\Connector\Sabre\Principal; +use OCA\Files_Versions\Versions\IVersionManager; +use OCA\Files_Versions\Versions\VersionManager; use OCP\AppFramework\App; -use OCA\Files_Versions\Expiration; -use OCP\AppFramework\Utility\ITimeFactory; +use OCP\AppFramework\IAppContainer; use OCA\Files_Versions\Capabilities; class Application extends App { @@ -43,14 +44,45 @@ class Application extends App { /* * Register $principalBackend for the DAV collection */ - $container->registerService('principalBackend', function () { + $container->registerService('principalBackend', function (IAppContainer $c) { + $server = $c->getServer(); return new Principal( - \OC::$server->getUserManager(), - \OC::$server->getGroupManager(), - \OC::$server->getShareManager(), - \OC::$server->getUserSession(), - \OC::$server->getConfig() + $server->getUserManager(), + $server->getGroupManager(), + $server->getShareManager(), + $server->getUserSession(), + $server->getConfig() ); }); + + $container->registerService(IVersionManager::class, function(IAppContainer $c) { + return new VersionManager(); + }); + + $this->registerVersionBackends(); + } + + public function registerVersionBackends() { + $server = $this->getContainer()->getServer(); + $logger = $server->getLogger(); + $appManager = $server->getAppManager(); + /** @var IVersionManager $versionManager */ + $versionManager = $this->getContainer()->getServer()->query(IVersionManager::class); + foreach($appManager->getInstalledApps() as $app) { + $appInfo = $appManager->getAppInfo($app); + if (isset($appInfo['versions'])) { + $backends = $appInfo['versions']; + foreach($backends as $backend) { + $class = $backend['@value']; + $for = $backend['@attributes']['for']; + try { + $backendObject = $server->query($class); + $versionManager->registerBackend($for, $backendObject); + } catch (\Exception $e) { + $logger->logException($e); + } + } + } + } } } diff --git a/apps/files_versions/lib/Controller/PreviewController.php b/apps/files_versions/lib/Controller/PreviewController.php index b8bf464fb3f..f41250a8971 100644 --- a/apps/files_versions/lib/Controller/PreviewController.php +++ b/apps/files_versions/lib/Controller/PreviewController.php @@ -21,45 +21,53 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ + namespace OCA\Files_Versions\Controller; +use OCA\Files_Versions\Versions\IVersionManager; use OCP\AppFramework\Controller; use OCP\AppFramework\Http; use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\Http\FileDisplayResponse; -use OCP\Files\File; -use OCP\Files\Folder; use OCP\Files\IMimeTypeDetector; use OCP\Files\IRootFolder; use OCP\Files\NotFoundException; use OCP\IPreview; use OCP\IRequest; +use OCP\IUserSession; class PreviewController extends Controller { /** @var IRootFolder */ private $rootFolder; - /** @var string */ - private $userId; + /** @var IUserSession */ + private $userSession; /** @var IMimeTypeDetector */ private $mimeTypeDetector; + /** @var IVersionManager */ + private $versionManager; + /** @var IPreview */ private $previewManager; - public function __construct($appName, - IRequest $request, - IRootFolder $rootFolder, - $userId, - IMimeTypeDetector $mimeTypeDetector, - IPreview $previewManager) { + public function __construct( + $appName, + IRequest $request, + IRootFolder $rootFolder, + IUserSession $userSession, + IMimeTypeDetector $mimeTypeDetector, + IVersionManager $versionManager, + IPreview $previewManager + ) { parent::__construct($appName, $request); $this->rootFolder = $rootFolder; - $this->userId = $userId; + $this->userSession = $userSession; $this->mimeTypeDetector = $mimeTypeDetector; + $this->versionManager = $versionManager; $this->previewManager = $previewManager; } @@ -79,20 +87,17 @@ class PreviewController extends Controller { $y = 44, $version = '' ) { - if($file === '' || $version === '' || $x === 0 || $y === 0) { + if ($file === '' || $version === '' || $x === 0 || $y === 0) { return new DataResponse([], Http::STATUS_BAD_REQUEST); } try { - $userFolder = $this->rootFolder->getUserFolder($this->userId); - /** @var Folder $versionFolder */ - $versionFolder = $userFolder->getParent()->get('files_versions'); - $mimeType = $this->mimeTypeDetector->detectPath($file); - $file = $versionFolder->get($file.'.v'.$version); - - /** @var File $file */ - $f = $this->previewManager->getPreview($file, $x, $y, true, IPreview::MODE_FILL, $mimeType); - return new FileDisplayResponse($f, Http::STATUS_OK, ['Content-Type' => $f->getMimeType()]); + $user = $this->userSession->getUser(); + $userFolder = $this->rootFolder->getUserFolder($user->getUID()); + $file = $userFolder->get($file); + $versionFile = $this->versionManager->getVersionFile($user, $file, (int)$version); + $preview = $this->previewManager->getPreview($versionFile, $x, $y, true, IPreview::MODE_FILL, $versionFile->getMimetype()); + return new FileDisplayResponse($preview, Http::STATUS_OK, ['Content-Type' => $preview->getMimeType()]); } catch (NotFoundException $e) { return new DataResponse([], Http::STATUS_NOT_FOUND); } catch (\InvalidArgumentException $e) { diff --git a/apps/files_versions/lib/Sabre/RestoreFolder.php b/apps/files_versions/lib/Sabre/RestoreFolder.php index c398d02692b..c8504646bad 100644 --- a/apps/files_versions/lib/Sabre/RestoreFolder.php +++ b/apps/files_versions/lib/Sabre/RestoreFolder.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace OCA\Files_Versions\Sabre; +use OCP\IUser; use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\ICollection; use Sabre\DAV\IMoveTarget; @@ -31,14 +32,6 @@ use Sabre\DAV\INode; class RestoreFolder implements ICollection, IMoveTarget { - - /** @var string */ - protected $userId; - - public function __construct(string $userId) { - $this->userId = $userId; - } - public function createFile($name, $data = null) { throw new Forbidden(); } @@ -80,7 +73,8 @@ class RestoreFolder implements ICollection, IMoveTarget { return false; } - return $sourceNode->rollBack(); + $sourceNode->rollBack(); + return true; } } diff --git a/apps/files_versions/lib/Sabre/RootCollection.php b/apps/files_versions/lib/Sabre/RootCollection.php index ca5979573b5..504c3362505 100644 --- a/apps/files_versions/lib/Sabre/RootCollection.php +++ b/apps/files_versions/lib/Sabre/RootCollection.php @@ -20,10 +20,13 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ + namespace OCA\Files_Versions\Sabre; +use OCA\Files_Versions\Versions\IVersionManager; use OCP\Files\IRootFolder; use OCP\IConfig; +use OCP\IUserManager; use Sabre\DAV\INode; use Sabre\DAVACL\AbstractPrincipalCollection; use Sabre\DAVACL\PrincipalBackend; @@ -33,12 +36,24 @@ class RootCollection extends AbstractPrincipalCollection { /** @var IRootFolder */ private $rootFolder; - public function __construct(PrincipalBackend\BackendInterface $principalBackend, - IRootFolder $rootFolder, - IConfig $config) { + /** @var IUserManager */ + private $userManager; + + /** @var IVersionManager */ + private $versionManager; + + public function __construct( + PrincipalBackend\BackendInterface $principalBackend, + IRootFolder $rootFolder, + IConfig $config, + IUserManager $userManager, + IVersionManager $versionManager + ) { parent::__construct($principalBackend, 'principals/users'); $this->rootFolder = $rootFolder; + $this->userManager = $userManager; + $this->versionManager = $versionManager; $this->disableListing = !$config->getSystemValue('debug', false); } @@ -54,12 +69,12 @@ class RootCollection extends AbstractPrincipalCollection { * @return INode */ public function getChildForPrincipal(array $principalInfo) { - list(,$name) = \Sabre\Uri\split($principalInfo['uri']); + list(, $name) = \Sabre\Uri\split($principalInfo['uri']); $user = \OC::$server->getUserSession()->getUser(); if (is_null($user) || $name !== $user->getUID()) { throw new \Sabre\DAV\Exception\Forbidden(); } - return new VersionHome($principalInfo, $this->rootFolder); + return new VersionHome($principalInfo, $this->rootFolder, $this->userManager, $this->versionManager); } public function getName() { diff --git a/apps/files_versions/lib/Sabre/VersionCollection.php b/apps/files_versions/lib/Sabre/VersionCollection.php index 481a5f491c3..9a3a6a365f0 100644 --- a/apps/files_versions/lib/Sabre/VersionCollection.php +++ b/apps/files_versions/lib/Sabre/VersionCollection.php @@ -21,11 +21,15 @@ declare(strict_types=1); * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ + namespace OCA\Files_Versions\Sabre; use OCA\Files_Versions\Storage; +use OCA\Files_Versions\Versions\IVersion; +use OCA\Files_Versions\Versions\IVersionManager; use OCP\Files\File; use OCP\Files\Folder; +use OCP\IUser; use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\Exception\NotFound; use Sabre\DAV\ICollection; @@ -37,13 +41,17 @@ class VersionCollection implements ICollection { /** @var File */ private $file; - /** @var string */ - private $userId; + /** @var IUser */ + private $user; + + /** @var IVersionManager */ + private $versionManager; - public function __construct(Folder $userFolder, File $file, string $userId) { + public function __construct(Folder $userFolder, File $file, IUser $user, IVersionManager $versionManager) { $this->userFolder = $userFolder; $this->file = $file; - $this->userId = $userId; + $this->user = $user; + $this->versionManager = $versionManager; } public function createFile($name, $data = null) { @@ -68,10 +76,10 @@ class VersionCollection implements ICollection { } public function getChildren(): array { - $versions = Storage::getVersions($this->userId, $this->userFolder->getRelativePath($this->file->getPath())); + $versions = $this->versionManager->getVersionsForFile($this->user, $this->file); - return array_map(function (array $data) { - return new VersionFile($data, $this->userFolder->getParent()); + return array_map(function (IVersion $version) { + return new VersionFile($version, $this->versionManager); }, $versions); } diff --git a/apps/files_versions/lib/Sabre/VersionFile.php b/apps/files_versions/lib/Sabre/VersionFile.php index 347058448fc..2d630008d2a 100644 --- a/apps/files_versions/lib/Sabre/VersionFile.php +++ b/apps/files_versions/lib/Sabre/VersionFile.php @@ -21,26 +21,26 @@ declare(strict_types=1); * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ + namespace OCA\Files_Versions\Sabre; -use OCA\Files_Versions\Storage; -use OCP\Files\File; -use OCP\Files\Folder; +use OCA\Files_Versions\Versions\IVersion; +use OCA\Files_Versions\Versions\IVersionManager; use OCP\Files\NotFoundException; use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\Exception\NotFound; use Sabre\DAV\IFile; class VersionFile implements IFile { - /** @var array */ - private $data; + /** @var IVersion */ + private $version; - /** @var Folder */ - private $userRoot; + /** @var IVersionManager */ + private $versionManager; - public function __construct(array $data, Folder $userRoot) { - $this->data = $data; - $this->userRoot = $userRoot; + public function __construct(IVersion $version, IVersionManager $versionManager) { + $this->version = $version; + $this->versionManager = $versionManager; } public function put($data) { @@ -49,27 +49,22 @@ class VersionFile implements IFile { public function get() { try { - /** @var Folder $versions */ - $versions = $this->userRoot->get('files_versions'); - /** @var File $version */ - $version = $versions->get($this->data['path'].'.v'.$this->data['version']); + return $this->versionManager->read($this->version); } catch (NotFoundException $e) { throw new NotFound(); } - - return $version->fopen('rb'); } public function getContentType(): string { - return $this->data['mimetype']; + return $this->version->getMimeType(); } public function getETag(): string { - return $this->data['version']; + return (string)$this->version->getRevisionId(); } public function getSize(): int { - return $this->data['size']; + return $this->version->getSize(); } public function delete() { @@ -77,7 +72,7 @@ class VersionFile implements IFile { } public function getName(): string { - return $this->data['version']; + return (string)$this->version->getRevisionId(); } public function setName($name) { @@ -85,10 +80,10 @@ class VersionFile implements IFile { } public function getLastModified(): int { - return (int)$this->data['version']; + return $this->version->getTimestamp(); } - public function rollBack(): bool { - return Storage::rollback($this->data['path'], $this->data['version']); + public function rollBack() { + $this->versionManager->rollback($this->version); } } diff --git a/apps/files_versions/lib/Sabre/VersionHome.php b/apps/files_versions/lib/Sabre/VersionHome.php index 7a99d2376d4..7be5974bbbe 100644 --- a/apps/files_versions/lib/Sabre/VersionHome.php +++ b/apps/files_versions/lib/Sabre/VersionHome.php @@ -20,9 +20,13 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ + namespace OCA\Files_Versions\Sabre; +use OC\User\NoUserException; +use OCA\Files_Versions\Versions\IVersionManager; use OCP\Files\IRootFolder; +use OCP\IUserManager; use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\ICollection; @@ -34,9 +38,25 @@ class VersionHome implements ICollection { /** @var IRootFolder */ private $rootFolder; - public function __construct(array $principalInfo, IRootFolder $rootFolder) { + /** @var IUserManager */ + private $userManager; + + /** @var IVersionManager */ + private $versionManager; + + public function __construct(array $principalInfo, IRootFolder $rootFolder, IUserManager $userManager, IVersionManager $versionManager) { $this->principalInfo = $principalInfo; $this->rootFolder = $rootFolder; + $this->userManager = $userManager; + $this->versionManager = $versionManager; + } + + private function getUser() { + list(, $name) = \Sabre\Uri\split($this->principalInfo['uri']); + $user = $this->userManager->get($name); + if (!$user) { + throw new NoUserException(); + } } public function delete() { @@ -44,8 +64,7 @@ class VersionHome implements ICollection { } public function getName(): string { - list(,$name) = \Sabre\Uri\split($this->principalInfo['uri']); - return $name; + return $this->getUser()->getUID(); } public function setName($name) { @@ -61,22 +80,22 @@ class VersionHome implements ICollection { } public function getChild($name) { - list(,$userId) = \Sabre\Uri\split($this->principalInfo['uri']); + $user = $this->getUser(); if ($name === 'versions') { - return new VersionRoot($userId, $this->rootFolder); + return new VersionRoot($user, $this->rootFolder, $this->versionManager); } if ($name === 'restore') { - return new RestoreFolder($userId); + return new RestoreFolder(); } } public function getChildren() { - list(,$userId) = \Sabre\Uri\split($this->principalInfo['uri']); + $user = $this->getUser(); return [ - new VersionRoot($userId, $this->rootFolder), - new RestoreFolder($userId), + new VersionRoot($user, $this->rootFolder, $this->versionManager), + new RestoreFolder(), ]; } diff --git a/apps/files_versions/lib/Sabre/VersionRoot.php b/apps/files_versions/lib/Sabre/VersionRoot.php index 743b1c6ef1b..1c689a4d87b 100644 --- a/apps/files_versions/lib/Sabre/VersionRoot.php +++ b/apps/files_versions/lib/Sabre/VersionRoot.php @@ -23,23 +23,29 @@ declare(strict_types=1); */ namespace OCA\Files_Versions\Sabre; +use OCA\Files_Versions\Versions\IVersionManager; use OCP\Files\File; use OCP\Files\IRootFolder; +use OCP\IUser; use Sabre\DAV\Exception\Forbidden; use Sabre\DAV\Exception\NotFound; use Sabre\DAV\ICollection; class VersionRoot implements ICollection { - /** @var string */ - private $userId; + /** @var IUser */ + private $user; /** @var IRootFolder */ private $rootFolder; - public function __construct(string $userId, IRootFolder $rootFolder) { - $this->userId = $userId; + /** @var IVersionManager */ + private $versionManager; + + public function __construct(IUser $user, IRootFolder $rootFolder, IVersionManager $versionManager) { + $this->user = $user; $this->rootFolder = $rootFolder; + $this->versionManager = $versionManager; } public function delete() { @@ -63,7 +69,7 @@ class VersionRoot implements ICollection { } public function getChild($name) { - $userFolder = $this->rootFolder->getUserFolder($this->userId); + $userFolder = $this->rootFolder->getUserFolder($this->user->getUID()); $fileId = (int)$name; $nodes = $userFolder->getById($fileId); @@ -78,7 +84,7 @@ class VersionRoot implements ICollection { throw new NotFound(); } - return new VersionCollection($userFolder, $node, $this->userId); + return new VersionCollection($userFolder, $node, $this->user, $this->versionManager); } public function getChildren(): array { diff --git a/apps/files_versions/lib/Storage.php b/apps/files_versions/lib/Storage.php index 401544cc5d7..e2e4888cbce 100644 --- a/apps/files_versions/lib/Storage.php +++ b/apps/files_versions/lib/Storage.php @@ -48,6 +48,7 @@ use OC\Files\View; use OCA\Files_Versions\AppInfo\Application; use OCA\Files_Versions\Command\Expire; use OCA\Files_Versions\Events\CreateVersionEvent; +use OCA\Files_Versions\Versions\IVersionManager; use OCP\Files\NotFoundException; use OCP\Lock\ILockingProvider; use OCP\User; @@ -178,10 +179,10 @@ class Storage { list($uid, $filename) = self::getUidAndFilename($filename); $files_view = new View('/'.$uid .'/files'); - $users_view = new View('/'.$uid); $eventDispatcher = \OC::$server->getEventDispatcher(); - $id = $files_view->getFileInfo($filename)->getId(); + $fileInfo = $files_view->getFileInfo($filename); + $id = $fileInfo->getId(); $nodes = \OC::$server->getRootFolder()->getById($id); foreach ($nodes as $node) { $event = new CreateVersionEvent($node); @@ -192,20 +193,16 @@ class Storage { } // no use making versions for empty files - if ($files_view->filesize($filename) === 0) { + if ($fileInfo->getSize() === 0) { return false; } - // create all parent folders - self::createMissingDirectories($filename, $users_view); - - self::scheduleExpire($uid, $filename); + /** @var IVersionManager $versionManager */ + $versionManager = \OC::$server->query(IVersionManager::class); + $userManager = \OC::$server->getUserManager(); + $user = $userManager->get($uid); - // store a new version of a file - $mtime = $users_view->filemtime('files/' . $filename); - $users_view->copy('files/' . $filename, 'files_versions/' . $filename . '.v' . $mtime); - // call getFileInfo to enforce a file cache entry for the new version - $users_view->getFileInfo('files_versions/' . $filename . '.v' . $mtime); + $versionManager->createVersion($user, $fileInfo); } @@ -695,7 +692,7 @@ class Storage { * @param string $uid owner of the file * @param string $fileName file/folder for which to schedule expiration */ - private static function scheduleExpire($uid, $fileName) { + public static function scheduleExpire($uid, $fileName) { // let the admin disable auto expire $expiration = self::getExpiration(); if ($expiration->isEnabled()) { @@ -833,7 +830,7 @@ class Storage { * "files" folder * @param View $view view on data/user/ */ - private static function createMissingDirectories($filename, $view) { + public static function createMissingDirectories($filename, $view) { $dirname = Filesystem::normalizePath(dirname($filename)); $dirParts = explode('/', $dirname); $dir = "/files_versions"; diff --git a/apps/files_versions/lib/Versions/BackendNotFoundException.php b/apps/files_versions/lib/Versions/BackendNotFoundException.php new file mode 100644 index 00000000000..09985a716b9 --- /dev/null +++ b/apps/files_versions/lib/Versions/BackendNotFoundException.php @@ -0,0 +1,26 @@ +<?php +/** + * @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\Files_Versions\Versions; + +class BackendNotFoundException extends \Exception { + +} diff --git a/apps/files_versions/lib/Versions/IVersion.php b/apps/files_versions/lib/Versions/IVersion.php new file mode 100644 index 00000000000..b6fc95814d8 --- /dev/null +++ b/apps/files_versions/lib/Versions/IVersion.php @@ -0,0 +1,99 @@ +<?php +declare(strict_types=1); +/** + * @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\Files_Versions\Versions; + +use OCP\Files\FileInfo; +use OCP\IUser; + +/** + * @since 15.0.0 + */ +interface IVersion { + /** + * @return IVersionBackend + * @since 15.0.0 + */ + public function getBackend(): IVersionBackend; + + /** + * Get the file info of the source file + * + * @return FileInfo + * @since 15.0.0 + */ + public function getSourceFile(): FileInfo; + + /** + * Get the id of the revision for the file + * + * @return int + * @since 15.0.0 + */ + public function getRevisionId(): int; + + /** + * Get the timestamp this version was created + * + * @return int + * @since 15.0.0 + */ + public function getTimestamp(): int; + + /** + * Get the size of this version + * + * @return int + * @since 15.0.0 + */ + public function getSize(): int; + + /** + * Get the name of the source file at the time of making this version + * + * @return string + * @since 15.0.0 + */ + public function getSourceFileName(): string; + + /** + * Get the mimetype of this version + * + * @return string + * @since 15.0.0 + */ + public function getMimeType(): string; + + /** + * Get the path of this version + * + * @return string + * @since 15.0.0 + */ + public function getVersionPath(): string; + + /** + * @return IUser + * @since 15.0.0 + */ + public function getUser(): IUser; +} diff --git a/apps/files_versions/lib/Versions/IVersionBackend.php b/apps/files_versions/lib/Versions/IVersionBackend.php new file mode 100644 index 00000000000..616d535f7fd --- /dev/null +++ b/apps/files_versions/lib/Versions/IVersionBackend.php @@ -0,0 +1,81 @@ +<?php declare(strict_types=1); +/** + * @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\Files_Versions\Versions; + +use OCP\Files\File; +use OCP\Files\FileInfo; +use OCP\Files\NotFoundException; +use OCP\Files\SimpleFS\ISimpleFile; +use OCP\IUser; + +/** + * @since 15.0.0 + */ +interface IVersionBackend { + /** + * Get all versions for a file + * + * @param IUser $user + * @param FileInfo $file + * @return IVersion[] + * @since 15.0.0 + */ + public function getVersionsForFile(IUser $user, FileInfo $file): array; + + /** + * Create a new version for a file + * + * @param IUser $user + * @param FileInfo $file + * @since 15.0.0 + */ + public function createVersion(IUser $user, FileInfo $file); + + /** + * Restore this version + * + * @param IVersion $version + * @since 15.0.0 + */ + public function rollback(IVersion $version); + + /** + * Open the file for reading + * + * @param IVersion $version + * @return resource + * @throws NotFoundException + * @since 15.0.0 + */ + public function read(IVersion $version); + + /** + * Get the preview for a specific version of a file + * + * @param IUser $user + * @param FileInfo $sourceFile + * @param int $revision + * @return ISimpleFile + * @since 15.0.0 + */ + public function getVersionFile(IUser $user, FileInfo $sourceFile, int $revision): File; +} diff --git a/apps/files_versions/lib/Versions/IVersionManager.php b/apps/files_versions/lib/Versions/IVersionManager.php new file mode 100644 index 00000000000..748b649b1a2 --- /dev/null +++ b/apps/files_versions/lib/Versions/IVersionManager.php @@ -0,0 +1,36 @@ +<?php declare(strict_types=1); +/** + * @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\Files_Versions\Versions; + +/** + * @since 15.0.0 + */ +interface IVersionManager extends IVersionBackend { + /** + * Register a new backend + * + * @param string $storageType + * @param IVersionBackend $backend + * @since 15.0.0 + */ + public function registerBackend(string $storageType, IVersionBackend $backend); +} diff --git a/apps/files_versions/lib/Versions/LegacyVersionsBackend.php b/apps/files_versions/lib/Versions/LegacyVersionsBackend.php new file mode 100644 index 00000000000..7293aca641e --- /dev/null +++ b/apps/files_versions/lib/Versions/LegacyVersionsBackend.php @@ -0,0 +1,105 @@ +<?php declare(strict_types=1); +/** + * @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\Files_Versions\Versions; + +use OC\Files\View; +use OCA\Files_Versions\Storage; +use OCP\Files\File; +use OCP\Files\FileInfo; +use OCP\Files\Folder; +use OCP\Files\IRootFolder; +use OCP\Files\NotFoundException; +use OCP\IUser; + +class LegacyVersionsBackend implements IVersionBackend { + /** @var IRootFolder */ + private $rootFolder; + + public function __construct(IRootFolder $rootFolder) { + $this->rootFolder = $rootFolder; + } + + public function getVersionsForFile(IUser $user, FileInfo $file): array { + $userFolder = $this->rootFolder->getUserFolder($user->getUID()); + $versions = Storage::getVersions($user->getUID(), $userFolder->getRelativePath($file->getPath())); + + return array_map(function (array $data) use ($file, $user) { + return new Version( + (int)$data['version'], + (int)$data['version'], + $data['name'], + (int)$data['size'], + $data['mimetype'], + $data['path'], + $file, + $this, + $user + ); + }, $versions); + } + + public function createVersion(IUser $user, FileInfo $file) { + $userFolder = $this->rootFolder->getUserFolder($user->getUID()); + $relativePath = $userFolder->getRelativePath($file->getPath()); + $userView = new View('/' . $user->getUID()); + // create all parent folders + Storage::createMissingDirectories($relativePath, $userView); + + Storage::scheduleExpire($user->getUID(), $relativePath); + + // store a new version of a file + $userView->copy('files/' . $relativePath, 'files_versions/' . $relativePath . '.v' . $file->getMtime()); + // ensure the file is scanned + $userView->getFileInfo('files_versions/' . $relativePath . '.v' . $file->getMtime()); + } + + public function rollback(IVersion $version) { + return Storage::rollback($version->getVersionPath(), $version->getRevisionId()); + } + + private function getVersionFolder(IUser $user): Folder { + $userRoot = $this->rootFolder->getUserFolder($user->getUID()) + ->getParent(); + try { + /** @var Folder $folder */ + $folder = $userRoot->get('files_versions'); + return $folder; + } catch (NotFoundException $e) { + return $userRoot->newFolder('files_versions'); + } + } + + public function read(IVersion $version) { + $versions = $this->getVersionFolder($version->getUser()); + /** @var File $file */ + $file = $versions->get($version->getVersionPath() . '.v' . $version->getRevisionId()); + return $file->fopen('r'); + } + + public function getVersionFile(IUser $user, FileInfo $sourceFile, int $revision): File { + $userFolder = $this->rootFolder->getUserFolder($user->getUID()); + $versionFolder = $this->getVersionFolder($user); + /** @var File $file */ + $file = $versionFolder->get($userFolder->getRelativePath($sourceFile->getPath()) . '.v' . $revision); + return $file; + } +} diff --git a/apps/files_versions/lib/Versions/Version.php b/apps/files_versions/lib/Versions/Version.php new file mode 100644 index 00000000000..5988234db61 --- /dev/null +++ b/apps/files_versions/lib/Versions/Version.php @@ -0,0 +1,113 @@ +<?php +declare(strict_types=1); +/** + * @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\Files_Versions\Versions; + +use OCP\Files\FileInfo; +use OCP\IUser; + +class Version implements IVersion { + /** @var int */ + private $timestamp; + + /** @var int */ + private $revisionId; + + /** @var string */ + private $name; + + /** @var int */ + private $size; + + /** @var string */ + private $mimetype; + + /** @var string */ + private $path; + + /** @var FileInfo */ + private $sourceFileInfo; + + /** @var IVersionBackend */ + private $backend; + + /** @var IUser */ + private $user; + + public function __construct( + int $timestamp, + int $revisionId, + string $name, + int $size, + string $mimetype, + string $path, + FileInfo $sourceFileInfo, + IVersionBackend $backend, + IUser $user + ) { + $this->timestamp = $timestamp; + $this->revisionId = $revisionId; + $this->name = $name; + $this->size = $size; + $this->mimetype = $mimetype; + $this->path = $path; + $this->sourceFileInfo = $sourceFileInfo; + $this->backend = $backend; + $this->user = $user; + } + + public function getBackend(): IVersionBackend { + return $this->backend; + } + + public function getSourceFile(): FileInfo { + return $this->sourceFileInfo; + } + + public function getRevisionId(): int { + return $this->revisionId; + } + + public function getTimestamp(): int { + return $this->timestamp; + } + + public function getSize(): int { + return $this->size; + } + + public function getSourceFileName(): string { + return $this->name; + } + + public function getMimeType(): string { + return $this->mimetype; + } + + public function getVersionPath(): string { + return $this->path; + } + + public function getUser(): IUser { + return $this->user; + } +} diff --git a/apps/files_versions/lib/Versions/VersionManager.php b/apps/files_versions/lib/Versions/VersionManager.php new file mode 100644 index 00000000000..757b6002710 --- /dev/null +++ b/apps/files_versions/lib/Versions/VersionManager.php @@ -0,0 +1,93 @@ +<?php declare(strict_types=1); +/** + * @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\Files_Versions\Versions; + +use OCP\Files\File; +use OCP\Files\FileInfo; +use OCP\Files\Storage\IStorage; +use OCP\IUser; + +class VersionManager implements IVersionManager { + /** @var IVersionBackend[] */ + private $backends = []; + + public function registerBackend(string $storageType, IVersionBackend $backend) { + $this->backends[$storageType] = $backend; + } + + /** + * @return IVersionBackend[] + */ + private function getBackends(): array { + return $this->backends; + } + + /** + * @param IStorage $storage + * @return IVersionBackend + * @throws BackendNotFoundException + */ + public function getBackendForStorage(IStorage $storage): IVersionBackend { + $fullType = get_class($storage); + $backends = $this->getBackends(); + $foundType = array_reduce(array_keys($backends), function ($type, $registeredType) use ($storage) { + if ( + $storage->instanceOfStorage($registeredType) && + ($type === '' || is_subclass_of($registeredType, $type)) + ) { + return $registeredType; + } else { + return $type; + } + }, ''); + if ($foundType === '') { + throw new BackendNotFoundException("Version backend for $fullType not found"); + } else { + return $backends[$foundType]; + } + } + + public function getVersionsForFile(IUser $user, FileInfo $file): array { + $backend = $this->getBackendForStorage($file->getStorage()); + return $backend->getVersionsForFile($user, $file); + } + + public function createVersion(IUser $user, FileInfo $file) { + $backend = $this->getBackendForStorage($file->getStorage()); + $backend->createVersion($user, $file); + } + + public function rollback(IVersion $version) { + $backend = $version->getBackend(); + return $backend->rollback($version); + } + + public function read(IVersion $version) { + $backend = $version->getBackend(); + return $backend->read($version); + } + + public function getVersionFile(IUser $user, FileInfo $sourceFile, int $revision): File { + $backend = $this->getBackendForStorage($sourceFile->getStorage()); + return $backend->getVersionFile($user, $sourceFile, $revision); + } +} diff --git a/apps/files_versions/tests/Controller/PreviewControllerTest.php b/apps/files_versions/tests/Controller/PreviewControllerTest.php index 384f43cf495..7c248b36349 100644 --- a/apps/files_versions/tests/Controller/PreviewControllerTest.php +++ b/apps/files_versions/tests/Controller/PreviewControllerTest.php @@ -20,9 +20,12 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ + namespace OCA\Files_Versions\Tests\Controller; +use OC\User\User; use OCA\Files_Versions\Controller\PreviewController; +use OCA\Files_Versions\Versions\IVersionManager; use OCP\AppFramework\Http; use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\Http\FileDisplayResponse; @@ -34,6 +37,8 @@ use OCP\Files\NotFoundException; use OCP\Files\SimpleFS\ISimpleFile; use OCP\IPreview; use OCP\IRequest; +use OCP\IUser; +use OCP\IUserSession; use Test\TestCase; class PreviewControllerTest extends TestCase { @@ -50,23 +55,39 @@ class PreviewControllerTest extends TestCase { /** @var IPreview|\PHPUnit_Framework_MockObject_MockObject */ private $previewManager; - /** @var PreviewController */ + /** @var PreviewController|\PHPUnit_Framework_MockObject_MockObject */ private $controller; + /** @var IUserSession|\PHPUnit_Framework_MockObject_MockObject */ + private $userSession; + + /** @var IVersionManager|\PHPUnit_Framework_MockObject_MockObject */ + private $versionManager; + public function setUp() { parent::setUp(); $this->rootFolder = $this->createMock(IRootFolder::class); $this->userId = 'user'; + $user = $this->createMock(IUser::class); + $user->expects($this->any()) + ->method('getUID') + ->willReturn($this->userId); $this->mimeTypeDetector = $this->createMock(IMimeTypeDetector::class); $this->previewManager = $this->createMock(IPreview::class); + $this->userSession = $this->createMock(IUserSession::class); + $this->userSession->expects($this->any()) + ->method('getUser') + ->willReturn($user); + $this->versionManager = $this->createMock(IVersionManager::class); $this->controller = new PreviewController( 'files_versions', $this->createMock(IRequest::class), $this->rootFolder, - $this->userId, + $this->userSession, $this->mimeTypeDetector, + $this->versionManager, $this->previewManager ); } @@ -102,24 +123,23 @@ class PreviewControllerTest extends TestCase { public function testValidPreview() { $userFolder = $this->createMock(Folder::class); $userRoot = $this->createMock(Folder::class); - $versions = $this->createMock(Folder::class); $this->rootFolder->method('getUserFolder') ->with($this->userId) ->willReturn($userFolder); $userFolder->method('getParent') ->willReturn($userRoot); - $userRoot->method('get') - ->with('files_versions') - ->willReturn($versions); - $this->mimeTypeDetector->method('detectPath') - ->with($this->equalTo('file')) - ->willReturn('myMime'); + $sourceFile = $this->createMock(File::class); + $userFolder->method('get') + ->with('file') + ->willReturn($sourceFile); $file = $this->createMock(File::class); - $versions->method('get') - ->with($this->equalTo('file.v42')) + $file->method('getMimetype') + ->willReturn('myMime'); + + $this->versionManager->method('getVersionFile') ->willReturn($file); $preview = $this->createMock(ISimpleFile::class); @@ -138,24 +158,23 @@ class PreviewControllerTest extends TestCase { public function testVersionNotFound() { $userFolder = $this->createMock(Folder::class); $userRoot = $this->createMock(Folder::class); - $versions = $this->createMock(Folder::class); $this->rootFolder->method('getUserFolder') ->with($this->userId) ->willReturn($userFolder); $userFolder->method('getParent') ->willReturn($userRoot); - $userRoot->method('get') - ->with('files_versions') - ->willReturn($versions); + + $sourceFile = $this->createMock(File::class); + $userFolder->method('get') + ->with('file') + ->willReturn($sourceFile); $this->mimeTypeDetector->method('detectPath') ->with($this->equalTo('file')) ->willReturn('myMime'); - $file = $this->createMock(File::class); - $versions->method('get') - ->with($this->equalTo('file.v42')) + $this->versionManager->method('getVersionFile') ->willThrowException(new NotFoundException()); $res = $this->controller->getPreview('file', 10, 10, '42'); diff --git a/apps/oauth2/lib/Controller/OauthApiController.php b/apps/oauth2/lib/Controller/OauthApiController.php index 2083741fa0c..73fed3654d5 100644 --- a/apps/oauth2/lib/Controller/OauthApiController.php +++ b/apps/oauth2/lib/Controller/OauthApiController.php @@ -22,8 +22,9 @@ namespace OCA\OAuth2\Controller; use OC\Authentication\Exceptions\InvalidTokenException; -use OC\Authentication\Token\ExpiredTokenException; +use OC\Authentication\Exceptions\ExpiredTokenException; use OC\Authentication\Token\IProvider as TokenProvider; +use OC\Security\Bruteforce\Throttler; use OCA\OAuth2\Db\AccessTokenMapper; use OCA\OAuth2\Db\ClientMapper; use OCA\OAuth2\Exceptions\AccessTokenNotFoundException; @@ -49,6 +50,8 @@ class OauthApiController extends Controller { private $secureRandom; /** @var ITimeFactory */ private $time; + /** @var Throttler */ + private $throttler; /** * @param string $appName @@ -59,6 +62,7 @@ class OauthApiController extends Controller { * @param TokenProvider $tokenProvider * @param ISecureRandom $secureRandom * @param ITimeFactory $time + * @param Throttler $throttler */ public function __construct($appName, IRequest $request, @@ -67,7 +71,8 @@ class OauthApiController extends Controller { ClientMapper $clientMapper, TokenProvider $tokenProvider, ISecureRandom $secureRandom, - ITimeFactory $time) { + ITimeFactory $time, + Throttler $throttler) { parent::__construct($appName, $request); $this->crypto = $crypto; $this->accessTokenMapper = $accessTokenMapper; @@ -75,6 +80,7 @@ class OauthApiController extends Controller { $this->tokenProvider = $tokenProvider; $this->secureRandom = $secureRandom; $this->time = $time; + $this->throttler = $throttler; } /** @@ -164,6 +170,8 @@ class OauthApiController extends Controller { $accessToken->setEncryptedToken($this->crypto->encrypt($newToken, $newCode)); $this->accessTokenMapper->update($accessToken); + $this->throttler->resetDelay($this->request->getRemoteAddress(), 'login', ['user' => $appToken->getUID()]); + return new JSONResponse( [ 'access_token' => $newToken, diff --git a/apps/oauth2/tests/Controller/OauthApiControllerTest.php b/apps/oauth2/tests/Controller/OauthApiControllerTest.php index 10748485971..f5a8138fa2d 100644 --- a/apps/oauth2/tests/Controller/OauthApiControllerTest.php +++ b/apps/oauth2/tests/Controller/OauthApiControllerTest.php @@ -22,11 +22,10 @@ namespace OCA\OAuth2\Tests\Controller; use OC\Authentication\Exceptions\InvalidTokenException; +use OC\Authentication\Exceptions\ExpiredTokenException; use OC\Authentication\Token\DefaultToken; -use OC\Authentication\Token\DefaultTokenMapper; -use OC\Authentication\Token\ExpiredTokenException; use OC\Authentication\Token\IProvider as TokenProvider; -use OC\Authentication\Token\IToken; +use OC\Security\Bruteforce\Throttler; use OCA\OAuth2\Controller\OauthApiController; use OCA\OAuth2\Db\AccessToken; use OCA\OAuth2\Db\AccessTokenMapper; @@ -57,6 +56,8 @@ class OauthApiControllerTest extends TestCase { private $secureRandom; /** @var ITimeFactory|\PHPUnit_Framework_MockObject_MockObject */ private $time; + /** @var Throttler|\PHPUnit_Framework_MockObject_MockObject */ + private $throttler; /** @var OauthApiController */ private $oauthApiController; @@ -70,6 +71,7 @@ class OauthApiControllerTest extends TestCase { $this->tokenProvider = $this->createMock(TokenProvider::class); $this->secureRandom = $this->createMock(ISecureRandom::class); $this->time = $this->createMock(ITimeFactory::class); + $this->throttler = $this->createMock(Throttler::class); $this->oauthApiController = new OauthApiController( 'oauth2', @@ -79,7 +81,8 @@ class OauthApiControllerTest extends TestCase { $this->clientMapper, $this->tokenProvider, $this->secureRandom, - $this->time + $this->time, + $this->throttler ); } @@ -286,6 +289,17 @@ class OauthApiControllerTest extends TestCase { 'user_id' => 'userId', ]); + $this->request->method('getRemoteAddress') + ->willReturn('1.2.3.4'); + + $this->throttler->expects($this->once()) + ->method('resetDelay') + ->with( + '1.2.3.4', + 'login', + ['user' => 'userId'] + ); + $this->assertEquals($expected, $this->oauthApiController->getToken('refresh_token', null, 'validrefresh', 'clientId', 'clientSecret')); } @@ -370,6 +384,17 @@ class OauthApiControllerTest extends TestCase { $this->request->server['PHP_AUTH_USER'] = 'clientId'; $this->request->server['PHP_AUTH_PW'] = 'clientSecret'; + $this->request->method('getRemoteAddress') + ->willReturn('1.2.3.4'); + + $this->throttler->expects($this->once()) + ->method('resetDelay') + ->with( + '1.2.3.4', + 'login', + ['user' => 'userId'] + ); + $this->assertEquals($expected, $this->oauthApiController->getToken('refresh_token', null, 'validrefresh', null, null)); } @@ -451,6 +476,17 @@ class OauthApiControllerTest extends TestCase { 'user_id' => 'userId', ]); + $this->request->method('getRemoteAddress') + ->willReturn('1.2.3.4'); + + $this->throttler->expects($this->once()) + ->method('resetDelay') + ->with( + '1.2.3.4', + 'login', + ['user' => 'userId'] + ); + $this->assertEquals($expected, $this->oauthApiController->getToken('refresh_token', null, 'validrefresh', 'clientId', 'clientSecret')); } } diff --git a/apps/sharebymail/tests/CapabilitiesTest.php b/apps/sharebymail/tests/CapabilitiesTest.php new file mode 100644 index 00000000000..b1545994199 --- /dev/null +++ b/apps/sharebymail/tests/CapabilitiesTest.php @@ -0,0 +1,51 @@ +<?php +/** + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\ShareByMail\Tests; + +use OCA\ShareByMail\Capabilities; +use Test\TestCase; + +class CapabilitiesTest extends TestCase { + /** @var Capabilities */ + private $capabilities; + + public function setUp() { + parent::setUp(); + + $this->capabilities = new Capabilities(); + } + + public function testGetCapabilities() { + $capabilities = [ + 'files_sharing' => + [ + 'sharebymail' => + [ + 'enabled' => true, + 'upload_files_drop' => ['enabled' => true], + 'password' => ['enabled' => true], + 'expire_date' => ['enabled' => true] + ] + ] + ]; + + $this->assertSame($capabilities, $this->capabilities->getCapabilities()); + } +} diff --git a/apps/systemtags/tests/Activity/SettingTest.php b/apps/systemtags/tests/Activity/SettingTest.php new file mode 100644 index 00000000000..40fcea750a6 --- /dev/null +++ b/apps/systemtags/tests/Activity/SettingTest.php @@ -0,0 +1,72 @@ +<?php +/** + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * 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 + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +namespace OCA\SystemTags\Tests\Activity; + +use OCA\SystemTags\Activity\Setting; +use OCP\IL10N; +use Test\TestCase; + +class SettingTest extends TestCase { + /** @var IL10N|\PHPUnit_Framework_MockObject_MockObject */ + private $l; + /** @var Setting */ + private $setting; + + public function setUp() { + parent::setUp(); + $this->l = $this->createMock(IL10N::class); + + $this->setting = new Setting($this->l); + } + + public function testGetIdentifier() { + $this->assertSame('systemtags', $this->setting->getIdentifier()); + } + + public function testGetName() { + $this->l + ->expects($this->once()) + ->method('t') + ->with('<strong>System tags</strong> for a file have been modified') + ->willReturn('<strong>System tags</strong> for a file have been modified'); + + $this->assertSame('<strong>System tags</strong> for a file have been modified', $this->setting->getName()); + } + + public function testGetPriority() { + $this->assertSame(50, $this->setting->getPriority()); + } + + public function testCanChangeStream() { + $this->assertSame(true, $this->setting->canChangeStream()); + } + + public function testIsDefaultEnabledStream() { + $this->assertSame(true, $this->setting->isDefaultEnabledStream()); + } + + public function testCanChangeMail() { + $this->assertSame(true, $this->setting->canChangeMail()); + } + + public function testIsDefaultEnabledMail() { + $this->assertSame(false, $this->setting->isDefaultEnabledMail()); + } +} diff --git a/apps/theming/l10n/fr.js b/apps/theming/l10n/fr.js index ae4791e202f..0527c421061 100644 --- a/apps/theming/l10n/fr.js +++ b/apps/theming/l10n/fr.js @@ -8,7 +8,7 @@ OC.L10N.register( "Name cannot be empty" : "Le nom ne peut pas être vide", "The given name is too long" : "Le nom donné est trop long", "The given web address is too long" : "L'adresse web donnée est trop longue", - "The given legal notice address is too long" : "L'adresse de la notice légale donnée est trop longue", + "The given legal notice address is too long" : "L'adresse fournie pour la notice légale est trop longue", "The given privacy policy address is too long" : "L'adresse de la politique de confidentialité est trop longue", "The given slogan is too long" : "Le slogan donné est trop long", "The given color is invalid" : "La couleur donnée est invalide", @@ -40,7 +40,7 @@ OC.L10N.register( "Upload new login background" : "Téléverser un nouvel arrière-plan de connexion", "Remove background image" : "Supprimer l'image en arrière-plan", "Advanced options" : "Options avancées", - "Legal notice link" : "Lien de la notice légale", + "Legal notice link" : "Lien vers la notice légale", "Privacy policy link" : "Lien de la politique de confidentialité", "Header logo" : "Logo d'en-tête", "Upload new header logo" : "Téléverser un nouveau logo d'en-tête", diff --git a/apps/theming/l10n/fr.json b/apps/theming/l10n/fr.json index d9f49f28f62..c47487f9e42 100644 --- a/apps/theming/l10n/fr.json +++ b/apps/theming/l10n/fr.json @@ -6,7 +6,7 @@ "Name cannot be empty" : "Le nom ne peut pas être vide", "The given name is too long" : "Le nom donné est trop long", "The given web address is too long" : "L'adresse web donnée est trop longue", - "The given legal notice address is too long" : "L'adresse de la notice légale donnée est trop longue", + "The given legal notice address is too long" : "L'adresse fournie pour la notice légale est trop longue", "The given privacy policy address is too long" : "L'adresse de la politique de confidentialité est trop longue", "The given slogan is too long" : "Le slogan donné est trop long", "The given color is invalid" : "La couleur donnée est invalide", @@ -38,7 +38,7 @@ "Upload new login background" : "Téléverser un nouvel arrière-plan de connexion", "Remove background image" : "Supprimer l'image en arrière-plan", "Advanced options" : "Options avancées", - "Legal notice link" : "Lien de la notice légale", + "Legal notice link" : "Lien vers la notice légale", "Privacy policy link" : "Lien de la politique de confidentialité", "Header logo" : "Logo d'en-tête", "Upload new header logo" : "Téléverser un nouveau logo d'en-tête", diff --git a/apps/twofactor_backupcodes/l10n/tr.js b/apps/twofactor_backupcodes/l10n/tr.js index 303db19654e..cdd6fa5058e 100644 --- a/apps/twofactor_backupcodes/l10n/tr.js +++ b/apps/twofactor_backupcodes/l10n/tr.js @@ -23,7 +23,7 @@ OC.L10N.register( "Nextcloud backup codes" : "Nextcloud yedek kodları", "You created two-factor backup codes for your account" : "İki aşamalı kimlik doğrulama için yedek kodlarınızı oluşturdunuz", "Second-factor backup codes" : "İki aşamalı kimlik doğrulama yedek kodları", - "You have enabled two-factor authentication but have not yet generated backup codes. Be sure to do this in case you lose access to your second factor." : "İki aşamalı kimlik doğrulamasını etkinleştirmişsiniz ancak yedek kodlarını üretmemişsiniz. İkinci aşamaya erişemediğiniz zaman kullanabilmek için yedek kodlarını üretmeyi unutmayın.", + "You have enabled two-factor authentication but have not yet generated backup codes. Be sure to do this in case you lose access to your second factor." : "İki aşamalı kimlik doğrulamasını etkinleştirmiş ancak yedek kodlarını üretmemişsiniz. İkinci aşamaya erişemediğiniz zaman kullanabilmek için yedek kodlarını üretmeyi unutmayın.", "Backup code" : "Yedek kod", "Use backup code" : "Yedek kodu kullan", "Two factor backup codes" : "İki aşamalı kimlik doğrulama yedek kodları", diff --git a/apps/twofactor_backupcodes/l10n/tr.json b/apps/twofactor_backupcodes/l10n/tr.json index 8d3137f79c4..77b1b650a2e 100644 --- a/apps/twofactor_backupcodes/l10n/tr.json +++ b/apps/twofactor_backupcodes/l10n/tr.json @@ -21,7 +21,7 @@ "Nextcloud backup codes" : "Nextcloud yedek kodları", "You created two-factor backup codes for your account" : "İki aşamalı kimlik doğrulama için yedek kodlarınızı oluşturdunuz", "Second-factor backup codes" : "İki aşamalı kimlik doğrulama yedek kodları", - "You have enabled two-factor authentication but have not yet generated backup codes. Be sure to do this in case you lose access to your second factor." : "İki aşamalı kimlik doğrulamasını etkinleştirmişsiniz ancak yedek kodlarını üretmemişsiniz. İkinci aşamaya erişemediğiniz zaman kullanabilmek için yedek kodlarını üretmeyi unutmayın.", + "You have enabled two-factor authentication but have not yet generated backup codes. Be sure to do this in case you lose access to your second factor." : "İki aşamalı kimlik doğrulamasını etkinleştirmiş ancak yedek kodlarını üretmemişsiniz. İkinci aşamaya erişemediğiniz zaman kullanabilmek için yedek kodlarını üretmeyi unutmayın.", "Backup code" : "Yedek kod", "Use backup code" : "Yedek kodu kullan", "Two factor backup codes" : "İki aşamalı kimlik doğrulama yedek kodları", diff --git a/apps/user_ldap/composer/composer/autoload_classmap.php b/apps/user_ldap/composer/composer/autoload_classmap.php index 98a1bbfa1b7..e25b7ee3126 100644 --- a/apps/user_ldap/composer/composer/autoload_classmap.php +++ b/apps/user_ldap/composer/composer/autoload_classmap.php @@ -56,7 +56,6 @@ return array( 'OCA\\User_LDAP\\Settings\\Section' => $baseDir . '/../lib/Settings/Section.php', 'OCA\\User_LDAP\\UserPluginManager' => $baseDir . '/../lib/UserPluginManager.php', 'OCA\\User_LDAP\\User\\DeletedUsersIndex' => $baseDir . '/../lib/User/DeletedUsersIndex.php', - 'OCA\\User_LDAP\\User\\IUserTools' => $baseDir . '/../lib/User/IUserTools.php', 'OCA\\User_LDAP\\User\\Manager' => $baseDir . '/../lib/User/Manager.php', 'OCA\\User_LDAP\\User\\OfflineUser' => $baseDir . '/../lib/User/OfflineUser.php', 'OCA\\User_LDAP\\User\\User' => $baseDir . '/../lib/User/User.php', diff --git a/apps/user_ldap/composer/composer/autoload_static.php b/apps/user_ldap/composer/composer/autoload_static.php index 83e49daf0f3..23819055be4 100644 --- a/apps/user_ldap/composer/composer/autoload_static.php +++ b/apps/user_ldap/composer/composer/autoload_static.php @@ -71,7 +71,6 @@ class ComposerStaticInitUser_LDAP 'OCA\\User_LDAP\\Settings\\Section' => __DIR__ . '/..' . '/../lib/Settings/Section.php', 'OCA\\User_LDAP\\UserPluginManager' => __DIR__ . '/..' . '/../lib/UserPluginManager.php', 'OCA\\User_LDAP\\User\\DeletedUsersIndex' => __DIR__ . '/..' . '/../lib/User/DeletedUsersIndex.php', - 'OCA\\User_LDAP\\User\\IUserTools' => __DIR__ . '/..' . '/../lib/User/IUserTools.php', 'OCA\\User_LDAP\\User\\Manager' => __DIR__ . '/..' . '/../lib/User/Manager.php', 'OCA\\User_LDAP\\User\\OfflineUser' => __DIR__ . '/..' . '/../lib/User/OfflineUser.php', 'OCA\\User_LDAP\\User\\User' => __DIR__ . '/..' . '/../lib/User/User.php', diff --git a/apps/user_ldap/lib/Access.php b/apps/user_ldap/lib/Access.php index e05bc539a77..fb2582e8266 100644 --- a/apps/user_ldap/lib/Access.php +++ b/apps/user_ldap/lib/Access.php @@ -46,7 +46,6 @@ namespace OCA\User_LDAP; use OC\HintException; use OC\Hooks\PublicEmitter; use OCA\User_LDAP\Exceptions\ConstraintViolationException; -use OCA\User_LDAP\User\IUserTools; use OCA\User_LDAP\User\Manager; use OCA\User_LDAP\User\OfflineUser; use OCA\User_LDAP\Mapping\AbstractMapping; @@ -59,7 +58,7 @@ use OCP\IUserManager; * Class Access * @package OCA\User_LDAP */ -class Access extends LDAPUtility implements IUserTools { +class Access extends LDAPUtility { const UUID_ATTRIBUTES = ['entryuuid', 'nsuniqueid', 'objectguid', 'guid', 'ipauniqueid']; /** @var \OCA\User_LDAP\Connection */ @@ -624,9 +623,9 @@ class Access extends LDAPUtility implements IUserTools { $this->connection->setConfiguration(['ldapCacheTTL' => $originalTTL]); $altName = $this->createAltInternalOwnCloudName($intName, $isUser); - if(is_string($altName) && $mapper->map($fdn, $altName, $uuid)) { - if($this->ncUserManager instanceof PublicEmitter && $isUser) { - $this->ncUserManager->emit('\OC\User', 'assignedUserId', [$intName]); + if (is_string($altName) && $mapper->map($fdn, $altName, $uuid)) { + if ($this->ncUserManager instanceof PublicEmitter && $isUser) { + $this->ncUserManager->emit('\OC\User', 'assignedUserId', [$altName]); } $newlyMapped = true; return $altName; diff --git a/apps/user_ldap/lib/User/IUserTools.php b/apps/user_ldap/lib/User/IUserTools.php deleted file mode 100644 index 4ba9cebb1a6..00000000000 --- a/apps/user_ldap/lib/User/IUserTools.php +++ /dev/null @@ -1,42 +0,0 @@ -<?php -/** - * @copyright Copyright (c) 2016, ownCloud, Inc. - * - * @author Arthur Schiwon <blizzz@arthur-schiwon.de> - * @author Joas Schilling <coding@schilljs.com> - * @author Morris Jobke <hey@morrisjobke.de> - * - * @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 <http://www.gnu.org/licenses/> - * - */ - -namespace OCA\User_LDAP\User; - -/** - * IUserTools - * - * defines methods that are required by User class for LDAP interaction - */ -interface IUserTools { - public function getConnection(); - - public function readAttribute($dn, $attr, $filter = 'objectClass=*'); - - public function stringResemblesDN($string); - - public function dn2username($dn, $ldapname = null); - - public function username2dn($name); -} diff --git a/apps/user_ldap/lib/User/Manager.php b/apps/user_ldap/lib/User/Manager.php index 9f2f3649777..13555f9e31b 100644 --- a/apps/user_ldap/lib/User/Manager.php +++ b/apps/user_ldap/lib/User/Manager.php @@ -45,7 +45,7 @@ use OCP\Notification\IManager as INotificationManager; * cache */ class Manager { - /** @var IUserTools */ + /** @var Access */ protected $access; /** @var IConfig */ @@ -110,11 +110,11 @@ class Manager { } /** - * @brief binds manager to an instance of IUserTools (implemented by - * Access). It needs to be assigned first before the manager can be used. - * @param IUserTools + * Binds manager to an instance of Access. + * It needs to be assigned first before the manager can be used. + * @param Access */ - public function setLdapAccess(IUserTools $access) { + public function setLdapAccess(Access $access) { $this->access = $access; } diff --git a/apps/user_ldap/lib/User/User.php b/apps/user_ldap/lib/User/User.php index 02764a72eca..706424d3189 100644 --- a/apps/user_ldap/lib/User/User.php +++ b/apps/user_ldap/lib/User/User.php @@ -30,6 +30,7 @@ namespace OCA\User_LDAP\User; +use OCA\User_LDAP\Access; use OCA\User_LDAP\Connection; use OCA\User_LDAP\FilesystemHelper; use OCA\User_LDAP\LogWrapper; @@ -48,7 +49,7 @@ use OCP\Notification\IManager as INotificationManager; */ class User { /** - * @var IUserTools + * @var Access */ protected $access; /** @@ -110,8 +111,7 @@ class User { * @brief constructor, make sure the subclasses call this one! * @param string $username the internal username * @param string $dn the LDAP DN - * @param IUserTools $access an instance that implements IUserTools for - * LDAP interaction + * @param Access $access * @param IConfig $config * @param FilesystemHelper $fs * @param Image $image any empty instance @@ -120,7 +120,7 @@ class User { * @param IUserManager $userManager * @param INotificationManager $notificationManager */ - public function __construct($username, $dn, IUserTools $access, + public function __construct($username, $dn, Access $access, IConfig $config, FilesystemHelper $fs, Image $image, LogWrapper $log, IAvatarManager $avatarManager, IUserManager $userManager, INotificationManager $notificationManager) { @@ -414,14 +414,23 @@ class User { * * @param string $displayName * @param string $displayName2 - * @returns string the effective display name + * @return string the effective display name */ public function composeAndStoreDisplayName($displayName, $displayName2 = '') { $displayName2 = (string)$displayName2; if($displayName2 !== '') { $displayName .= ' (' . $displayName2 . ')'; } - $this->store('displayName', $displayName); + $oldName = $this->config->getUserValue($this->uid, 'user_ldap', 'displayName', null); + if ($oldName !== $displayName) { + $this->store('displayName', $displayName); + $user = $this->userManager->get($this->getUsername()); + if (!empty($oldName) && $user instanceof \OC\User\User) { + // if it was empty, it would be a new record, not a change emitting the trigger could + // potentially cause a UniqueConstraintViolationException, depending on some factors. + $user->triggerChange('displayName', $displayName); + } + } return $displayName; } diff --git a/apps/user_ldap/tests/User/ManagerTest.php b/apps/user_ldap/tests/User/ManagerTest.php index 104a70ff700..5c111abdc4e 100644 --- a/apps/user_ldap/tests/User/ManagerTest.php +++ b/apps/user_ldap/tests/User/ManagerTest.php @@ -28,11 +28,13 @@ namespace OCA\User_LDAP\Tests\User; +use OCA\User_LDAP\Access; +use OCA\User_LDAP\Connection; use OCA\User_LDAP\FilesystemHelper; use OCA\User_LDAP\ILDAPWrapper; use OCA\User_LDAP\LogWrapper; -use OCA\User_LDAP\User\IUserTools; use OCA\User_LDAP\User\Manager; +use OCA\User_LDAP\User\User; use OCP\IAvatarManager; use OCP\IConfig; use OCP\IDBConnection; @@ -48,200 +50,181 @@ use OCP\Notification\IManager as INotificationManager; * @package OCA\User_LDAP\Tests\User */ class ManagerTest extends \Test\TestCase { + /** @var Access|\PHPUnit_Framework_MockObject_MockObject */ + protected $access; - private function getTestInstances() { - $access = $this->createMock(IUserTools::class); - $config = $this->createMock(IConfig::class); - $filesys = $this->createMock(FilesystemHelper::class); - $log = $this->createMock(LogWrapper::class); - $avaMgr = $this->createMock(IAvatarManager::class); - $image = $this->createMock(Image::class); - $dbc = $this->createMock(IDBConnection::class); - $userMgr = $this->createMock(IUserManager::class); - $notiMgr = $this->createMock(INotificationManager::class); - - $connection = new \OCA\User_LDAP\Connection( - $lw = $this->createMock(ILDAPWrapper::class), - '', - null - ); + /** @var IConfig|\PHPUnit_Framework_MockObject_MockObject */ + protected $config; - $access->expects($this->any()) - ->method('getConnection') - ->will($this->returnValue($connection)); + /** @var FilesystemHelper|\PHPUnit_Framework_MockObject_MockObject */ + protected $fileSystemHelper; - return array($access, $config, $filesys, $image, $log, $avaMgr, $dbc, $userMgr, $notiMgr); - } + /** @var LogWrapper|\PHPUnit_Framework_MockObject_MockObject */ + protected $log; - public function testGetByDNExisting() { - list($access, $config, $filesys, $image, $log, $avaMgr, $dbc, $userMgr, $notiMgr) = - $this->getTestInstances(); + /** @var IAvatarManager|\PHPUnit_Framework_MockObject_MockObject */ + protected $avatarManager; - $inputDN = 'cn=foo,dc=foobar,dc=bar'; - $uid = '563418fc-423b-1033-8d1c-ad5f418ee02e'; + /** @var Image|\PHPUnit_Framework_MockObject_MockObject */ + protected $image; - $access->expects($this->once()) - ->method('stringResemblesDN') - ->with($this->equalTo($inputDN)) - ->will($this->returnValue(true)); + /** @var IDBConnection|\PHPUnit_Framework_MockObject_MockObject */ + protected $dbc; - $access->expects($this->once()) - ->method('dn2username') - ->with($this->equalTo($inputDN)) - ->will($this->returnValue($uid)); + /** @var IUserManager|\PHPUnit_Framework_MockObject_MockObject */ + protected $ncUserManager; - $access->expects($this->never()) - ->method('username2dn'); + /** @var INotificationManager|\PHPUnit_Framework_MockObject_MockObject */ + protected $notificationManager; - $manager = new Manager($config, $filesys, $log, $avaMgr, $image, $dbc, $userMgr, $notiMgr); - $manager->setLdapAccess($access); - $user = $manager->get($inputDN); + /** @var ILDAPWrapper|\PHPUnit_Framework_MockObject_MockObject */ + protected $ldapWrapper; - // Now we fetch the user again. If this leads to a failing test, - // runtime caching the manager is broken. - $user = $manager->get($inputDN); - - $this->assertInstanceOf('\OCA\User_LDAP\User\User', $user); - } + /** @var Connection */ + protected $connection; - public function testGetByEDirectoryDN() { - list($access, $config, $filesys, $image, $log, $avaMgr, $dbc, $userMgr, $notiMgr) = - $this->getTestInstances(); + /** @var Manager */ + protected $manager; - $inputDN = 'uid=foo,o=foobar,c=bar'; - $uid = '563418fc-423b-1033-8d1c-ad5f418ee02e'; + public function setUp() { + parent::setUp(); - $access->expects($this->once()) - ->method('stringResemblesDN') - ->with($this->equalTo($inputDN)) - ->will($this->returnValue(true)); - - $access->expects($this->once()) - ->method('dn2username') - ->with($this->equalTo($inputDN)) - ->will($this->returnValue($uid)); + $this->access = $this->createMock(Access::class); + $this->config = $this->createMock(IConfig::class); + $this->fileSystemHelper = $this->createMock(FilesystemHelper::class); + $this->log = $this->createMock(LogWrapper::class); + $this->avatarManager = $this->createMock(IAvatarManager::class); + $this->image = $this->createMock(Image::class); + $this->dbc = $this->createMock(IDBConnection::class); + $this->ncUserManager = $this->createMock(IUserManager::class); + $this->notificationManager = $this->createMock(INotificationManager::class); - $access->expects($this->never()) - ->method('username2dn'); + $this->ldapWrapper = $this->createMock(ILDAPWrapper::class); + $this->connection = new Connection($this->ldapWrapper, '', null); - $manager = new Manager($config, $filesys, $log, $avaMgr, $image, $dbc, $userMgr, $notiMgr); - $manager->setLdapAccess($access); - $user = $manager->get($inputDN); + $this->access->expects($this->any()) + ->method('getConnection') + ->will($this->returnValue($this->connection)); + + /** @noinspection PhpUnhandledExceptionInspection */ + $this->manager = new Manager( + $this->config, + $this->fileSystemHelper, + $this->log, + $this->avatarManager, + $this->image, + $this->dbc, + $this->ncUserManager, + $this->notificationManager + ); - $this->assertInstanceOf('\OCA\User_LDAP\User\User', $user); + $this->manager->setLdapAccess($this->access); } - public function testGetByExoticDN() { - list($access, $config, $filesys, $image, $log, $avaMgr, $dbc, $userMgr, $notiMgr) = - $this->getTestInstances(); + public function dnProvider() { + return [ + ['cn=foo,dc=foobar,dc=bar'], + ['uid=foo,o=foobar,c=bar'], + ['ab=cde,f=ghei,mno=pq'], + ]; + } - $inputDN = 'ab=cde,f=ghei,mno=pq'; + /** + * @dataProvider dnProvider + */ + public function testGetByDNExisting(string $inputDN) { $uid = '563418fc-423b-1033-8d1c-ad5f418ee02e'; - $access->expects($this->once()) + $this->access->expects($this->once()) ->method('stringResemblesDN') ->with($this->equalTo($inputDN)) ->will($this->returnValue(true)); - - $access->expects($this->once()) + $this->access->expects($this->once()) ->method('dn2username') ->with($this->equalTo($inputDN)) ->will($this->returnValue($uid)); - - $access->expects($this->never()) + $this->access->expects($this->never()) ->method('username2dn'); - $manager = new Manager($config, $filesys, $log, $avaMgr, $image, $dbc, $userMgr, $notiMgr); - $manager->setLdapAccess($access); - $user = $manager->get($inputDN); + /** @noinspection PhpUnhandledExceptionInspection */ + $this->manager->get($inputDN); - $this->assertInstanceOf('\OCA\User_LDAP\User\User', $user); + // Now we fetch the user again. If this leads to a failing test, + // runtime caching the manager is broken. + /** @noinspection PhpUnhandledExceptionInspection */ + $user = $this->manager->get($inputDN); + + $this->assertInstanceOf(User::class, $user); } public function testGetByDNNotExisting() { - list($access, $config, $filesys, $image, $log, $avaMgr, $dbc, $userMgr, $notiMgr) = - $this->getTestInstances(); - $inputDN = 'cn=gone,dc=foobar,dc=bar'; - $access->expects($this->once()) + $this->access->expects($this->once()) ->method('stringResemblesDN') ->with($this->equalTo($inputDN)) ->will($this->returnValue(true)); - - $access->expects($this->once()) + $this->access->expects($this->once()) ->method('dn2username') ->with($this->equalTo($inputDN)) ->will($this->returnValue(false)); - - $access->expects($this->once()) + $this->access->expects($this->once()) ->method('username2dn') ->with($this->equalTo($inputDN)) ->will($this->returnValue(false)); - $manager = new Manager($config, $filesys, $log, $avaMgr, $image, $dbc, $userMgr, $notiMgr); - $manager->setLdapAccess($access); - $user = $manager->get($inputDN); + /** @noinspection PhpUnhandledExceptionInspection */ + $user = $this->manager->get($inputDN); $this->assertNull($user); } public function testGetByUidExisting() { - list($access, $config, $filesys, $image, $log, $avaMgr, $dbc, $userMgr, $notiMgr) = - $this->getTestInstances(); - $dn = 'cn=foo,dc=foobar,dc=bar'; $uid = '563418fc-423b-1033-8d1c-ad5f418ee02e'; - $access->expects($this->never()) + $this->access->expects($this->never()) ->method('dn2username'); - - $access->expects($this->once()) + $this->access->expects($this->once()) ->method('username2dn') ->with($this->equalTo($uid)) ->will($this->returnValue($dn)); - - $access->expects($this->once()) + $this->access->expects($this->once()) ->method('stringResemblesDN') ->with($this->equalTo($uid)) ->will($this->returnValue(false)); - $manager = new Manager($config, $filesys, $log, $avaMgr, $image, $dbc, $userMgr, $notiMgr); - $manager->setLdapAccess($access); - $user = $manager->get($uid); + /** @noinspection PhpUnhandledExceptionInspection */ + $this->manager->get($uid); // Now we fetch the user again. If this leads to a failing test, // runtime caching the manager is broken. - $user = $manager->get($uid); + /** @noinspection PhpUnhandledExceptionInspection */ + $user = $this->manager->get($uid); - $this->assertInstanceOf('\OCA\User_LDAP\User\User', $user); + $this->assertInstanceOf(User::class, $user); } public function testGetByUidNotExisting() { - list($access, $config, $filesys, $image, $log, $avaMgr, $dbc, $userMgr, $notiMgr) = - $this->getTestInstances(); - $uid = 'gone'; - $access->expects($this->never()) + $this->access->expects($this->never()) ->method('dn2username'); - - $access->expects($this->exactly(1)) + $this->access->expects($this->exactly(1)) ->method('username2dn') ->with($this->equalTo($uid)) ->will($this->returnValue(false)); - $manager = new Manager($config, $filesys, $log, $avaMgr, $image, $dbc, $userMgr, $notiMgr); - $manager->setLdapAccess($access); - $user = $manager->get($uid); + /** @noinspection PhpUnhandledExceptionInspection */ + $user = $this->manager->get($uid); $this->assertNull($user); } public function attributeRequestProvider() { return [ - [ false ], - [ true ], + [false], + [true], ]; } @@ -249,23 +232,16 @@ class ManagerTest extends \Test\TestCase { * @dataProvider attributeRequestProvider */ public function testGetAttributes($minimal) { - list($access, $config, $filesys, $image, $log, $avaMgr, $dbc, $userMgr, $notiMgr) = - $this->getTestInstances(); - - $manager = new Manager($config, $filesys, $log, $avaMgr, $image, $dbc, $userMgr, $notiMgr); - $manager->setLdapAccess($access); - - $connection = $access->getConnection(); - $connection->setConfiguration([ + $this->connection->setConfiguration([ 'ldapEmailAttribute' => 'mail', 'ldapUserAvatarRule' => 'default', 'ldapQuotaAttribute' => '', ]); - $attributes = $manager->getAttributes($minimal); + $attributes = $this->manager->getAttributes($minimal); $this->assertTrue(in_array('dn', $attributes)); - $this->assertTrue(in_array($access->getConnection()->ldapEmailAttribute, $attributes)); + $this->assertTrue(in_array($this->access->getConnection()->ldapEmailAttribute, $attributes)); $this->assertFalse(in_array('', $attributes)); $this->assertSame(!$minimal, in_array('jpegphoto', $attributes)); $this->assertSame(!$minimal, in_array('thumbnailphoto', $attributes)); diff --git a/apps/user_ldap/tests/User/UserTest.php b/apps/user_ldap/tests/User/UserTest.php index 837c72a3a31..6ff9defe47b 100644 --- a/apps/user_ldap/tests/User/UserTest.php +++ b/apps/user_ldap/tests/User/UserTest.php @@ -998,23 +998,58 @@ class UserTest extends \Test\TestCase { public function displayNameProvider() { return [ - ['Roland Deschain', '', 'Roland Deschain'], - ['Roland Deschain', null, 'Roland Deschain'], - ['Roland Deschain', 'gunslinger@darktower.com', 'Roland Deschain (gunslinger@darktower.com)'], + ['Roland Deschain', '', 'Roland Deschain', false], + ['Roland Deschain', '', 'Roland Deschain', true], + ['Roland Deschain', null, 'Roland Deschain', false], + ['Roland Deschain', 'gunslinger@darktower.com', 'Roland Deschain (gunslinger@darktower.com)', false], + ['Roland Deschain', 'gunslinger@darktower.com', 'Roland Deschain (gunslinger@darktower.com)', true], ]; } /** * @dataProvider displayNameProvider */ - public function testComposeAndStoreDisplayName($part1, $part2, $expected) { + public function testComposeAndStoreDisplayName($part1, $part2, $expected, $expectTriggerChange) { $this->config->expects($this->once()) ->method('setUserValue'); + $oldName = $expectTriggerChange ? 'xxGunslingerxx' : null; + $this->config->expects($this->once()) + ->method('getUserValue') + ->with($this->user->getUsername(), 'user_ldap', 'displayName', null) + ->willReturn($oldName); + + $ncUserObj = $this->createMock(\OC\User\User::class); + if ($expectTriggerChange) { + $ncUserObj->expects($this->once()) + ->method('triggerChange') + ->with('displayName', $expected); + } else { + $ncUserObj->expects($this->never()) + ->method('triggerChange'); + } + $this->userManager->expects($this->once()) + ->method('get') + ->willReturn($ncUserObj); $displayName = $this->user->composeAndStoreDisplayName($part1, $part2); $this->assertSame($expected, $displayName); } + public function testComposeAndStoreDisplayNameNoOverwrite() { + $displayName = 'Randall Flagg'; + $this->config->expects($this->never()) + ->method('setUserValue'); + $this->config->expects($this->once()) + ->method('getUserValue') + ->willReturn($displayName); + + $this->userManager->expects($this->never()) + ->method('get'); // Implicit: no triggerChange can be called + + $composedDisplayName = $this->user->composeAndStoreDisplayName($displayName); + $this->assertSame($composedDisplayName, $displayName); + } + public function testHandlePasswordExpiryWarningDefaultPolicy() { $this->connection->expects($this->any()) ->method('__get') diff --git a/apps/workflowengine/lib/Check/FileMimeType.php b/apps/workflowengine/lib/Check/FileMimeType.php index bd94ec9d5bc..5f572f5aa9d 100644 --- a/apps/workflowengine/lib/Check/FileMimeType.php +++ b/apps/workflowengine/lib/Check/FileMimeType.php @@ -76,6 +76,11 @@ class FileMimeType extends AbstractStringCheck { return $this->mimeType[$this->storage->getId()][$this->path]; } + if ($this->storage->is_dir($this->path)) { + $this->mimeType[$this->storage->getId()][$this->path] = 'httpd/unix-directory'; + return $this->mimeType[$this->storage->getId()][$this->path]; + } + if ($this->isWebDAVRequest()) { // Creating a folder if ($this->request->getMethod() === 'MKCOL') { |