diff options
author | Roeland Jago Douma <rullzer@users.noreply.github.com> | 2018-10-30 19:25:44 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-10-30 19:25:44 +0100 |
commit | 514185820e1e1ad1a82648eb3c927552357e0f5f (patch) | |
tree | 07f5cc32bdd9a55fbd9c4868dc047193129b2e2a | |
parent | 3eb5ac9cff2cd2a111e51fe3ce24ecdd615ddafa (diff) | |
parent | d4f39a9033e809b498dd577f9b7ce8e8ffcbb69d (diff) | |
download | nextcloud-server-514185820e1e1ad1a82648eb3c927552357e0f5f.tar.gz nextcloud-server-514185820e1e1ad1a82648eb3c927552357e0f5f.zip |
Merge pull request #11898 from nextcloud/feature/read_only_public_share
Feature/read only public share
21 files changed, 556 insertions, 38 deletions
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/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/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/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 de149d3a136..e41d6d117fd 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -652,6 +652,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 7bd172c569d..b9976ddd7b2 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -682,6 +682,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/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/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/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'; |