diff options
32 files changed, 969 insertions, 102 deletions
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/files_sharing/css/sharetabview.scss b/apps/files_sharing/css/sharetabview.scss index 14be9562228..0d277c58bd7 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; } } @@ -204,8 +229,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; diff --git a/apps/files_sharing/lib/Controller/ShareAPIController.php b/apps/files_sharing/lib/Controller/ShareAPIController.php index ff19c35e2b5..a935189491e 100644 --- a/apps/files_sharing/lib/Controller/ShareAPIController.php +++ b/apps/files_sharing/lib/Controller/ShareAPIController.php @@ -252,6 +252,7 @@ class ShareAPIController extends OCSController { $result['mail_send'] = $share->getMailSend() ? 1 : 0; + $result['hide_download'] = $share->getHideDownload() ? 1 : 0; return $result; } @@ -745,6 +746,7 @@ class ShareAPIController extends OCSController { * @param string $publicUpload * @param string $expireDate * @param string $note + * @param string $hideDownload * @return DataResponse * @throws LockedException * @throws NotFoundException @@ -759,7 +761,8 @@ class ShareAPIController extends OCSController { string $sendPasswordByTalk = null, string $publicUpload = null, string $expireDate = null, - string $note = null + string $note = null, + string $hideDownload = null ): DataResponse { try { $share = $this->getShareById($id); @@ -773,7 +776,7 @@ 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 && $hideDownload === null) { throw new OCSBadRequestException($this->l->t('Wrong or no update parameter given')); } @@ -786,6 +789,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; 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..bd263de3f62 100644 --- a/apps/files_sharing/tests/Controller/ShareAPIControllerTest.php +++ b/apps/files_sharing/tests/Controller/ShareAPIControllerTest.php @@ -353,6 +353,7 @@ class ShareAPIControllerTest extends TestCase { 'note' => 'personal note', 'displayname_file_owner' => 'ownerDisplay', 'mimetype' => 'myMimeType', + 'hide_download' => 0, ]; $data[] = [$share, $expected]; @@ -397,6 +398,7 @@ class ShareAPIControllerTest extends TestCase { 'note' => 'personal note', 'displayname_file_owner' => 'ownerDisplay', 'mimetype' => 'myFolderMimeType', + 'hide_download' => 0, ]; $data[] = [$share, $expected]; @@ -445,6 +447,7 @@ class ShareAPIControllerTest extends TestCase { 'note' => 'personal note', 'displayname_file_owner' => 'ownerDisplay', 'mimetype' => 'myFolderMimeType', + 'hide_download' => 0, ]; $data[] = [$share, $expected]; @@ -2175,6 +2178,7 @@ class ShareAPIControllerTest extends TestCase { 'note' => 'personal note', 'mail_send' => 0, 'mimetype' => 'myMimeType', + 'hide_download' => 0, ], $share, [], false ]; // User backend up @@ -2204,6 +2208,7 @@ class ShareAPIControllerTest extends TestCase { 'share_with_displayname' => 'recipientDN', 'mail_send' => 0, 'mimetype' => 'myMimeType', + 'hide_download' => 0, ], $share, [ ['owner', $owner], ['initiator', $initiator], @@ -2249,6 +2254,7 @@ class ShareAPIControllerTest extends TestCase { 'share_with_displayname' => 'recipient', 'mail_send' => 0, 'mimetype' => 'myMimeType', + 'hide_download' => 0, ], $share, [], false ]; @@ -2292,6 +2298,7 @@ class ShareAPIControllerTest extends TestCase { 'share_with_displayname' => 'recipientGroupDisplayName', 'mail_send' => 0, 'mimetype' => 'myMimeType', + 'hide_download' => 0, ], $share, [], false ]; @@ -2333,6 +2340,7 @@ class ShareAPIControllerTest extends TestCase { 'share_with_displayname' => 'recipientGroup2', 'mail_send' => 0, 'mimetype' => 'myMimeType', + 'hide_download' => 0, ], $share, [], false ]; @@ -2377,6 +2385,7 @@ class ShareAPIControllerTest extends TestCase { 'mail_send' => 0, 'url' => 'myLink', 'mimetype' => 'myMimeType', + 'hide_download' => 0, ], $share, [], false ]; @@ -2418,6 +2427,7 @@ class ShareAPIControllerTest extends TestCase { 'share_with_displayname' => 'foobar', 'mail_send' => 0, 'mimetype' => 'myFolderMimeType', + 'hide_download' => 0, ], $share, [], false ]; @@ -2462,6 +2472,7 @@ class ShareAPIControllerTest extends TestCase { 'share_with_avatar' => 'path/to/the/avatar', 'mail_send' => 0, 'mimetype' => 'myFolderMimeType', + 'hide_download' => 0, ], $share, [], false ]; @@ -2504,6 +2515,7 @@ class ShareAPIControllerTest extends TestCase { 'share_with_avatar' => '', 'mail_send' => 0, 'mimetype' => 'myFolderMimeType', + 'hide_download' => 0, ], $share, [], false ]; @@ -2546,6 +2558,7 @@ class ShareAPIControllerTest extends TestCase { 'share_with_avatar' => '', 'mail_send' => 0, 'mimetype' => 'myFolderMimeType', + 'hide_download' => 0, ], $share, [], false ]; @@ -2603,7 +2616,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 ]; @@ -2647,7 +2661,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 +2802,7 @@ class ShareAPIControllerTest extends TestCase { 'share_with_displayname' => '', 'mail_send' => 0, 'mimetype' => 'myMimeType', + 'hide_download' => 0, ], $share, false, [] ]; @@ -2828,6 +2844,7 @@ class ShareAPIControllerTest extends TestCase { 'share_with_displayname' => 'recipientRoomName', 'mail_send' => 0, 'mimetype' => 'myMimeType', + 'hide_download' => 0, ], $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/config/config.sample.php b/config/config.sample.php index 9a5648c95df..25f56904dc4 100644 --- a/config/config.sample.php +++ b/config/config.sample.php @@ -1499,11 +1499,26 @@ $CONFIG = array( /** * List of trusted proxy servers * - * If you configure these also consider setting `forwarded_for_headers` which - * otherwise defaults to `HTTP_X_FORWARDED_FOR` (the `X-Forwarded-For` header). + * You may set this to an array containing a combination of + * - IPv4 addresses, e.g. `192.168.2.123` + * - IPv4 ranges in CIDR notation, e.g. `192.168.2.0/24` + * - IPv6 addresses, e.g. `fd9e:21a7:a92c:2323::1` + * + * _(CIDR notation for IPv6 is currently work in progress and thus not + * available as of yet)_ + * + * When an incoming request's `REMOTE_ADDR` matches any of the IP addresses + * specified here, it is assumed to be a proxy instead of a client. Thus, the + * client IP will be read from the HTTP header specified in + * `forwarded_for_headers` instead of from `REMOTE_ADDR`. + * + * So if you configure `trusted_proxies`, also consider setting + * `forwarded_for_headers` which otherwise defaults to `HTTP_X_FORWARDED_FOR` + * (the `X-Forwarded-For` header). + * * Defaults to an empty array. */ -'trusted_proxies' => array('203.0.113.45', '198.51.100.128'), +'trusted_proxies' => array('203.0.113.45', '198.51.100.128', '192.168.2.0/24'), /** * Headers that should be trusted as client IP address in combination with diff --git a/core/Migrations/Version15000Date20181015062942.php b/core/Migrations/Version15000Date20181015062942.php new file mode 100644 index 00000000000..e73b663d2fd --- /dev/null +++ b/core/Migrations/Version15000Date20181015062942.php @@ -0,0 +1,54 @@ +<?php +declare(strict_types=1); +/** + * @copyright Copyright (c) 2018 Roeland Jago Douma <roeland@famdouma.nl> + * + * @author Roeland Jago Douma <roeland@famdouma.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 OC\Core\Migrations; + +use Closure; +use Doctrine\DBAL\Types\Type; +use OCP\DB\ISchemaWrapper; +use OCP\Migration\SimpleMigrationStep; +use OCP\Migration\IOutput; + +class Version15000Date20181015062942 extends SimpleMigrationStep { + + /** + * @param IOutput $output + * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` + * @param array $options + * @return null|ISchemaWrapper + */ + public function changeSchema(IOutput $output, Closure $schemaClosure, array $options) { + /** @var ISchemaWrapper $schema */ + $schema = $schemaClosure(); + + $table = $schema->getTable('share'); + $table->addColumn('hide_download', 'smallint', [ + 'notnull' => true, + 'length' => 1, + 'default' => 0, + ]); + + return $schema; + } +} diff --git a/core/css/icons.scss b/core/css/icons.scss index 5b96d1223a7..99b1dc9c215 100644 --- a/core/css/icons.scss +++ b/core/css/icons.scss @@ -466,3 +466,7 @@ img, object, video, button, textarea, input, select, div[contenteditable='true'] @include icon-color('search', 'actions', $color-black, 1, true); } + +.icon-talk { + @include icon-color('app-dark', 'spreed', $color-black, 1); +} diff --git a/core/css/jquery-ui-fixes.scss b/core/css/jquery-ui-fixes.scss index e30beee44e5..eab22e70d62 100644 --- a/core/css/jquery-ui-fixes.scss +++ b/core/css/jquery-ui-fixes.scss @@ -70,7 +70,8 @@ .ui-widget-header .ui-state-highlight { border: 1px solid var(--color-main-background); background: var(--color-main-background) none; - color: var(--color-text-lighter); + color: var(--color-text-light); + font-weight: 600; } .ui-state-highlight a, .ui-widget-content .ui-state-highlight a, @@ -171,9 +172,12 @@ &.ui-menu { padding: 0; .ui-menu-item a { + color: var(--color-text-lighter); + padding: 4px 4px 4px 14px; + &.ui-state-focus, &.ui-state-active { - font-weight: inherit; box-shadow: inset 4px 0 var(--color-primary); + color: var(--color-text); } } } diff --git a/core/js/share/sharedialoglinkshareview_popover_menu.handlebars b/core/js/share/sharedialoglinkshareview_popover_menu.handlebars index 412ed8efca0..baee3aa6630 100644 --- a/core/js/share/sharedialoglinkshareview_popover_menu.handlebars +++ b/core/js/share/sharedialoglinkshareview_popover_menu.handlebars @@ -11,6 +11,16 @@ <input id="linkText-{{cid}}" class="linkText" type="text" readonly="readonly" value="{{shareLinkURL}}" /> </span> </li> + {{#if showHideDownloadCheckbox}} + <li> + <span class="shareOption menuitem"> + <span class="icon-loading-small hidden"></span> + <input type="checkbox" name="hideDownload" id="sharingDialogHideDownload-{{cid}}" class="checkbox hideDownloadCheckbox" + {{#if hideDownload}}checked="checked"{{/if}} /> + <label for="sharingDialogHideDownload-{{cid}}">{{hideDownloadLabel}}</label> + </span> + </li> + {{/if}} {{#if publicUpload}} <li> <span class="shareOption menuitem"> diff --git a/core/js/sharedialoglinkshareview.js b/core/js/sharedialoglinkshareview.js index aac4843c8e0..7603b058a96 100644 --- a/core/js/sharedialoglinkshareview.js +++ b/core/js/sharedialoglinkshareview.js @@ -47,6 +47,8 @@ 'change .linkCheckbox': 'onLinkCheckBoxChange', // open menu 'click .share-menu .icon-more': 'onToggleMenu', + // hide download + 'change .hideDownloadCheckbox': 'onHideDownloadChange', // password 'focusout input.linkPassText': 'onPasswordEntered', 'keyup input.linkPassText': 'onPasswordKeyUp', @@ -179,6 +181,20 @@ $el.select(); }, + onHideDownloadChange: function() { + var $checkbox = this.$('.hideDownloadCheckbox'); + $checkbox.siblings('.icon-loading-small').removeClass('hidden').addClass('inlineblock'); + + var hideDownload = false; + if($checkbox.is(':checked')) { + hideDownload = true; + } + + this.model.saveLinkShare({ + hideDownload: hideDownload + }); + }, + onShowPasswordClick: function() { this.$el.find('.linkPass').slideToggle(OC.menuSpeed); this.$el.find('.linkPassMenu').toggleClass('hidden'); @@ -401,6 +417,9 @@ var passwordPlaceholderInitial = this.configModel.get('enforcePasswordForPublicLink') ? PASSWORD_PLACEHOLDER_MESSAGE : PASSWORD_PLACEHOLDER_MESSAGE_OPTIONAL; + var showHideDownloadCheckbox = !this.model.isFolder(); + var hideDownload = this.model.get('linkShare').hideDownload; + var publicEditable = !this.model.isFolder() && isLinkShare @@ -464,6 +483,9 @@ shareLinkURL: this.model.get('linkShare').link, urlLabel: t('core', 'Link'), + showHideDownloadCheckbox: showHideDownloadCheckbox, + hideDownload: hideDownload, + hideDownloadLabel: t('core', 'Hide download'), enablePasswordLabel: t('core', 'Password protect'), passwordLabel: t('core', 'Password'), passwordPlaceholder: isPasswordSet ? PASSWORD_PLACEHOLDER : PASSWORD_PLACEHOLDER_MESSAGE, diff --git a/core/js/sharedialogview.js b/core/js/sharedialogview.js index 9c648357e61..082bf9571d7 100644 --- a/core/js/sharedialogview.js +++ b/core/js/sharedialogview.js @@ -312,6 +312,41 @@ var suggestions = exactMatches.concat(users).concat(groups).concat(remotes).concat(remoteGroups).concat(emails).concat(circles).concat(rooms).concat(lookup); + function dynamicSort(property) { + return function (a,b) { + var aProperty = ''; + var bProperty = ''; + if (typeof a[property] !== 'undefined') { + aProperty = a[property]; + } + if (typeof b[property] !== 'undefined') { + bProperty = b[property]; + } + return (aProperty < bProperty) ? -1 : (aProperty > bProperty) ? 1 : 0; + } + } + + /** + * Sort share entries by uuid to properly group them + */ + var grouped = suggestions.sort(dynamicSort('uuid')); + + var previousUuid = null; + var groupedLength = grouped.length; + var result = []; + /** + * build the result array that only contains all contact entries from + * merged contacts, if the search term matches its contact name + */ + for (i = 0; i < groupedLength; i++) { + if (typeof grouped[i].uuid !== 'undefined' && grouped[i].uuid === previousUuid) { + grouped[i].merged = true; + } + if (searchTerm === grouped[i].name || typeof grouped[i].merged === 'undefined') { + result.push(grouped[i]); + } + previousUuid = grouped[i].uuid; + } var moreResultsAvailable = ( oc_config['sharing.maxAutocompleteResults'] > 0 @@ -328,7 +363,7 @@ ) ); - deferred.resolve(suggestions, exactMatches, moreResultsAvailable); + deferred.resolve(result, exactMatches, moreResultsAvailable); } else { deferred.reject(result.ocs.meta.message); } @@ -441,33 +476,72 @@ }, autocompleteRenderItem: function(ul, item) { - + var icon = 'icon-user'; var text = item.label; + if (typeof item.name !== 'undefined') { + text = item.name; + } if (item.value.shareType === OC.Share.SHARE_TYPE_GROUP) { - text = t('core', '{sharee} (group)', { sharee: text }, undefined, { escape: false }); + icon = 'icon-contacts-dark'; } else if (item.value.shareType === OC.Share.SHARE_TYPE_REMOTE) { - text = t('core', '{sharee} (remote)', {sharee: text}, undefined, {escape: false}); + icon = 'icon-shared'; } else if (item.value.shareType === OC.Share.SHARE_TYPE_REMOTE_GROUP) { text = t('core', '{sharee} (remote group)', { sharee: text }, undefined, { escape: false }); + icon = 'icon-shared'; } else if (item.value.shareType === OC.Share.SHARE_TYPE_EMAIL) { - text = t('core', '{sharee} (email)', { sharee: text }, undefined, { escape: false }); + icon = 'icon-mail'; } else if (item.value.shareType === OC.Share.SHARE_TYPE_CIRCLE) { text = t('core', '{sharee} ({type}, {owner})', {sharee: text, type: item.value.circleInfo, owner: item.value.circleOwner}, undefined, {escape: false}); + icon = 'icon-circle'; } else if (item.value.shareType === OC.Share.SHARE_TYPE_ROOM) { - text = t('core', '{sharee} (conversation)', { sharee: text }, undefined, { escape: false }); + icon = 'icon-talk'; + } + var description = ''; + var getTranslatedType = function(type) { + switch (type) { + case 'HOME': + return t('core', 'Home'); + case 'WORK': + return t('core', 'Home'); + case 'OTHER': + return t('core', 'Other'); + default: + return type; + } + }; + if (typeof item.type !== 'undefined' && item.type !== null) { + description = getTranslatedType(item.type); } var insert = $("<div class='share-autocomplete-item'/>"); - var avatar = $("<div class='avatardiv'></div>").appendTo(insert); - if (item.value.shareType === OC.Share.SHARE_TYPE_USER || item.value.shareType === OC.Share.SHARE_TYPE_CIRCLE) { - avatar.avatar(item.value.shareWith, 32, undefined, undefined, undefined, item.label); + if (item.merged) { + insert.addClass('merged'); + text = item.value.shareWith; } else { - avatar.imageplaceholder(text, undefined, 32); + var avatar = $("<div class='avatardiv'></div>").appendTo(insert); + if (item.value.shareType === OC.Share.SHARE_TYPE_USER || item.value.shareType === OC.Share.SHARE_TYPE_CIRCLE) { + avatar.avatar(item.value.shareWith, 32, undefined, undefined, undefined, item.label); + } else { + if (typeof item.uuid === 'undefined') { + item.uuid = text; + } + avatar.imageplaceholder(item.uuid, text, 32); + } + description = item.value.shareWith; + } + if (description !== '') { + insert.addClass('with-description'); } $("<div class='autocomplete-item-text'></div>") - .text(text) + .html( + text.replace( + new RegExp(this.term, "gi"), + "<span class='ui-state-highlight'>$&</span>") + + '<span class="autocomplete-item-details">' + description + '</span>' + ) .appendTo(insert); insert.attr('title', item.value.shareWith); + insert.append('<span class="icon '+icon+'" title="' + text + '"></span>'); insert = $("<a>") .append(insert); return $("<li>") @@ -479,6 +553,20 @@ _onSelectRecipient: function(e, s) { var self = this; + if (e.keyCode == 9) { + e.preventDefault(); + if (typeof s.item.name !== 'undefined') { + e.target.value = s.item.name; + } else { + e.target.value = s.item.label; + } + setTimeout(function() { + $(e.target).attr('disabled', false) + .autocomplete('search', $(e.target).val()); + }, 0); + return false; + } + e.preventDefault(); // Ensure that the keydown handler for the input field is not // called; otherwise it would try to add the recipient again, which diff --git a/core/js/shareitemmodel.js b/core/js/shareitemmodel.js index f4a3caf1370..3f92a8591e5 100644 --- a/core/js/shareitemmodel.js +++ b/core/js/shareitemmodel.js @@ -18,6 +18,7 @@ * @typedef {object} OC.Share.Types.LinkShareInfo * @property {bool} isLinkShare * @property {string} token + * @property {bool} hideDownload * @property {string|null} password * @property {string} link * @property {number} permissions @@ -136,6 +137,7 @@ call = this.updateShare(shareId, attributes, options); } else { attributes = _.defaults(attributes, { + hideDownload: false, password: '', passwordChanged: false, permissions: OC.PERMISSION_READ, @@ -866,6 +868,9 @@ isLinkShare: true, id: share.id, token: share.token, + // hide_download is returned as an int, so force it + // to a boolean + hideDownload: !!share.hide_download, password: share.share_with, link: link, permissions: share.permissions, diff --git a/core/js/sharetemplates.js b/core/js/sharetemplates.js index efdd3ff6606..0c1fee37455 100644 --- a/core/js/sharetemplates.js +++ b/core/js/sharetemplates.js @@ -61,6 +61,20 @@ templates['sharedialoglinkshareview'] = template({"1":function(container,depth0, templates['sharedialoglinkshareview_popover_menu'] = template({"1":function(container,depth0,helpers,partials,data) { var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression; + return " <li>\n <span class=\"shareOption menuitem\">\n <span class=\"icon-loading-small hidden\"></span>\n <input type=\"checkbox\" name=\"hideDownload\" id=\"sharingDialogHideDownload-" + + alias4(((helper = (helper = helpers.cid || (depth0 != null ? depth0.cid : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"cid","hash":{},"data":data}) : helper))) + + "\" class=\"checkbox hideDownloadCheckbox\"\n " + + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.hideDownload : depth0),{"name":"if","hash":{},"fn":container.program(2, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + + " />\n <label for=\"sharingDialogHideDownload-" + + alias4(((helper = (helper = helpers.cid || (depth0 != null ? depth0.cid : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"cid","hash":{},"data":data}) : helper))) + + "\">" + + alias4(((helper = (helper = helpers.hideDownloadLabel || (depth0 != null ? depth0.hideDownloadLabel : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"hideDownloadLabel","hash":{},"data":data}) : helper))) + + "</label>\n </span>\n </li>\n"; +},"2":function(container,depth0,helpers,partials,data) { + return "checked=\"checked\""; +},"4":function(container,depth0,helpers,partials,data) { + var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression; + return " <li>\n <span class=\"shareOption menuitem\">\n <span class=\"icon-loading-small hidden\"></span>\n <input type=\"radio\" name=\"publicUpload\" value=\"" + alias4(((helper = (helper = helpers.publicUploadRValue || (depth0 != null ? depth0.publicUploadRValue : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"publicUploadRValue","hash":{},"data":data}) : helper))) + "\" id=\"sharingDialogAllowPublicUpload-r-" @@ -92,7 +106,7 @@ templates['sharedialoglinkshareview_popover_menu'] = template({"1":function(cont + "\">" + alias4(((helper = (helper = helpers.publicUploadWLabel || (depth0 != null ? depth0.publicUploadWLabel : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"publicUploadWLabel","hash":{},"data":data}) : helper))) + "</label>\n </span>\n </li>\n"; -},"3":function(container,depth0,helpers,partials,data) { +},"6":function(container,depth0,helpers,partials,data) { var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression; return " <li id=\"allowPublicEditingWrapper\">\n <span class=\"shareOption menuitem\">\n <span class=\"icon-loading-small hidden\"></span>\n <input type=\"checkbox\" name=\"allowPublicEditing\" id=\"sharingDialogAllowPublicEditing-" @@ -104,41 +118,39 @@ templates['sharedialoglinkshareview_popover_menu'] = template({"1":function(cont + "\">" + alias4(((helper = (helper = helpers.publicEditingLabel || (depth0 != null ? depth0.publicEditingLabel : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"publicEditingLabel","hash":{},"data":data}) : helper))) + "</label>\n </span>\n </li>\n"; -},"5":function(container,depth0,helpers,partials,data) { +},"8":function(container,depth0,helpers,partials,data) { var stack1, helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression; return " <li>\n <span class=\"shareOption menuitem\">\n <input type=\"checkbox\" name=\"showPassword\" id=\"showPassword-" + alias4(((helper = (helper = helpers.cid || (depth0 != null ? depth0.cid : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"cid","hash":{},"data":data}) : helper))) + "\" class=\"checkbox showPasswordCheckbox\"\n " - + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.isPasswordSet : depth0),{"name":"if","hash":{},"fn":container.program(6, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.isPasswordSet : depth0),{"name":"if","hash":{},"fn":container.program(2, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + " " - + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.isPasswordEnforced : depth0),{"name":"if","hash":{},"fn":container.program(8, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.isPasswordEnforced : depth0),{"name":"if","hash":{},"fn":container.program(9, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + " value=\"1\" />\n <label for=\"showPassword-" + alias4(((helper = (helper = helpers.cid || (depth0 != null ? depth0.cid : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"cid","hash":{},"data":data}) : helper))) + "\">" + alias4(((helper = (helper = helpers.enablePasswordLabel || (depth0 != null ? depth0.enablePasswordLabel : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"enablePasswordLabel","hash":{},"data":data}) : helper))) + "</label>\n </span>\n </li>\n <li class=\"" - + ((stack1 = helpers.unless.call(alias1,(depth0 != null ? depth0.isPasswordSet : depth0),{"name":"unless","hash":{},"fn":container.program(10, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + + ((stack1 = helpers.unless.call(alias1,(depth0 != null ? depth0.isPasswordSet : depth0),{"name":"unless","hash":{},"fn":container.program(11, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + " linkPassMenu\">\n <span class=\"shareOption menuitem icon-share-pass\">\n <input id=\"linkPassText-" + alias4(((helper = (helper = helpers.cid || (depth0 != null ? depth0.cid : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"cid","hash":{},"data":data}) : helper))) + "\" class=\"linkPassText\" type=\"password\" placeholder=\"" + alias4(((helper = (helper = helpers.passwordPlaceholder || (depth0 != null ? depth0.passwordPlaceholder : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"passwordPlaceholder","hash":{},"data":data}) : helper))) + "\" autocomplete=\"new-password\" />\n <span class=\"icon icon-loading-small hidden\"></span>\n </span>\n </li>\n"; -},"6":function(container,depth0,helpers,partials,data) { - return "checked=\"checked\""; -},"8":function(container,depth0,helpers,partials,data) { +},"9":function(container,depth0,helpers,partials,data) { return "disabled=\"disabled\""; -},"10":function(container,depth0,helpers,partials,data) { +},"11":function(container,depth0,helpers,partials,data) { return "hidden"; -},"12":function(container,depth0,helpers,partials,data) { +},"13":function(container,depth0,helpers,partials,data) { var helper; return container.escapeExpression(((helper = (helper = helpers.expireDate || (depth0 != null ? depth0.expireDate : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"expireDate","hash":{},"data":data}) : helper))); -},"14":function(container,depth0,helpers,partials,data) { +},"15":function(container,depth0,helpers,partials,data) { var helper; return container.escapeExpression(((helper = (helper = helpers.defaultExpireDate || (depth0 != null ? depth0.defaultExpireDate : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"defaultExpireDate","hash":{},"data":data}) : helper))); -},"16":function(container,depth0,helpers,partials,data) { +},"17":function(container,depth0,helpers,partials,data) { var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression; return " <li>\n <a href=\"#\" class=\"shareOption menuitem pop-up\" data-url=\"" @@ -162,21 +174,22 @@ templates['sharedialoglinkshareview_popover_menu'] = template({"1":function(cont + "\" class=\"linkText\" type=\"text\" readonly=\"readonly\" value=\"" + alias4(((helper = (helper = helpers.shareLinkURL || (depth0 != null ? depth0.shareLinkURL : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"shareLinkURL","hash":{},"data":data}) : helper))) + "\" />\n </span>\n </li>\n" - + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.publicUpload : depth0),{"name":"if","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") - + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.publicEditing : depth0),{"name":"if","hash":{},"fn":container.program(3, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") - + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.showPasswordCheckBox : depth0),{"name":"if","hash":{},"fn":container.program(5, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.showHideDownloadCheckbox : depth0),{"name":"if","hash":{},"fn":container.program(1, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.publicUpload : depth0),{"name":"if","hash":{},"fn":container.program(4, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.publicEditing : depth0),{"name":"if","hash":{},"fn":container.program(6, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.showPasswordCheckBox : depth0),{"name":"if","hash":{},"fn":container.program(8, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + " <li>\n <span class=\"shareOption menuitem\">\n <input id=\"expireDate-" + alias4(((helper = (helper = helpers.cid || (depth0 != null ? depth0.cid : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"cid","hash":{},"data":data}) : helper))) + "\" type=\"checkbox\" name=\"expirationDate\" class=\"expireDate checkbox\"\n " - + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.hasExpireDate : depth0),{"name":"if","hash":{},"fn":container.program(6, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.hasExpireDate : depth0),{"name":"if","hash":{},"fn":container.program(2, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + " " - + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.isExpirationEnforced : depth0),{"name":"if","hash":{},"fn":container.program(8, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.isExpirationEnforced : depth0),{"name":"if","hash":{},"fn":container.program(9, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + "\" />\n <label for=\"expireDate-" + alias4(((helper = (helper = helpers.cid || (depth0 != null ? depth0.cid : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"cid","hash":{},"data":data}) : helper))) + "\">" + alias4(((helper = (helper = helpers.expireDateLabel || (depth0 != null ? depth0.expireDateLabel : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"expireDateLabel","hash":{},"data":data}) : helper))) + "</label>\n </span>\n </li>\n <li class=\"" - + ((stack1 = helpers.unless.call(alias1,(depth0 != null ? depth0.hasExpireDate : depth0),{"name":"unless","hash":{},"fn":container.program(10, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + + ((stack1 = helpers.unless.call(alias1,(depth0 != null ? depth0.hasExpireDate : depth0),{"name":"unless","hash":{},"fn":container.program(11, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + "\">\n <span class=\"menuitem icon-expiredate expirationDateContainer-" + alias4(((helper = (helper = helpers.cid || (depth0 != null ? depth0.cid : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"cid","hash":{},"data":data}) : helper))) + "\">\n <label for=\"expirationDatePicker-" @@ -190,7 +203,7 @@ templates['sharedialoglinkshareview_popover_menu'] = template({"1":function(cont + "\" class=\"datepicker\" type=\"text\" placeholder=\"" + alias4(((helper = (helper = helpers.expirationDatePlaceholder || (depth0 != null ? depth0.expirationDatePlaceholder : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"expirationDatePlaceholder","hash":{},"data":data}) : helper))) + "\" value=\"" - + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.hasExpireDate : depth0),{"name":"if","hash":{},"fn":container.program(12, data, 0),"inverse":container.program(14, data, 0),"data":data})) != null ? stack1 : "") + + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.hasExpireDate : depth0),{"name":"if","hash":{},"fn":container.program(13, data, 0),"inverse":container.program(15, data, 0),"data":data})) != null ? stack1 : "") + "\" />\n </span>\n </li>\n <li>\n <a href=\"#\" class=\"share-add\">\n <span class=\"icon-loading-small hidden\"></span>\n <span class=\"icon icon-edit\"></span>\n <span>" + alias4(((helper = (helper = helpers.addNoteLabel || (depth0 != null ? depth0.addNoteLabel : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"addNoteLabel","hash":{},"data":data}) : helper))) + "</span>\n <input type=\"button\" class=\"share-note-delete icon-delete\">\n </a>\n </li>\n <li class=\"share-note-form share-note-link hidden\">\n <span class=\"menuitem icon-note\">\n <textarea class=\"share-note\">" @@ -198,7 +211,7 @@ templates['sharedialoglinkshareview_popover_menu'] = template({"1":function(cont + "</textarea>\n <input type=\"submit\" class=\"icon-confirm share-note-submit\" value=\"\" id=\"add-note-" + alias4(((helper = (helper = helpers.shareId || (depth0 != null ? depth0.shareId : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"shareId","hash":{},"data":data}) : helper))) + "\" />\n </span>\n </li>\n" - + ((stack1 = helpers.each.call(alias1,(depth0 != null ? depth0.social : depth0),{"name":"each","hash":{},"fn":container.program(16, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + + ((stack1 = helpers.each.call(alias1,(depth0 != null ? depth0.social : depth0),{"name":"each","hash":{},"fn":container.program(17, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + " </ul>\n</div>\n"; },"useData":true}); templates['sharedialoglinkshareview_popover_menu_pending'] = template({"1":function(container,depth0,helpers,partials,data) { diff --git a/core/js/tests/specs/sharedialoglinkshareview.js b/core/js/tests/specs/sharedialoglinkshareview.js index 9d07dcb479d..d8dec3968e3 100644 --- a/core/js/tests/specs/sharedialoglinkshareview.js +++ b/core/js/tests/specs/sharedialoglinkshareview.js @@ -72,6 +72,100 @@ describe('OC.Share.ShareDialogLinkShareView', function () { configModel.isShareWithLinkAllowed.restore(); }); + describe('hide download', function () { + + var $hideDownloadCheckbox; + var $workingIcon; + + beforeEach(function () { + // Needed to render the view + configModel.isShareWithLinkAllowed.returns(true); + + // Setting the share also triggers the rendering + shareModel.set({ + linkShare: { + isLinkShare: true, + } + }); + + $hideDownloadCheckbox = view.$el.find('.hideDownloadCheckbox'); + $workingIcon = $hideDownloadCheckbox.prev('.icon-loading-small'); + + sinon.stub(shareModel, 'saveLinkShare'); + + expect($workingIcon.hasClass('hidden')).toBeTruthy(); + }); + + afterEach(function () { + shareModel.saveLinkShare.restore(); + }); + + it('is shown if the share is a file', function() { + expect($hideDownloadCheckbox.length).toBeTruthy(); + }); + + it('is not shown if the share is a folder', function() { + shareModel.fileInfoModel.set('mimetype', 'httpd/unix-directory'); + + // Setting the item type also triggers the rendering + shareModel.set({ + itemType: 'folder' + }); + + $hideDownloadCheckbox = view.$el.find('.hideDownloadCheckbox'); + + expect($hideDownloadCheckbox.length).toBeFalsy(); + }); + + it('checkbox is checked when the setting is enabled', function () { + shareModel.set({ + linkShare: { + isLinkShare: true, + hideDownload: true + } + }); + + $hideDownloadCheckbox = view.$el.find('.hideDownloadCheckbox'); + + expect($hideDownloadCheckbox.is(':checked')).toEqual(true); + }); + + it('checkbox is not checked when the setting is disabled', function () { + expect($hideDownloadCheckbox.is(':checked')).toEqual(false); + }); + + it('enables the setting if clicked when unchecked', function () { + // Simulate the click by checking the checkbox and then triggering + // the "change" event. + $hideDownloadCheckbox.prop('checked', true); + $hideDownloadCheckbox.change(); + + expect($workingIcon.hasClass('hidden')).toBeFalsy(); + expect(shareModel.saveLinkShare.withArgs({ hideDownload: true }).calledOnce).toBeTruthy(); + }); + + it('disables the setting if clicked when checked', function () { + shareModel.set({ + linkShare: { + isLinkShare: true, + hideDownload: true + } + }); + + $hideDownloadCheckbox = view.$el.find('.hideDownloadCheckbox'); + $workingIcon = $hideDownloadCheckbox.prev('.icon-loading-small'); + + // Simulate the click by unchecking the checkbox and then triggering + // the "change" event. + $hideDownloadCheckbox.prop('checked', false); + $hideDownloadCheckbox.change(); + + expect($workingIcon.hasClass('hidden')).toBeFalsy(); + expect(shareModel.saveLinkShare.withArgs({ hideDownload: false }).calledOnce).toBeTruthy(); + }); + + }); + describe('onPasswordEntered', function () { var $passwordText; diff --git a/core/js/tests/specs/shareitemmodelSpec.js b/core/js/tests/specs/shareitemmodelSpec.js index 2e89b2e3cda..a2eabbf4ae4 100644 --- a/core/js/tests/specs/shareitemmodelSpec.js +++ b/core/js/tests/specs/shareitemmodelSpec.js @@ -168,7 +168,8 @@ describe('OC.Share.ShareItemModel', function() { stime: 1403884258, storage: 1, token: 'tehtoken', - uid_owner: 'root' + uid_owner: 'root', + hide_download: 1 } ])); @@ -186,6 +187,7 @@ describe('OC.Share.ShareItemModel', function() { var linkShare = model.get('linkShare'); expect(linkShare.isLinkShare).toEqual(true); + expect(linkShare.hideDownload).toEqual(true); // TODO: check more attributes }); @@ -289,7 +291,8 @@ describe('OC.Share.ShareItemModel', function() { stime: 1403884258, storage: 1, token: 'tehtoken', - uid_owner: 'root' + uid_owner: 'root', + hide_download: 0 }, { displayname_owner: 'root', expiration: '2015-10-15 00:00:00', @@ -307,7 +310,8 @@ describe('OC.Share.ShareItemModel', function() { stime: 1403884509, storage: 1, token: 'anothertoken', - uid_owner: 'root' + uid_owner: 'root', + hide_download: 1 }] )); OC.currentUser = 'root'; @@ -320,6 +324,7 @@ describe('OC.Share.ShareItemModel', function() { var linkShare = model.get('linkShare'); expect(linkShare.isLinkShare).toEqual(true); expect(linkShare.token).toEqual('tehtoken'); + expect(linkShare.hideDownload).toEqual(false); // TODO: check child too }); @@ -579,6 +584,7 @@ describe('OC.Share.ShareItemModel', function() { expect(addShareStub.calledOnce).toEqual(true); expect(addShareStub.firstCall.args[0]).toEqual({ + hideDownload: false, password: '', passwordChanged: false, permissions: OC.PERMISSION_READ, @@ -603,6 +609,7 @@ describe('OC.Share.ShareItemModel', function() { expect(addShareStub.calledOnce).toEqual(true); expect(addShareStub.firstCall.args[0]).toEqual({ + hideDownload: false, password: '', passwordChanged: false, permissions: OC.PERMISSION_READ, diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index f1f18457fff..a2a483f1f4b 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -668,6 +668,7 @@ return array( 'OC\\Core\\Migrations\\Version14000Date20180710092004' => $baseDir . '/core/Migrations/Version14000Date20180710092004.php', 'OC\\Core\\Migrations\\Version14000Date20180712153140' => $baseDir . '/core/Migrations/Version14000Date20180712153140.php', 'OC\\Core\\Migrations\\Version15000Date20180926101451' => $baseDir . '/core/Migrations/Version15000Date20180926101451.php', + 'OC\\Core\\Migrations\\Version15000Date20181015062942' => $baseDir . '/core/Migrations/Version15000Date20181015062942.php', 'OC\\DB\\Adapter' => $baseDir . '/lib/private/DB/Adapter.php', 'OC\\DB\\AdapterMySQL' => $baseDir . '/lib/private/DB/AdapterMySQL.php', 'OC\\DB\\AdapterOCI8' => $baseDir . '/lib/private/DB/AdapterOCI8.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index d91bfbc9cf8..b4b84cfa962 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -698,6 +698,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OC\\Core\\Migrations\\Version14000Date20180710092004' => __DIR__ . '/../../..' . '/core/Migrations/Version14000Date20180710092004.php', 'OC\\Core\\Migrations\\Version14000Date20180712153140' => __DIR__ . '/../../..' . '/core/Migrations/Version14000Date20180712153140.php', 'OC\\Core\\Migrations\\Version15000Date20180926101451' => __DIR__ . '/../../..' . '/core/Migrations/Version15000Date20180926101451.php', + 'OC\\Core\\Migrations\\Version15000Date20181015062942' => __DIR__ . '/../../..' . '/core/Migrations/Version15000Date20181015062942.php', 'OC\\DB\\Adapter' => __DIR__ . '/../../..' . '/lib/private/DB/Adapter.php', 'OC\\DB\\AdapterMySQL' => __DIR__ . '/../../..' . '/lib/private/DB/AdapterMySQL.php', 'OC\\DB\\AdapterOCI8' => __DIR__ . '/../../..' . '/lib/private/DB/AdapterOCI8.php', diff --git a/lib/private/Collaboration/Collaborators/MailPlugin.php b/lib/private/Collaboration/Collaborators/MailPlugin.php index 101d6845ec3..6faa5d5d125 100644 --- a/lib/private/Collaboration/Collaborators/MailPlugin.php +++ b/lib/private/Collaboration/Collaborators/MailPlugin.php @@ -84,11 +84,17 @@ class MailPlugin implements ISearchPlugin { foreach ($addressBookContacts as $contact) { if (isset($contact['EMAIL'])) { $emailAddresses = $contact['EMAIL']; - if (!is_array($emailAddresses)) { + if (\is_string($emailAddresses)) { $emailAddresses = [$emailAddresses]; } - foreach ($emailAddresses as $emailAddress) { + foreach ($emailAddresses as $type => $emailAddress) { $displayName = $emailAddress; + $emailAddressType = null; + if (\is_array($emailAddress)) { + $emailAddressData = $emailAddress; + $emailAddress = $emailAddressData['value']; + $emailAddressType = $emailAddressData['type']; + } if (isset($contact['FN'])) { $displayName = $contact['FN'] . ' (' . $emailAddress . ')'; } @@ -121,6 +127,8 @@ class MailPlugin implements ISearchPlugin { if (!$this->isCurrentUser($cloud) && !$searchResult->hasResult($userType, $cloud->getUser())) { $singleResult = [[ 'label' => $displayName, + 'uuid' => $contact['UID'], + 'name' => $contact['FN'], 'value' => [ 'shareType' => Share::SHARE_TYPE_USER, 'shareWith' => $cloud->getUser(), @@ -142,6 +150,8 @@ class MailPlugin implements ISearchPlugin { if (!$this->isCurrentUser($cloud) && !$searchResult->hasResult($userType, $cloud->getUser())) { $userResults['wide'][] = [ 'label' => $displayName, + 'uuid' => $contact['UID'], + 'name' => $contact['FN'], 'value' => [ 'shareType' => Share::SHARE_TYPE_USER, 'shareWith' => $cloud->getUser(), @@ -160,6 +170,9 @@ class MailPlugin implements ISearchPlugin { } $result['exact'][] = [ 'label' => $displayName, + 'uuid' => $contact['UID'], + 'name' => $contact['FN'], + 'type' => $emailAddressType ?? '', 'value' => [ 'shareType' => Share::SHARE_TYPE_EMAIL, 'shareWith' => $emailAddress, @@ -168,6 +181,9 @@ class MailPlugin implements ISearchPlugin { } else { $result['wide'][] = [ 'label' => $displayName, + 'uuid' => $contact['UID'], + 'name' => $contact['FN'], + 'type' => $emailAddressType ?? '', 'value' => [ 'shareType' => Share::SHARE_TYPE_EMAIL, 'shareWith' => $emailAddress, @@ -194,6 +210,7 @@ class MailPlugin implements ISearchPlugin { if (!$searchResult->hasExactIdMatch($emailType) && filter_var($search, FILTER_VALIDATE_EMAIL)) { $result['exact'][] = [ 'label' => $search, + 'uuid' => $search, 'value' => [ 'shareType' => Share::SHARE_TYPE_EMAIL, 'shareWith' => $search, diff --git a/lib/private/Collaboration/Collaborators/RemotePlugin.php b/lib/private/Collaboration/Collaborators/RemotePlugin.php index e0f5298f83b..d877346b155 100644 --- a/lib/private/Collaboration/Collaborators/RemotePlugin.php +++ b/lib/private/Collaboration/Collaborators/RemotePlugin.php @@ -30,6 +30,8 @@ use OCP\Collaboration\Collaborators\SearchResultType; use OCP\Contacts\IManager; use OCP\Federation\ICloudIdManager; use OCP\IConfig; +use OCP\IUserManager; +use OCP\IUserSession; use OCP\Share; class RemotePlugin implements ISearchPlugin { @@ -41,12 +43,20 @@ class RemotePlugin implements ISearchPlugin { private $cloudIdManager; /** @var IConfig */ private $config; + /** @var IUserManager */ + private $userManager; + /** @var string */ + private $userId = ''; - public function __construct(IManager $contactsManager, ICloudIdManager $cloudIdManager, IConfig $config) { + public function __construct(IManager $contactsManager, ICloudIdManager $cloudIdManager, IConfig $config, IUserManager $userManager, IUserSession $userSession) { $this->contactsManager = $contactsManager; $this->cloudIdManager = $cloudIdManager; $this->config = $config; - + $this->userManager = $userManager; + $user = $userSession->getUser(); + if ($user !== null) { + $this->userId = $user->getUID(); + } $this->shareeEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') === 'yes'; } @@ -63,23 +73,47 @@ class RemotePlugin implements ISearchPlugin { } if (isset($contact['CLOUD'])) { $cloudIds = $contact['CLOUD']; - if (!is_array($cloudIds)) { + if (is_string($cloudIds)) { $cloudIds = [$cloudIds]; } $lowerSearch = strtolower($search); foreach ($cloudIds as $cloudId) { + $cloudIdType = ''; + if (\is_array($cloudId)) { + $cloudIdData = $cloudId; + $cloudId = $cloudIdData['value']; + $cloudIdType = $cloudIdData['type']; + } try { - list(, $serverUrl) = $this->splitUserRemote($cloudId); + list($remoteUser, $serverUrl) = $this->splitUserRemote($cloudId); } catch (\InvalidArgumentException $e) { continue; } + $localUser = $this->userManager->get($remoteUser); + /** + * Add local share if remote cloud id matches a local user ones + */ + if ($localUser !== null && $remoteUser !== $this->userId && $cloudId === $localUser->getCloudId() ) { + $result['wide'][] = [ + 'label' => $contact['FN'], + 'uuid' => $contact['UID'], + 'value' => [ + 'shareType' => Share::SHARE_TYPE_USER, + 'shareWith' => $remoteUser + ] + ]; + } + if (strtolower($contact['FN']) === $lowerSearch || strtolower($cloudId) === $lowerSearch) { if (strtolower($cloudId) === $lowerSearch) { $searchResult->markExactIdMatch($resultType); } $result['exact'][] = [ 'label' => $contact['FN'] . " ($cloudId)", + 'uuid' => $contact['UID'], + 'name' => $contact['FN'], + 'type' => $cloudIdType, 'value' => [ 'shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => $cloudId, @@ -89,6 +123,9 @@ class RemotePlugin implements ISearchPlugin { } else { $result['wide'][] = [ 'label' => $contact['FN'] . " ($cloudId)", + 'uuid' => $contact['UID'], + 'name' => $contact['FN'], + 'type' => $cloudIdType, 'value' => [ 'shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => $cloudId, @@ -106,14 +143,24 @@ class RemotePlugin implements ISearchPlugin { $result['wide'] = array_slice($result['wide'], $offset, $limit); } + /** + * Add generic share with remote item for valid cloud ids that are not users of the local instance + */ if (!$searchResult->hasExactIdMatch($resultType) && $this->cloudIdManager->isValidCloudId($search) && $offset === 0) { - $result['exact'][] = [ - 'label' => $search, - 'value' => [ - 'shareType' => Share::SHARE_TYPE_REMOTE, - 'shareWith' => $search, - ], - ]; + try { + list($remoteUser, $serverUrl) = $this->splitUserRemote($search); + $localUser = $this->userManager->get($remoteUser); + if ($localUser === null || $search !== $localUser->getCloudId()) { + $result['exact'][] = [ + 'label' => $search, + 'value' => [ + 'shareType' => Share::SHARE_TYPE_REMOTE, + 'shareWith' => $search, + ], + ]; + } + } catch (\InvalidArgumentException $e) { + } } $searchResult->addResultSet($resultType, $result['wide'], $result['exact']); diff --git a/lib/private/Share20/DefaultShareProvider.php b/lib/private/Share20/DefaultShareProvider.php index 9c5d78a5958..3dcca0facbc 100644 --- a/lib/private/Share20/DefaultShareProvider.php +++ b/lib/private/Share20/DefaultShareProvider.php @@ -296,6 +296,7 @@ class DefaultShareProvider implements IShareProvider { ->set('token', $qb->createNamedParameter($share->getToken())) ->set('expiration', $qb->createNamedParameter($share->getExpirationDate(), IQueryBuilder::PARAM_DATE)) ->set('note', $qb->createNamedParameter($share->getNote())) + ->set('hide_download', $qb->createNamedParameter($share->getHideDownload() ? 1 : 0), IQueryBuilder::PARAM_INT) ->execute(); } @@ -953,6 +954,7 @@ class DefaultShareProvider implements IShareProvider { } $share->setProviderId($this->identifier()); + $share->setHideDownload((int)$data['hide_download'] === 1); return $share; } diff --git a/lib/private/Share20/Share.php b/lib/private/Share20/Share.php index 71c0453d9e5..e218360f87b 100644 --- a/lib/private/Share20/Share.php +++ b/lib/private/Share20/Share.php @@ -30,6 +30,7 @@ use OCP\Files\Node; use OCP\Files\NotFoundException; use OCP\IUserManager; use OCP\Share\Exceptions\IllegalIDChangeException; +use OCP\Share\IShare; class Share implements \OCP\Share\IShare { @@ -85,6 +86,9 @@ class Share implements \OCP\Share\IShare { /** @var ICacheEntry|null */ private $nodeCacheEntry; + /** @var bool */ + private $hideDownload = false; + public function __construct(IRootFolder $rootFolder, IUserManager $userManager) { $this->rootFolder = $rootFolder; $this->userManager = $userManager; @@ -514,4 +518,13 @@ class Share implements \OCP\Share\IShare { public function getNodeCacheEntry() { return $this->nodeCacheEntry; } + + public function setHideDownload(bool $hide): IShare { + $this->hideDownload = $hide; + return $this; + } + + public function getHideDownload(): bool { + return $this->hideDownload; + } } diff --git a/lib/public/IAddressBook.php b/lib/public/IAddressBook.php index 67c34c9e8c9..4739e6f0c5b 100644 --- a/lib/public/IAddressBook.php +++ b/lib/public/IAddressBook.php @@ -55,16 +55,18 @@ namespace OCP { /** * @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']] + * ] * @since 5.0.0 */ public function search($pattern, $searchProperties, $options); - // // dummy results - // return array( - // array('id' => 0, 'FN' => 'Thomas Müller', 'EMAIL' => 'a@b.c', 'GEO' => '37.386013;-122.082932'), - // array('id' => 5, 'FN' => 'Thomas Tanghus', 'EMAIL' => array('d@e.f', 'g@h.i')), - // ); /** * @param array $properties this array if key-value-pairs defines a contact diff --git a/lib/public/Share/IShare.php b/lib/public/Share/IShare.php index 43543fdad47..dcd5fdecbea 100644 --- a/lib/public/Share/IShare.php +++ b/lib/public/Share/IShare.php @@ -418,4 +418,25 @@ interface IShare { * @since 11.0.0 */ public function getNodeCacheEntry(); + + /** + * Sets a shares hide download state + * This is mainly for public shares. It will signal that the share page should + * hide download buttons etc. + * + * @param bool $ro + * @return IShare + * @since 15.0.0 + */ + public function setHideDownload(bool $hide): IShare; + + /** + * Gets a shares hide download state + * This is mainly for public shares. It will signal that the share page should + * hide download buttons etc. + * + * @return bool + * @since 15.0.0 + */ + public function getHideDownload(): bool; } diff --git a/tests/acceptance/features/app-files.feature b/tests/acceptance/features/app-files.feature index 74490180ad3..70e085ca665 100644 --- a/tests/acceptance/features/app-files.feature +++ b/tests/acceptance/features/app-files.feature @@ -121,6 +121,32 @@ Feature: app-files And I open the Share menu Then I see that the Share menu is shown + Scenario: hide download in a public shared link + Given I act as John + And I am logged in + And I share the link for "welcome.txt" + And I set the download of the shared link as hidden + And I write down the shared link + When I act as Jane + And I visit the shared link I wrote down + And I see that the current page is the shared link I wrote down + Then I see that the download button is not shown + And I see that the Share menu button is not shown + + Scenario: show download again in a public shared link + Given I act as John + And I am logged in + And I share the link for "welcome.txt" + And I set the download of the shared link as hidden + And I set the download of the shared link as shown + And I write down the shared link + When I act as Jane + And I visit the shared link I wrote down + And I see that the current page is the shared link I wrote down + Then I see that the download button is shown + And I open the Share menu + And I see that the Share menu is shown + Scenario: creation is not possible by default in a public shared folder Given I act as John And I am logged in diff --git a/tests/acceptance/features/bootstrap/FilesAppContext.php b/tests/acceptance/features/bootstrap/FilesAppContext.php index 408995b9a83..4b648bfc544 100644 --- a/tests/acceptance/features/bootstrap/FilesAppContext.php +++ b/tests/acceptance/features/bootstrap/FilesAppContext.php @@ -235,6 +235,27 @@ class FilesAppContext implements Context, ActorAwareInterface { /** * @return Locator */ + public static function hideDownloadCheckbox() { + // forThe()->checkbox("Hide download") can not be used here; that would + // return the checkbox itself, but the element that the user interacts + // with is the label. + return Locator::forThe()->xpath("//label[normalize-space() = 'Hide download']")-> + descendantOf(self::shareLinkMenu())-> + describedAs("Hide download checkbox in the details view in Files app"); + } + + /** + * @return Locator + */ + public static function hideDownloadCheckboxInput() { + return Locator::forThe()->checkbox("Hide download")-> + descendantOf(self::shareLinkMenu())-> + describedAs("Hide download checkbox input in the details view in Files app"); + } + + /** + * @return Locator + */ public static function allowUploadAndEditingRadioButton() { // forThe()->radio("Allow upload and editing") can not be used here; // that would return the radio button itself, but the element that the @@ -335,6 +356,28 @@ class FilesAppContext implements Context, ActorAwareInterface { } /** + * @When I set the download of the shared link as hidden + */ + public function iSetTheDownloadOfTheSharedLinkAsHidden() { + $this->showShareLinkMenuIfNeeded(); + + $this->iSeeThatTheDownloadOfTheLinkShareIsShown(); + + $this->actor->find(self::hideDownloadCheckbox(), 2)->click(); + } + + /** + * @When I set the download of the shared link as shown + */ + public function iSetTheDownloadOfTheSharedLinkAsShown() { + $this->showShareLinkMenuIfNeeded(); + + $this->iSeeThatTheDownloadOfTheLinkShareIsHidden(); + + $this->actor->find(self::hideDownloadCheckbox(), 2)->click(); + } + + /** * @When I set the shared link as editable */ public function iSetTheSharedLinkAsEditable() { @@ -461,6 +504,24 @@ class FilesAppContext implements Context, ActorAwareInterface { } /** + * @Then I see that the download of the link share is hidden + */ + public function iSeeThatTheDownloadOfTheLinkShareIsHidden() { + $this->showShareLinkMenuIfNeeded(); + + PHPUnit_Framework_Assert::assertTrue($this->actor->find(self::hideDownloadCheckboxInput(), 10)->isChecked()); + } + + /** + * @Then I see that the download of the link share is shown + */ + public function iSeeThatTheDownloadOfTheLinkShareIsShown() { + $this->showShareLinkMenuIfNeeded(); + + PHPUnit_Framework_Assert::assertFalse($this->actor->find(self::hideDownloadCheckboxInput(), 10)->isChecked()); + } + + /** * @Then I see that the working icon for password protect is shown */ public function iSeeThatTheWorkingIconForPasswordProtectIsShown() { diff --git a/tests/acceptance/features/bootstrap/FilesSharingAppContext.php b/tests/acceptance/features/bootstrap/FilesSharingAppContext.php index 1fe12d5f42d..531184442dd 100644 --- a/tests/acceptance/features/bootstrap/FilesSharingAppContext.php +++ b/tests/acceptance/features/bootstrap/FilesSharingAppContext.php @@ -104,6 +104,14 @@ class FilesSharingAppContext implements Context, ActorAwareInterface { } /** + * @return Locator + */ + public static function downloadButton() { + return Locator::forThe()->id("downloadFile")-> + describedAs("Download button in Shared file page"); + } + + /** * @When I visit the shared link I wrote down */ public function iVisitTheSharedLinkIWroteDown() { @@ -199,10 +207,42 @@ class FilesSharingAppContext implements Context, ActorAwareInterface { } /** + * @Then I see that the Share menu button is not shown + */ + public function iSeeThatTheShareMenuButtonIsNotShown() { + try { + PHPUnit_Framework_Assert::assertFalse( + $this->actor->find(self::shareMenuButton())->isVisible()); + } catch (NoSuchElementException $exception) { + } + } + + /** * @Then I see that the shared file preview shows the text :text */ public function iSeeThatTheSharedFilePreviewShowsTheText($text) { PHPUnit_Framework_Assert::assertContains($text, $this->actor->find(self::textPreview(), 10)->getText()); } + /** + * @Then I see that the download button is shown + */ + public function iSeeThatTheDownloadButtonIsShown() { + if (!WaitFor::elementToBeEventuallyShown( + $this->actor, self::downloadButton(), $timeout = 10 * $this->actor->getFindTimeoutMultiplier())) { + PHPUnit_Framework_Assert::fail("The download button is not visible yet after $timeout seconds"); + } + } + + /** + * @Then I see that the download button is not shown + */ + public function iSeeThatTheDownloadButtonIsNotShown() { + try { + PHPUnit_Framework_Assert::assertFalse( + $this->actor->find(self::downloadButton())->isVisible()); + } catch (NoSuchElementException $exception) { + } + } + } diff --git a/tests/lib/Collaboration/Collaborators/MailPluginTest.php b/tests/lib/Collaboration/Collaborators/MailPluginTest.php index 775941bd440..a9d0244d38c 100644 --- a/tests/lib/Collaboration/Collaborators/MailPluginTest.php +++ b/tests/lib/Collaboration/Collaborators/MailPluginTest.php @@ -120,16 +120,20 @@ class MailPluginTest extends TestCase { public function dataGetEmail() { return [ + // data set 0 ['test', [], true, ['emails' => [], 'exact' => ['emails' => []]], false, false], + // data set 1 ['test', [], false, ['emails' => [], 'exact' => ['emails' => []]], false, false], + // data set 2 [ 'test@remote.com', [], true, - ['emails' => [], 'exact' => ['emails' => [['label' => 'test@remote.com', 'value' => ['shareType' => Share::SHARE_TYPE_EMAIL, 'shareWith' => 'test@remote.com']]]]], + ['emails' => [], 'exact' => ['emails' => [['uuid' => 'test@remote.com', 'label' => 'test@remote.com', 'value' => ['shareType' => Share::SHARE_TYPE_EMAIL, 'shareWith' => 'test@remote.com']]]]], false, false, ], + // data set 3 [ // no valid email address 'test@remote', [], @@ -138,26 +142,31 @@ class MailPluginTest extends TestCase { false, false, ], + // data set 4 [ 'test@remote.com', [], false, - ['emails' => [], 'exact' => ['emails' => [['label' => 'test@remote.com', 'value' => ['shareType' => Share::SHARE_TYPE_EMAIL, 'shareWith' => 'test@remote.com']]]]], + ['emails' => [], 'exact' => ['emails' => [['uuid' => 'test@remote.com', 'label' => 'test@remote.com', 'value' => ['shareType' => Share::SHARE_TYPE_EMAIL, 'shareWith' => 'test@remote.com']]]]], false, false, ], + // data set 5 [ 'test', [ [ + 'UID' => 'uid3', 'FN' => 'User3 @ Localhost', ], [ + 'UID' => 'uid2', 'FN' => 'User2 @ Localhost', 'EMAIL' => [ ], ], [ + 'UID' => 'uid1', 'FN' => 'User @ Localhost', 'EMAIL' => [ 'username@localhost', @@ -165,22 +174,26 @@ class MailPluginTest extends TestCase { ], ], true, - ['emails' => [['label' => 'User @ Localhost (username@localhost)', 'value' => ['shareType' => Share::SHARE_TYPE_EMAIL, 'shareWith' => 'username@localhost']]], 'exact' => ['emails' => []]], + ['emails' => [['uuid' => 'uid1', 'name' => 'User @ Localhost', 'type' => '', 'label' => 'User @ Localhost (username@localhost)', 'value' => ['shareType' => Share::SHARE_TYPE_EMAIL, 'shareWith' => 'username@localhost']]], 'exact' => ['emails' => []]], false, false, ], + // data set 6 [ 'test', [ [ + 'UID' => 'uid3', 'FN' => 'User3 @ Localhost', ], [ + 'UID' => 'uid2', 'FN' => 'User2 @ Localhost', 'EMAIL' => [ ], ], [ + 'UID' => 'uid1', 'FN' => 'User @ Localhost', 'EMAIL' => [ 'username@localhost', @@ -192,18 +205,22 @@ class MailPluginTest extends TestCase { false, false, ], + // data set 7 [ 'test@remote.com', [ [ + 'UID' => 'uid3', 'FN' => 'User3 @ Localhost', ], [ + 'UID' => 'uid2', 'FN' => 'User2 @ Localhost', 'EMAIL' => [ ], ], [ + 'UID' => 'uid1', 'FN' => 'User @ Localhost', 'EMAIL' => [ 'username@localhost', @@ -211,22 +228,26 @@ class MailPluginTest extends TestCase { ], ], true, - ['emails' => [['label' => 'User @ Localhost (username@localhost)', 'value' => ['shareType' => Share::SHARE_TYPE_EMAIL, 'shareWith' => 'username@localhost']]], 'exact' => ['emails' => [['label' => 'test@remote.com', 'value' => ['shareType' => Share::SHARE_TYPE_EMAIL, 'shareWith' => 'test@remote.com']]]]], + ['emails' => [['uuid' => 'uid1', 'name' => 'User @ Localhost', 'type' => '', 'label' => 'User @ Localhost (username@localhost)', 'value' => ['shareType' => Share::SHARE_TYPE_EMAIL, 'shareWith' => 'username@localhost']]], 'exact' => ['emails' => [['label' => 'test@remote.com', 'uuid' => 'test@remote.com', 'value' => ['shareType' => Share::SHARE_TYPE_EMAIL, 'shareWith' => 'test@remote.com']]]]], false, false, ], + // data set 8 [ 'test@remote.com', [ [ + 'UID' => 'uid3', 'FN' => 'User3 @ Localhost', ], [ + 'UID' => 'uid2', 'FN' => 'User2 @ Localhost', 'EMAIL' => [ ], ], [ + 'UID' => 'uid1', 'FN' => 'User @ Localhost', 'EMAIL' => [ 'username@localhost', @@ -234,22 +255,26 @@ class MailPluginTest extends TestCase { ], ], false, - ['emails' => [], 'exact' => ['emails' => [['label' => 'test@remote.com', 'value' => ['shareType' => Share::SHARE_TYPE_EMAIL, 'shareWith' => 'test@remote.com']]]]], + ['emails' => [], 'exact' => ['emails' => [['label' => 'test@remote.com', 'uuid' => 'test@remote.com', 'value' => ['shareType' => Share::SHARE_TYPE_EMAIL, 'shareWith' => 'test@remote.com']]]]], false, false, ], + // data set 9 [ 'username@localhost', [ [ + 'UID' => 'uid3', 'FN' => 'User3 @ Localhost', ], [ + 'UID' => 'uid2', 'FN' => 'User2 @ Localhost', 'EMAIL' => [ ], ], [ + 'UID' => 'uid1', 'FN' => 'User @ Localhost', 'EMAIL' => [ 'username@localhost', @@ -257,22 +282,26 @@ class MailPluginTest extends TestCase { ], ], true, - ['emails' => [], 'exact' => ['emails' => [['label' => 'User @ Localhost (username@localhost)', 'value' => ['shareType' => Share::SHARE_TYPE_EMAIL, 'shareWith' => 'username@localhost']]]]], + ['emails' => [], 'exact' => ['emails' => [['name' => 'User @ Localhost', 'uuid' => 'uid1', 'type' => '', 'label' => 'User @ Localhost (username@localhost)', 'value' => ['shareType' => Share::SHARE_TYPE_EMAIL, 'shareWith' => 'username@localhost']]]]], true, false, ], + // data set 10 [ 'username@localhost', [ [ + 'UID' => 'uid1', 'FN' => 'User3 @ Localhost', ], [ + 'UID' => 'uid2', 'FN' => 'User2 @ Localhost', 'EMAIL' => [ ], ], [ + 'UID' => 'uid1', 'FN' => 'User @ Localhost', 'EMAIL' => [ 'username@localhost', @@ -280,23 +309,27 @@ class MailPluginTest extends TestCase { ], ], false, - ['emails' => [], 'exact' => ['emails' => [['label' => 'User @ Localhost (username@localhost)', 'value' => ['shareType' => Share::SHARE_TYPE_EMAIL, 'shareWith' => 'username@localhost']]]]], + ['emails' => [], 'exact' => ['emails' => [['name' => 'User @ Localhost', 'uuid' => 'uid1', 'type' => '', 'label' => 'User @ Localhost (username@localhost)', 'value' => ['shareType' => Share::SHARE_TYPE_EMAIL, 'shareWith' => 'username@localhost']]]]], true, false, ], + // data set 11 // contact with space [ 'user name@localhost', [ [ + 'UID' => 'uid3', 'FN' => 'User3 @ Localhost', ], [ + 'UID' => 'uid2', 'FN' => 'User2 @ Localhost', 'EMAIL' => [ ], ], [ + 'UID' => 'uid1', 'FN' => 'User Name @ Localhost', 'EMAIL' => [ 'user name@localhost', @@ -304,23 +337,27 @@ class MailPluginTest extends TestCase { ], ], false, - ['emails' => [], 'exact' => ['emails' => [['label' => 'User Name @ Localhost (user name@localhost)', 'value' => ['shareType' => Share::SHARE_TYPE_EMAIL, 'shareWith' => 'user name@localhost']]]]], + ['emails' => [], 'exact' => ['emails' => [['name' => 'User Name @ Localhost', 'uuid' => 'uid1', 'type' => '', 'label' => 'User Name @ Localhost (user name@localhost)', 'value' => ['shareType' => Share::SHARE_TYPE_EMAIL, 'shareWith' => 'user name@localhost']]]]], true, false, ], + // data set 12 // remote with space, no contact [ 'user space@remote.com', [ [ + 'UID' => 'uid3', 'FN' => 'User3 @ Localhost', ], [ + 'UID' => 'uid2', 'FN' => 'User2 @ Localhost', 'EMAIL' => [ ], ], [ + 'UID' => 'uid1', 'FN' => 'User @ Localhost', 'EMAIL' => [ 'username@localhost', @@ -332,11 +369,13 @@ class MailPluginTest extends TestCase { false, false, ], + // data set 13 // Local user found by email [ 'test@example.com', [ [ + 'UID' => 'uid1', 'FN' => 'User', 'EMAIL' => ['test@example.com'], 'CLOUD' => ['test@localhost'], @@ -344,15 +383,17 @@ class MailPluginTest extends TestCase { ] ], false, - ['users' => [], 'exact' => ['users' => [['label' => 'User (test@example.com)','value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test'],]]]], + ['users' => [], 'exact' => ['users' => [['uuid' => 'uid1', 'name' => 'User', 'label' => 'User (test@example.com)','value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test'],]]]], true, false, ], + // data set 14 // Current local user found by email => no result [ 'test@example.com', [ [ + 'UID' => 'uid1', 'FN' => 'User', 'EMAIL' => ['test@example.com'], 'CLOUD' => ['current@localhost'], @@ -364,29 +405,34 @@ class MailPluginTest extends TestCase { false, false, ], + // data set 15 // Pagination and "more results" for user matches byyyyyyy emails [ 'test@example', [ [ + 'UID' => 'uid1', 'FN' => 'User1', 'EMAIL' => ['test@example.com'], 'CLOUD' => ['test1@localhost'], 'isLocalSystemBook' => true, ], [ + 'UID' => 'uid2', 'FN' => 'User2', 'EMAIL' => ['test@example.de'], 'CLOUD' => ['test2@localhost'], 'isLocalSystemBook' => true, ], [ + 'UID' => 'uid3', 'FN' => 'User3', 'EMAIL' => ['test@example.org'], 'CLOUD' => ['test3@localhost'], 'isLocalSystemBook' => true, ], [ + 'UID' => 'uid4', 'FN' => 'User4', 'EMAIL' => ['test@example.net'], 'CLOUD' => ['test4@localhost'], @@ -395,32 +441,37 @@ class MailPluginTest extends TestCase { ], true, ['users' => [ - ['label' => 'User1 (test@example.com)', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test1']], - ['label' => 'User2 (test@example.de)', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test2']], + ['uuid' => 'uid1', 'name' => 'User1', 'label' => 'User1 (test@example.com)', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test1']], + ['uuid' => 'uid2', 'name' => 'User2', 'label' => 'User2 (test@example.de)', 'value' => ['shareType' => Share::SHARE_TYPE_USER, 'shareWith' => 'test2']], ], 'emails' => [], 'exact' => ['users' => [], 'emails' => []]], false, true, ], + // data set 16 // Pagination and "more results" for normal emails [ 'test@example', [ [ + 'UID' => 'uid1', 'FN' => 'User1', 'EMAIL' => ['test@example.com'], 'CLOUD' => ['test1@localhost'], ], [ + 'UID' => 'uid2', 'FN' => 'User2', 'EMAIL' => ['test@example.de'], 'CLOUD' => ['test2@localhost'], ], [ + 'UID' => 'uid3', 'FN' => 'User3', 'EMAIL' => ['test@example.org'], 'CLOUD' => ['test3@localhost'], ], [ + 'UID' => 'uid4', 'FN' => 'User4', 'EMAIL' => ['test@example.net'], 'CLOUD' => ['test4@localhost'], @@ -428,12 +479,45 @@ class MailPluginTest extends TestCase { ], true, ['emails' => [ - ['label' => 'User1 (test@example.com)', 'value' => ['shareType' => Share::SHARE_TYPE_EMAIL, 'shareWith' => 'test@example.com']], - ['label' => 'User2 (test@example.de)', 'value' => ['shareType' => Share::SHARE_TYPE_EMAIL, 'shareWith' => 'test@example.de']], + ['uuid' => 'uid1', 'name' => 'User1', 'type' => '', 'label' => 'User1 (test@example.com)', 'value' => ['shareType' => Share::SHARE_TYPE_EMAIL, 'shareWith' => 'test@example.com']], + ['uuid' => 'uid2', 'name' => 'User2', 'type' => '', 'label' => 'User2 (test@example.de)', 'value' => ['shareType' => Share::SHARE_TYPE_EMAIL, 'shareWith' => 'test@example.de']], ], 'exact' => ['emails' => []]], false, true, ], + // data set 17 + // multiple email addresses with type + [ + 'User Name', + [ + [ + 'UID' => 'uid3', + 'FN' => 'User3', + ], + [ + 'UID' => 'uid2', + 'FN' => 'User2', + 'EMAIL' => [ + ], + ], + [ + 'UID' => 'uid1', + 'FN' => 'User Name', + 'EMAIL' => [ + ['type' => 'HOME', 'value' => 'username@localhost'], + ['type' => 'WORK', 'value' => 'username@other'], + ], + ], + ], + false, + ['emails' => [ + ], 'exact' => ['emails' => [ + ['name' => 'User Name', 'uuid' => 'uid1', 'type' => 'HOME', 'label' => 'User Name (username@localhost)', 'value' => ['shareType' => Share::SHARE_TYPE_EMAIL, 'shareWith' => 'username@localhost']], + ['name' => 'User Name', 'uuid' => 'uid1', 'type' => 'WORK', 'label' => 'User Name (username@other)', 'value' => ['shareType' => Share::SHARE_TYPE_EMAIL, 'shareWith' => 'username@other']] + ]]], + false, + false, + ], ]; } @@ -513,7 +597,7 @@ class MailPluginTest extends TestCase { 'UID' => 'User' ] ], - ['users' => [['label' => 'User (test@example.com)','value' => ['shareType' => 0, 'shareWith' => 'test'],]], 'emails' => [], 'exact' => ['emails' => [], 'users' => []]], + ['users' => [['label' => 'User (test@example.com)', 'uuid' => 'User', 'name' => 'User', 'value' => ['shareType' => 0, 'shareWith' => 'test'],]], 'emails' => [], 'exact' => ['emails' => [], 'users' => []]], false, false, [ @@ -553,7 +637,7 @@ class MailPluginTest extends TestCase { 'UID' => 'User' ] ], - ['emails' => [], 'exact' => ['emails' => [['label' => 'test@example.com', 'value' => ['shareType' => 4,'shareWith' => 'test@example.com']]]]], + ['emails' => [], 'exact' => ['emails' => [['label' => 'test@example.com', 'uuid' => 'test@example.com', 'value' => ['shareType' => 4,'shareWith' => 'test@example.com']]]]], false, false, [ diff --git a/tests/lib/Collaboration/Collaborators/RemotePluginTest.php b/tests/lib/Collaboration/Collaborators/RemotePluginTest.php index aa009a7134b..aff68185767 100644 --- a/tests/lib/Collaboration/Collaborators/RemotePluginTest.php +++ b/tests/lib/Collaboration/Collaborators/RemotePluginTest.php @@ -31,10 +31,17 @@ use OCP\Collaboration\Collaborators\SearchResultType; use OCP\Contacts\IManager; use OCP\Federation\ICloudIdManager; use OCP\IConfig; +use OCP\IUser; +use OCP\IUserManager; +use OCP\IUserSession; use OCP\Share; use Test\TestCase; class RemotePluginTest extends TestCase { + + /** @var IUserManager|\PHPUnit_Framework_MockObject_MockObject */ + protected $userManager; + /** @var IConfig|\PHPUnit_Framework_MockObject_MockObject */ protected $config; @@ -53,6 +60,7 @@ class RemotePluginTest extends TestCase { public function setUp() { parent::setUp(); + $this->userManager = $this->createMock(IUserManager::class); $this->config = $this->createMock(IConfig::class); $this->contactsManager = $this->createMock(IManager::class); $this->cloudIdManager = new CloudIdManager(); @@ -60,7 +68,15 @@ class RemotePluginTest extends TestCase { } public function instantiatePlugin() { - $this->plugin = new RemotePlugin($this->contactsManager, $this->cloudIdManager, $this->config); + $user = $this->createMock(IUser::class); + $user->expects($this->any()) + ->method('getUID') + ->willReturn('admin'); + $userSession = $this->createMock(IUserSession::class); + $userSession->expects($this->any()) + ->method('getUser') + ->willReturn($user); + $this->plugin = new RemotePlugin($this->contactsManager, $this->cloudIdManager, $this->config, $this->userManager, $userSession); } /** @@ -152,14 +168,17 @@ class RemotePluginTest extends TestCase { 'test', [ [ + 'UID' => 'uid', 'FN' => 'User3 @ Localhost', ], [ + 'UID' => 'uid', 'FN' => 'User2 @ Localhost', 'CLOUD' => [ ], ], [ + 'UID' => 'uid1', 'FN' => 'User @ Localhost', 'CLOUD' => [ 'username@localhost', @@ -167,7 +186,7 @@ class RemotePluginTest extends TestCase { ], ], true, - ['remotes' => [['label' => 'User @ Localhost (username@localhost)', 'value' => ['shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => 'username@localhost', 'server' => 'localhost']]], 'exact' => ['remotes' => []]], + ['remotes' => [['name' => 'User @ Localhost', 'label' => 'User @ Localhost (username@localhost)', 'uuid' => 'uid1', 'type' => '', 'value' => ['shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => 'username@localhost', 'server' => 'localhost']]], 'exact' => ['remotes' => []]], false, true, ], @@ -175,14 +194,17 @@ class RemotePluginTest extends TestCase { 'test', [ [ + 'UID' => 'uid', 'FN' => 'User3 @ Localhost', ], [ + 'UID' => 'uid', 'FN' => 'User2 @ Localhost', 'CLOUD' => [ ], ], [ + 'UID' => 'uid', 'FN' => 'User @ Localhost', 'CLOUD' => [ 'username@localhost', @@ -198,14 +220,17 @@ class RemotePluginTest extends TestCase { 'test@remote', [ [ + 'UID' => 'uid', 'FN' => 'User3 @ Localhost', ], [ + 'UID' => 'uid', 'FN' => 'User2 @ Localhost', 'CLOUD' => [ ], ], [ + 'UID' => 'uid', 'FN' => 'User @ Localhost', 'CLOUD' => [ 'username@localhost', @@ -213,7 +238,7 @@ class RemotePluginTest extends TestCase { ], ], true, - ['remotes' => [['label' => 'User @ Localhost (username@localhost)', 'value' => ['shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => 'username@localhost', 'server' => 'localhost']]], 'exact' => ['remotes' => [['label' => 'test@remote', 'value' => ['shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => 'test@remote']]]]], + ['remotes' => [['name' => 'User @ Localhost', 'label' => 'User @ Localhost (username@localhost)', 'uuid' => 'uid', 'type' => '', 'value' => ['shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => 'username@localhost', 'server' => 'localhost']]], 'exact' => ['remotes' => [['label' => 'test@remote', 'value' => ['shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => 'test@remote']]]]], false, true, ], @@ -221,14 +246,17 @@ class RemotePluginTest extends TestCase { 'test@remote', [ [ + 'UID' => 'uid', 'FN' => 'User3 @ Localhost', ], [ + 'UID' => 'uid', 'FN' => 'User2 @ Localhost', 'CLOUD' => [ ], ], [ + 'UID' => 'uid', 'FN' => 'User @ Localhost', 'CLOUD' => [ 'username@localhost', @@ -244,14 +272,17 @@ class RemotePluginTest extends TestCase { 'username@localhost', [ [ + 'UID' => 'uid3', 'FN' => 'User3 @ Localhost', ], [ + 'UID' => '2', 'FN' => 'User2 @ Localhost', 'CLOUD' => [ ], ], [ + 'UID' => 'uid1', 'FN' => 'User @ Localhost', 'CLOUD' => [ 'username@localhost', @@ -259,7 +290,7 @@ class RemotePluginTest extends TestCase { ], ], true, - ['remotes' => [], 'exact' => ['remotes' => [['label' => 'User @ Localhost (username@localhost)', 'value' => ['shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => 'username@localhost', 'server' => 'localhost']]]]], + ['remotes' => [], 'exact' => ['remotes' => [['name' => 'User @ Localhost', 'label' => 'User @ Localhost (username@localhost)', 'uuid' => 'uid1', 'type' => '', 'value' => ['shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => 'username@localhost', 'server' => 'localhost']]]]], true, true, ], @@ -267,14 +298,17 @@ class RemotePluginTest extends TestCase { 'username@localhost', [ [ + 'UID' => 'uid3', 'FN' => 'User3 @ Localhost', ], [ + 'UID' => 'uid2', 'FN' => 'User2 @ Localhost', 'CLOUD' => [ ], ], [ + 'UID' => 'uid1', 'FN' => 'User @ Localhost', 'CLOUD' => [ 'username@localhost', @@ -282,7 +316,7 @@ class RemotePluginTest extends TestCase { ], ], false, - ['remotes' => [], 'exact' => ['remotes' => [['label' => 'User @ Localhost (username@localhost)', 'value' => ['shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => 'username@localhost', 'server' => 'localhost']]]]], + ['remotes' => [], 'exact' => ['remotes' => [['name' => 'User @ Localhost', 'label' => 'User @ Localhost (username@localhost)', 'uuid' => 'uid1', 'type' => '', 'value' => ['shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => 'username@localhost', 'server' => 'localhost']]]]], true, true, ], @@ -291,14 +325,17 @@ class RemotePluginTest extends TestCase { 'user name@localhost', [ [ + 'UID' => 'uid1', 'FN' => 'User3 @ Localhost', ], [ + 'UID' => 'uid2', 'FN' => 'User2 @ Localhost', 'CLOUD' => [ ], ], [ + 'UID' => 'uid3', 'FN' => 'User Name @ Localhost', 'CLOUD' => [ 'user name@localhost', @@ -306,7 +343,7 @@ class RemotePluginTest extends TestCase { ], ], false, - ['remotes' => [], 'exact' => ['remotes' => [['label' => 'User Name @ Localhost (user name@localhost)', 'value' => ['shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => 'user name@localhost', 'server' => 'localhost']]]]], + ['remotes' => [], 'exact' => ['remotes' => [['name' => 'User Name @ Localhost', 'label' => 'User Name @ Localhost (user name@localhost)', 'uuid' => 'uid3', 'type' => '', 'value' => ['shareType' => Share::SHARE_TYPE_REMOTE, 'shareWith' => 'user name@localhost', 'server' => 'localhost']]]]], true, true, ], @@ -315,14 +352,17 @@ class RemotePluginTest extends TestCase { 'user space@remote', [ [ + 'UID' => 'uid3', 'FN' => 'User3 @ Localhost', ], [ + 'UID' => 'uid2', 'FN' => 'User2 @ Localhost', 'CLOUD' => [ ], ], [ + 'UID' => 'uid1', 'FN' => 'User @ Localhost', 'CLOUD' => [ 'username@localhost', diff --git a/version.php b/version.php index c3740f01fa3..93a8b5fd53e 100644 --- a/version.php +++ b/version.php @@ -29,7 +29,7 @@ // between betas, final and RCs. This is _not_ the public version number. Reset minor/patchlevel // when updating major/minor version number. -$OC_Version = array(15, 0, 0, 1); +$OC_Version = array(15, 0, 0, 2); // The human readable string $OC_VersionString = '15.0.0 alpha'; |