From d805959e819e64ccf47dfa55fca96b222dedfa9a Mon Sep 17 00:00:00 2001 From: Roeland Jago Douma Date: Tue, 19 Jun 2018 09:20:35 +0200 Subject: [PATCH 1/9] Add API to undelete delete group shares When a group share is deleted we keep track of this in the DB. Right now it is only possible for a recipient to get back the share by asking the sharer to delete it and to share it again. This doesn't scale. This endpoint makes it possible to get back the share. Signed-off-by: Roeland Jago Douma --- .../lib/FederatedShareProvider.php | 5 + apps/files_sharing/appinfo/routes.php | 13 ++ .../composer/composer/autoload_classmap.php | 1 + .../composer/composer/autoload_static.php | 1 + .../Controller/DeletedShareAPIController.php | 119 ++++++++++++++++++ apps/sharebymail/lib/ShareByMailProvider.php | 5 + lib/private/Share20/DefaultShareProvider.php | 37 ++++++ lib/private/Share20/Manager.php | 8 ++ lib/public/Share/IManager.php | 15 +++ lib/public/Share/IShareProvider.php | 14 +++ 10 files changed, 218 insertions(+) create mode 100644 apps/files_sharing/lib/Controller/DeletedShareAPIController.php diff --git a/apps/federatedfilesharing/lib/FederatedShareProvider.php b/apps/federatedfilesharing/lib/FederatedShareProvider.php index 5a12f8e9be..f81b826f12 100644 --- a/apps/federatedfilesharing/lib/FederatedShareProvider.php +++ b/apps/federatedfilesharing/lib/FederatedShareProvider.php @@ -38,6 +38,7 @@ use OCP\IConfig; use OCP\IL10N; use OCP\ILogger; use OCP\IUserManager; +use OCP\Share\Exceptions\GenericShareException; use OCP\Share\IShare; use OCP\Share\IShareProvider; use OC\Share20\Exception\InvalidShare; @@ -585,6 +586,10 @@ class FederatedShareProvider implements IShareProvider { // TODO move this code over to this app } + public function restore(IShare $share, string $recipient): IShare { + throw new GenericShareException('not implemented'); + } + public function getSharesInFolder($userId, Folder $node, $reshares) { $qb = $this->dbConnection->getQueryBuilder(); diff --git a/apps/files_sharing/appinfo/routes.php b/apps/files_sharing/appinfo/routes.php index 8e5110c6a1..eea6715e57 100644 --- a/apps/files_sharing/appinfo/routes.php +++ b/apps/files_sharing/appinfo/routes.php @@ -73,6 +73,19 @@ return [ 'url' => '/api/v1/shares/{id}', 'verb' => 'DELETE', ], + /* + * Deleted Shares + */ + [ + 'name' => 'DeletedShareAPI#index', + 'url' => '/api/v1/deletedshares', + 'verb' => 'GET', + ], + [ + 'name' => 'DeletedShareAPI#undelete', + 'url' => '/api/v1/deletedshares/{id}', + 'verb' => 'POST', + ], /* * OCS Sharee API */ diff --git a/apps/files_sharing/composer/composer/autoload_classmap.php b/apps/files_sharing/composer/composer/autoload_classmap.php index e5a86bbd09..cb64d44a54 100644 --- a/apps/files_sharing/composer/composer/autoload_classmap.php +++ b/apps/files_sharing/composer/composer/autoload_classmap.php @@ -22,6 +22,7 @@ return array( 'OCA\\Files_Sharing\\Capabilities' => $baseDir . '/../lib/Capabilities.php', 'OCA\\Files_Sharing\\Collaboration\\ShareRecipientSorter' => $baseDir . '/../lib/Collaboration/ShareRecipientSorter.php', 'OCA\\Files_Sharing\\Command\\CleanupRemoteStorages' => $baseDir . '/../lib/Command/CleanupRemoteStorages.php', + 'OCA\\Files_Sharing\\Controller\\DeletedShareAPIController' => $baseDir . '/../lib/Controller/DeletedShareAPIController.php', 'OCA\\Files_Sharing\\Controller\\ExternalSharesController' => $baseDir . '/../lib/Controller/ExternalSharesController.php', 'OCA\\Files_Sharing\\Controller\\PublicPreviewController' => $baseDir . '/../lib/Controller/PublicPreviewController.php', 'OCA\\Files_Sharing\\Controller\\RemoteController' => $baseDir . '/../lib/Controller/RemoteController.php', diff --git a/apps/files_sharing/composer/composer/autoload_static.php b/apps/files_sharing/composer/composer/autoload_static.php index bf41ef45ea..dcfd5c4fe4 100644 --- a/apps/files_sharing/composer/composer/autoload_static.php +++ b/apps/files_sharing/composer/composer/autoload_static.php @@ -37,6 +37,7 @@ class ComposerStaticInitFiles_Sharing 'OCA\\Files_Sharing\\Capabilities' => __DIR__ . '/..' . '/../lib/Capabilities.php', 'OCA\\Files_Sharing\\Collaboration\\ShareRecipientSorter' => __DIR__ . '/..' . '/../lib/Collaboration/ShareRecipientSorter.php', 'OCA\\Files_Sharing\\Command\\CleanupRemoteStorages' => __DIR__ . '/..' . '/../lib/Command/CleanupRemoteStorages.php', + 'OCA\\Files_Sharing\\Controller\\DeletedShareAPIController' => __DIR__ . '/..' . '/../lib/Controller/DeletedShareAPIController.php', 'OCA\\Files_Sharing\\Controller\\ExternalSharesController' => __DIR__ . '/..' . '/../lib/Controller/ExternalSharesController.php', 'OCA\\Files_Sharing\\Controller\\PublicPreviewController' => __DIR__ . '/..' . '/../lib/Controller/PublicPreviewController.php', 'OCA\\Files_Sharing\\Controller\\RemoteController' => __DIR__ . '/..' . '/../lib/Controller/RemoteController.php', diff --git a/apps/files_sharing/lib/Controller/DeletedShareAPIController.php b/apps/files_sharing/lib/Controller/DeletedShareAPIController.php new file mode 100644 index 0000000000..9dadf8e25b --- /dev/null +++ b/apps/files_sharing/lib/Controller/DeletedShareAPIController.php @@ -0,0 +1,119 @@ + + * @Copyright 2018, John Molakvoæ (skjnldsv) + * + * @author John Molakvoæ (skjnldsv) + * @author Roeland Jago Douma + * + * @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 . + * + */ + +namespace OCA\Files_Sharing\Controller; + +use OCP\AppFramework\Http\DataResponse; +use OCP\AppFramework\OCS\OCSException; +use OCP\AppFramework\OCS\OCSNotFoundException; +use OCP\AppFramework\OCSController; +use OCP\IRequest; +use OCP\IUserManager; +use OCP\Share\Exceptions\GenericShareException; +use OCP\Share\Exceptions\ShareNotFound; +use OCP\Share\IManager as ShareManager; +use OCP\Share\IShare; + +class DeletedShareAPIController extends OCSController { + + /** @var ShareManager */ + private $shareManager; + + /** @var string */ + private $userId; + + /** @var IUserManager */ + private $userManager; + + public function __construct(string $appName, + IRequest $request, + ShareManager $shareManager, + string $UserId, + IUserManager $userManager) { + parent::__construct($appName, $request); + + $this->shareManager = $shareManager; + $this->userId = $UserId; + $this->userManager = $userManager; + } + + private function formatShare(IShare $share): array { + return [ + 'id' => $share->getFullId(), + 'uid_owner' => $share->getShareOwner(), + 'displayname_owner' => $this->userManager->get($share->getShareOwner())->getDisplayName(), + 'path' => $share->getTarget(), + ]; + } + + /** + * @NoAdminRequired + */ + public function index(): DataResponse { + $shares = $this->shareManager->getSharedWith($this->userId, \OCP\Share::SHARE_TYPE_GROUP, null, -1, 0); + + // Only get deleted shares + $shares = array_filter($shares, function(IShare $share) { + return $share->getPermissions() === 0; + }); + + // Only get shares where the owner still exists + $shares = array_filter($shares, function (IShare $share) { + return $this->userManager->userExists($share->getShareOwner()); + }); + + $shares = array_map(function (IShare $share) { + return $this->formatShare($share); + }, $shares); + + return new DataResponse($shares); + } + + /** + * @NoAdminRequired + * + * @throws OCSException + */ + public function undelete(string $id): DataResponse { + try { + $share = $this->shareManager->getShareById($id, $this->userId); + } catch (ShareNotFound $e) { + throw new OCSNotFoundException('Share not found'); + } + + if ($share->getPermissions() !== 0) { + throw new OCSNotFoundException('No deleted share found'); + } + + try { + $this->shareManager->restoreShare($share, $this->userId); + } catch (GenericShareException $e) { + throw new OCSException('Something went wrong'); + } + + return new DataResponse([]); + } +} diff --git a/apps/sharebymail/lib/ShareByMailProvider.php b/apps/sharebymail/lib/ShareByMailProvider.php index 61b479d3e9..1a1855b9c4 100644 --- a/apps/sharebymail/lib/ShareByMailProvider.php +++ b/apps/sharebymail/lib/ShareByMailProvider.php @@ -42,6 +42,7 @@ use OCP\Mail\IMailer; use OCP\Security\IHasher; use OCP\Security\ISecureRandom; use OC\Share20\Share; +use OCP\Share\Exceptions\GenericShareException; use OCP\Share\Exceptions\ShareNotFound; use OCP\Share\IShare; use OCP\Share\IShareProvider; @@ -692,6 +693,10 @@ class ShareByMailProvider implements IShareProvider { // nothing to do here, mail shares are only outgoing shares } + public function restore(IShare $share, string $recipient): IShare { + throw new GenericShareException('not implemented'); + } + /** * @inheritdoc */ diff --git a/lib/private/Share20/DefaultShareProvider.php b/lib/private/Share20/DefaultShareProvider.php index 3c56b24707..5e52156d1d 100644 --- a/lib/private/Share20/DefaultShareProvider.php +++ b/lib/private/Share20/DefaultShareProvider.php @@ -31,6 +31,7 @@ namespace OC\Share20; use OC\Files\Cache\Cache; use OCP\Files\Folder; +use OCP\Share\IShare; use OCP\Share\IShareProvider; use OC\Share20\Exception\InvalidShare; use OC\Share20\Exception\ProviderException; @@ -410,6 +411,41 @@ class DefaultShareProvider implements IShareProvider { } } + /** + * @inheritdoc + * + * For now this only works for group shares + * If this gets implemented for normal shares we have to extend it + */ + public function restore(IShare $share, string $recipient): IShare { + $qb = $this->dbConn->getQueryBuilder(); + $qb->select('permissions') + ->from('share') + ->where( + $qb->expr()->eq('id', $qb->createNamedParameter($share->getId())) + ); + $cursor = $qb->execute(); + $data = $cursor->fetch(); + $cursor->closeCursor(); + + $originalPermission = $data['permissions']; + + $qb = $this->dbConn->getQueryBuilder(); + $qb->update('share') + ->set('permissions', $qb->createNamedParameter($originalPermission)) + ->where( + $qb->expr()->eq('parent', $qb->createNamedParameter($share->getParent())) + )->andWhere( + $qb->expr()->eq('share_type', $qb->createNamedParameter(self::SHARE_TYPE_USERGROUP)) + )->andWhere( + $qb->expr()->eq('share_with', $qb->createNamedParameter($recipient)) + ); + + $qb->execute(); + + return $this->getShareById($share->getId(), $recipient); + } + /** * @inheritdoc */ @@ -922,6 +958,7 @@ class DefaultShareProvider implements IShareProvider { while($data = $stmt->fetch()) { $shareMap[$data['parent']]->setPermissions((int)$data['permissions']); $shareMap[$data['parent']]->setTarget($data['file_target']); + $shareMap[$data['parent']]->setParent($data['parent']); } $stmt->closeCursor(); diff --git a/lib/private/Share20/Manager.php b/lib/private/Share20/Manager.php index cddd8c8d92..c0827f1373 100644 --- a/lib/private/Share20/Manager.php +++ b/lib/private/Share20/Manager.php @@ -61,6 +61,7 @@ use OCP\Share\Exceptions\GenericShareException; use OCP\Share\Exceptions\ShareNotFound; use OCP\Share\IManager; use OCP\Share\IProviderFactory; +use OCP\Share\IShare; use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\EventDispatcher\GenericEvent; use OCP\Share\IShareProvider; @@ -978,6 +979,13 @@ class Manager implements IManager { $this->eventDispatcher->dispatch('OCP\Share::postUnshareFromSelf', $event); } + public function restoreShare(IShare $share, string $recipientId): IShare { + list($providerId, ) = $this->splitFullId($share->getFullId()); + $provider = $this->factory->getProvider($providerId); + + return $provider->restore($share, $recipientId); + } + /** * @inheritdoc */ diff --git a/lib/public/Share/IManager.php b/lib/public/Share/IManager.php index 493db5e514..56e35f517c 100644 --- a/lib/public/Share/IManager.php +++ b/lib/public/Share/IManager.php @@ -29,6 +29,7 @@ namespace OCP\Share; use OCP\Files\Folder; use OCP\Files\Node; +use OCP\Share\Exceptions\GenericShareException; use OCP\Share\Exceptions\ShareNotFound; /** @@ -83,6 +84,20 @@ interface IManager { */ public function deleteFromSelf(IShare $share, $recipientId); + /** + * Restore the share when it has been deleted + * Certain share types can be restored when they have been deleted + * but the provider should properly handle this\ + * + * @param IShare $share The share to restore + * @param string $recipientId The user to restore the share for + * @return IShare The restored share object + * @throws GenericShareException In case restoring the share failed + * + * @since 14.0.0 + */ + public function restoreShare(IShare $share, string $recipientId): IShare; + /** * Move the share as a recipient of the share. * This is updating the share target. So where the recipient has the share mounted. diff --git a/lib/public/Share/IShareProvider.php b/lib/public/Share/IShareProvider.php index 4a1ac9b8b8..6731bf8882 100644 --- a/lib/public/Share/IShareProvider.php +++ b/lib/public/Share/IShareProvider.php @@ -25,6 +25,8 @@ namespace OCP\Share; use OCP\Files\Folder; +use OCP\Share\Exceptions\GenericShareException; +use OCP\Share\Exceptions\ShareNotFound; use OCP\Files\Node; /** @@ -80,6 +82,18 @@ interface IShareProvider { */ public function deleteFromSelf(\OCP\Share\IShare $share, $recipient); + /** + * Restore a share for a given recipient. The implementation could be provider independant. + * + * @param IShare $share + * @param string $recipient + * @return IShare The restored share object + * + * @since 14.0.0 + * @throws GenericShareException In case the share could not be restored + */ + public function restore(IShare $share, string $recipient): IShare; + /** * Move a share as a recipient. * This is updating the share target. Thus the mount point of the recipient. From 6a0552224de21e137c3cfa63c589c2b3f65a0987 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20Molakvo=C3=A6=20=28skjnldsv=29?= Date: Tue, 19 Jun 2018 17:44:20 +0200 Subject: [PATCH 2/9] getDeletedSharedWith method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: John Molakvoæ (skjnldsv) --- .../Controller/DeletedShareAPIController.php | 12 +----------- lib/private/Share20/Manager.php | 19 +++++++++++++++++++ lib/public/Share/IManager.php | 14 ++++++++++++++ 3 files changed, 34 insertions(+), 11 deletions(-) diff --git a/apps/files_sharing/lib/Controller/DeletedShareAPIController.php b/apps/files_sharing/lib/Controller/DeletedShareAPIController.php index 9dadf8e25b..2e4f4d52d7 100644 --- a/apps/files_sharing/lib/Controller/DeletedShareAPIController.php +++ b/apps/files_sharing/lib/Controller/DeletedShareAPIController.php @@ -73,17 +73,7 @@ class DeletedShareAPIController extends OCSController { * @NoAdminRequired */ public function index(): DataResponse { - $shares = $this->shareManager->getSharedWith($this->userId, \OCP\Share::SHARE_TYPE_GROUP, null, -1, 0); - - // Only get deleted shares - $shares = array_filter($shares, function(IShare $share) { - return $share->getPermissions() === 0; - }); - - // Only get shares where the owner still exists - $shares = array_filter($shares, function (IShare $share) { - return $this->userManager->userExists($share->getShareOwner()); - }); + $shares = $this->shareManager->getDeletedSharedWith($this->userId, \OCP\Share::SHARE_TYPE_GROUP, null, -1, 0); $shares = array_map(function (IShare $share) { return $this->formatShare($share); diff --git a/lib/private/Share20/Manager.php b/lib/private/Share20/Manager.php index c0827f1373..5116351a6b 100644 --- a/lib/private/Share20/Manager.php +++ b/lib/private/Share20/Manager.php @@ -1129,6 +1129,25 @@ class Manager implements IManager { return $shares; } + /** + * @inheritdoc + */ + public function getDeletedSharedWith($userId, $shareType, $node = null, $limit = 50, $offset = 0) { + $shares = $this->getSharedWith($userId, $shareType, $node, $limit, $offset); + + // Only get deleted shares + $shares = array_filter($shares, function(IShare $share) { + return $share->getPermissions() === 0; + }); + + // Only get shares where the owner still exists + $shares = array_filter($shares, function (IShare $share) { + return $this->userManager->userExists($share->getShareOwner()); + }); + + return $shares; + } + /** * @inheritdoc */ diff --git a/lib/public/Share/IManager.php b/lib/public/Share/IManager.php index 56e35f517c..c8abab378e 100644 --- a/lib/public/Share/IManager.php +++ b/lib/public/Share/IManager.php @@ -149,6 +149,20 @@ interface IManager { */ public function getSharedWith($userId, $shareType, $node = null, $limit = 50, $offset = 0); + /** + * Get deleted shares shared with $user. + * Filter by $node if provided + * + * @param string $userId + * @param int $shareType + * @param Node|null $node + * @param int $limit The maximum number of shares returned, -1 for all + * @param int $offset + * @return IShare[] + * @since 9.0.0 + */ + public function getDeletedSharedWith($userId, $shareType, $node = null, $limit = 50, $offset = 0); + /** * Retrieve a share by the share id. * If the recipient is set make sure to retrieve the file for that user. From 3b835d8076c844adde1014e994256740c790857b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20Molakvo=C3=A6=20=28skjnldsv=29?= Date: Wed, 20 Jun 2018 19:14:50 +0200 Subject: [PATCH 3/9] Js magic for deleted shares MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: John Molakvoæ (skjnldsv) --- apps/files/css/files.scss | 3 + apps/files/img/unshare.svg | 1 + apps/files_sharing/appinfo/app.php | 16 +++++ apps/files_sharing/js/app.js | 59 +++++++++++++++++ apps/files_sharing/js/sharedfilelist.js | 48 ++++++++++---- .../Controller/DeletedShareAPIController.php | 66 +++++++++++++++++-- .../lib/Controller/ShareAPIController.php | 1 - 7 files changed, 175 insertions(+), 19 deletions(-) create mode 100644 apps/files/img/unshare.svg diff --git a/apps/files/css/files.scss b/apps/files/css/files.scss index 01703df5bf..24ecbf399e 100644 --- a/apps/files/css/files.scss +++ b/apps/files/css/files.scss @@ -105,6 +105,9 @@ .nav-icon-trashbin { background-image: url('../img/delete.svg?v=1'); } +.nav-icon-deletedshares { + background-image: url('../img/unshare.svg?v=1'); +} #app-navigation .nav-files a.nav-icon-files { width: auto; diff --git a/apps/files/img/unshare.svg b/apps/files/img/unshare.svg new file mode 100644 index 0000000000..0c22ca6405 --- /dev/null +++ b/apps/files/img/unshare.svg @@ -0,0 +1 @@ + diff --git a/apps/files_sharing/appinfo/app.php b/apps/files_sharing/appinfo/app.php index f5aa1fc09a..40a103edb9 100644 --- a/apps/files_sharing/appinfo/app.php +++ b/apps/files_sharing/appinfo/app.php @@ -48,6 +48,9 @@ $eventDispatcher->addListener( ); $config = \OC::$server->getConfig(); +$shareManager = \OC::$server->getShareManager(); +$userSession = \OC::$server->getUserSession(); + if ($config->getAppValue('core', 'shareapi_enabled', 'yes') === 'yes') { \OCA\Files\App::getNavigationManager()->add(function () { $l = \OC::$server->getL10N('files_sharing'); @@ -59,6 +62,19 @@ if ($config->getAppValue('core', 'shareapi_enabled', 'yes') === 'yes') { 'name' => $l->t('Shared with you'), ]; }); + $deletedShares = $shareManager->getDeletedSharedWith($userSession->getUser()->getUID(), \OCP\Share::SHARE_TYPE_GROUP, null, -1, 0); + if (count($deletedShares) > 0) { + \OCA\Files\App::getNavigationManager()->add(function () { + $l = \OC::$server->getL10N('files_sharing'); + return [ + 'id' => 'deletedshares', + 'appname' => 'files_sharing', + 'script' => 'list.php', + 'order' => 18, + 'name' => $l->t('Deleted shares'), + ]; + }); + } if (\OCP\Util::isSharingDisabledForUser() === false) { \OCA\Files\App::getNavigationManager()->add(function () { diff --git a/apps/files_sharing/js/app.js b/apps/files_sharing/js/app.js index e6c9159eda..01857e4f26 100644 --- a/apps/files_sharing/js/app.js +++ b/apps/files_sharing/js/app.js @@ -92,6 +92,30 @@ OCA.Sharing.App = { return this._linkFileList; }, + initSharingDeleted: function($el) { + if (this._deletedFileList) { + return this._deletedFileList; + } + this._deletedFileList = new OCA.Sharing.FileList( + $el, + { + id: 'shares.deleted', + scrollContainer: $('#app-content'), + showDeleted: true, + sharedWithUser: true, + fileActions: this._restoreShareAction(), + config: OCA.Files.App.getFilesConfig() + } + ); + + this._extendFileList(this._deletedFileList); + this._deletedFileList.appName = t('files_sharing', 'Deleted shares'); + this._deletedFileList.$el.find('#emptycontent').html('
' + + '

' + t('files_sharing', 'No deleted shares') + '

' + + '

' + t('files_sharing', 'Shares you deleted will show up here') + '

'); + return this._deletedFileList; + }, + removeSharingIn: function() { if (this._inFileList) { this._inFileList.$fileList.empty(); @@ -110,6 +134,12 @@ OCA.Sharing.App = { } }, + removeSharingDeleted: function() { + if (this._deletedFileList) { + this._deletedFileList.$fileList.empty(); + } + }, + /** * Destroy the app */ @@ -151,6 +181,29 @@ OCA.Sharing.App = { return fileActions; }, + _restoreShareAction: function() { + var fileActions = new OCA.Files.FileActions(); + fileActions.registerAction({ + name: 'Restore', + displayName: '', + altText: t('files_sharing', 'Restore share'), + mime: 'all', + permissions: OC.PERMISSION_ALL, + iconClass: 'icon-history', + type: OCA.Files.FileActions.TYPE_INLINE, + actionHandler: function(fileName, context) { + var shareId = context.$file.data('shareId'); + $.post(OC.linkToOCS('apps/files_sharing/api/v1/deletedshares', 2) + shareId) + .success(function(result) { + context.fileList.remove(context.fileInfoModel.attributes.name); + }).fail(function() { + OC.Notification.showTemporary(t('files_sharing', 'Something happened. Unable to restore the share.')); + }); + } + }); + return fileActions; + }, + _onActionsUpdated: function(ev) { _.each([this._inFileList, this._outFileList, this._linkFileList], function(list) { if (!list) { @@ -193,4 +246,10 @@ $(document).ready(function() { $('#app-content-sharinglinks').on('hide', function() { OCA.Sharing.App.removeSharingLinks(); }); + $('#app-content-deletedshares').on('show', function(e) { + OCA.Sharing.App.initSharingDeleted($(e.target)); + }); + $('#app-content-deletedshares').on('hide', function() { + OCA.Sharing.App.removeSharingDeleted(); + }); }); diff --git a/apps/files_sharing/js/sharedfilelist.js b/apps/files_sharing/js/sharedfilelist.js index ad818d9141..aaa04ca12f 100644 --- a/apps/files_sharing/js/sharedfilelist.js +++ b/apps/files_sharing/js/sharedfilelist.js @@ -37,6 +37,7 @@ */ _sharedWithUser: false, _linksOnly: false, + _showDeleted: false, _clientSideSort: true, _allowSelection: false, @@ -56,6 +57,9 @@ if (options && options.linksOnly) { this._linksOnly = true; } + if (options && options.showDeleted) { + this._showDeleted = true; + } }, _renderRow: function() { @@ -78,7 +82,7 @@ var permission = parseInt($tr.attr('data-permissions')) | OC.PERMISSION_DELETE; $tr.attr('data-permissions', permission); } - + // add row with expiration date for link only shares - influenced by _createRow of filelist if (this._linksOnly) { var expirationTimestamp = 0; @@ -183,20 +187,36 @@ // there is only root this._setCurrentDir('/', false); + + if (this._showDeleted) { + var shares = $.ajax({ + url: OC.linkToOCS('apps/files_sharing/api/v1', 2) + 'deletedshares', + /* jshint camelcase: false */ + data: { + format: 'json', + include_tags: true + }, + type: 'GET', + beforeSend: function(xhr) { + xhr.setRequestHeader('OCS-APIREQUEST', 'true'); + }, + }); + } else { + var shares = $.ajax({ + url: OC.linkToOCS('apps/files_sharing/api/v1') + 'shares', + /* jshint camelcase: false */ + data: { + format: 'json', + shared_with_me: !!this._sharedWithUser, + include_tags: true + }, + type: 'GET', + beforeSend: function(xhr) { + xhr.setRequestHeader('OCS-APIREQUEST', 'true'); + }, + }); + } var promises = []; - var shares = $.ajax({ - url: OC.linkToOCS('apps/files_sharing/api/v1') + 'shares', - /* jshint camelcase: false */ - data: { - format: 'json', - shared_with_me: !!this._sharedWithUser, - include_tags: true - }, - type: 'GET', - beforeSend: function(xhr) { - xhr.setRequestHeader('OCS-APIREQUEST', 'true'); - }, - }); promises.push(shares); if (!!this._sharedWithUser) { diff --git a/apps/files_sharing/lib/Controller/DeletedShareAPIController.php b/apps/files_sharing/lib/Controller/DeletedShareAPIController.php index 2e4f4d52d7..bd00d1a261 100644 --- a/apps/files_sharing/lib/Controller/DeletedShareAPIController.php +++ b/apps/files_sharing/lib/Controller/DeletedShareAPIController.php @@ -30,6 +30,8 @@ use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\OCS\OCSException; use OCP\AppFramework\OCS\OCSNotFoundException; use OCP\AppFramework\OCSController; +use OCP\Files\IRootFolder; +use OCP\IGroupManager; use OCP\IRequest; use OCP\IUserManager; use OCP\Share\Exceptions\GenericShareException; @@ -48,25 +50,81 @@ class DeletedShareAPIController extends OCSController { /** @var IUserManager */ private $userManager; + /** @var IGroupManager */ + private $groupManager; + + /** @var IRootFolder */ + private $rootFolder; + public function __construct(string $appName, IRequest $request, ShareManager $shareManager, string $UserId, - IUserManager $userManager) { + IUserManager $userManager, + IGroupManager $groupManager, + IRootFolder $rootFolder) { parent::__construct($appName, $request); $this->shareManager = $shareManager; $this->userId = $UserId; $this->userManager = $userManager; + $this->groupManager = $groupManager; + $this->rootFolder = $rootFolder; } private function formatShare(IShare $share): array { - return [ + + $result = [ 'id' => $share->getFullId(), - 'uid_owner' => $share->getShareOwner(), - 'displayname_owner' => $this->userManager->get($share->getShareOwner())->getDisplayName(), + 'share_type' => $share->getShareType(), + 'uid_owner' => $share->getSharedBy(), + 'displayname_owner' => $this->userManager->get($share->getSharedBy())->getDisplayName(), + 'permissions' => $share->getPermissions(), + 'stime' => $share->getShareTime()->getTimestamp(), + 'parent' => null, + 'expiration' => null, + 'token' => null, + 'uid_file_owner' => $share->getShareOwner(), + 'displayname_file_owner' => $this->userManager->get($share->getShareOwner())->getDisplayName(), 'path' => $share->getTarget(), ]; + $userFolder = $this->rootFolder->getUserFolder($share->getSharedBy()); + $nodes = $userFolder->getById($share->getNodeId()); + if (empty($nodes)) { + // fallback to guessing the path + $node = $userFolder->get($share->getTarget()); + if ($node === null || $share->getTarget() === '') { + throw new NotFoundException(); + } + } else { + $node = $nodes[0]; + } + + $result['path'] = $userFolder->getRelativePath($node->getPath()); + if ($node instanceOf \OCP\Files\Folder) { + $result['item_type'] = 'folder'; + } else { + $result['item_type'] = 'file'; + } + $result['mimetype'] = $node->getMimetype(); + $result['storage_id'] = $node->getStorage()->getId(); + $result['storage'] = $node->getStorage()->getCache()->getNumericStorageId(); + $result['item_source'] = $node->getId(); + $result['file_source'] = $node->getId(); + $result['file_parent'] = $node->getParent()->getId(); + $result['file_target'] = $share->getTarget(); + + $expiration = $share->getExpirationDate(); + if ($expiration !== null) { + $result['expiration'] = $expiration->format('Y-m-d 00:00:00'); + } + + $group = $this->groupManager->get($share->getSharedWith()); + $result['share_with'] = $share->getSharedWith(); + $result['share_with_displayname'] = $group !== null ? $group->getDisplayName() : $share->getSharedWith(); + + return $result; + } /** diff --git a/apps/files_sharing/lib/Controller/ShareAPIController.php b/apps/files_sharing/lib/Controller/ShareAPIController.php index 35fe3ac81a..67ff9eae6d 100644 --- a/apps/files_sharing/lib/Controller/ShareAPIController.php +++ b/apps/files_sharing/lib/Controller/ShareAPIController.php @@ -151,7 +151,6 @@ class ShareAPIController extends OCSController { $node = $recipientNode; } else { $nodes = $userFolder->getById($share->getNodeId()); - if (empty($nodes)) { // fallback to guessing the path $node = $userFolder->get($share->getTarget()); From d86c1e95030ebd613348f36d62984ecd842c2618 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20Molakvo=C3=A6=20=28skjnldsv=29?= Date: Wed, 20 Jun 2018 20:32:19 +0200 Subject: [PATCH 4/9] Disable sidebar on deleted share MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: John Molakvoæ (skjnldsv) --- apps/files_sharing/js/app.js | 10 +++++----- apps/files_sharing/js/share.js | 16 +++++++++++++--- apps/files_sharing/js/sharedfilelist.js | 4 ++++ .../lib/Controller/DeletedShareAPIController.php | 2 +- 4 files changed, 23 insertions(+), 9 deletions(-) diff --git a/apps/files_sharing/js/app.js b/apps/files_sharing/js/app.js index 01857e4f26..f63410bc9b 100644 --- a/apps/files_sharing/js/app.js +++ b/apps/files_sharing/js/app.js @@ -194,11 +194,11 @@ OCA.Sharing.App = { actionHandler: function(fileName, context) { var shareId = context.$file.data('shareId'); $.post(OC.linkToOCS('apps/files_sharing/api/v1/deletedshares', 2) + shareId) - .success(function(result) { - context.fileList.remove(context.fileInfoModel.attributes.name); - }).fail(function() { - OC.Notification.showTemporary(t('files_sharing', 'Something happened. Unable to restore the share.')); - }); + .success(function(result) { + context.fileList.remove(context.fileInfoModel.attributes.name); + }).fail(function() { + OC.Notification.showTemporary(t('files_sharing', 'Something happened. Unable to restore the share.')); + }); } }); return fileActions; diff --git a/apps/files_sharing/js/share.js b/apps/files_sharing/js/share.js index 22513e1b47..5f7705a836 100644 --- a/apps/files_sharing/js/share.js +++ b/apps/files_sharing/js/share.js @@ -42,6 +42,12 @@ var fileActions = fileList.fileActions; var oldCreateRow = fileList._createRow; fileList._createRow = function(fileData) { + + if (fileData.permissions === 0) { + // no permission, disabling sidebar + delete fileActions.actions.all.Details; + } + var tr = oldCreateRow.apply(this, arguments); var sharePermissions = OCA.Sharing.Util.getSharePermissions(fileData); tr.attr('data-share-permissions', sharePermissions); @@ -158,11 +164,15 @@ permissions: OC.PERMISSION_ALL, iconClass: 'icon-shared', type: OCA.Files.FileActions.TYPE_INLINE, - actionHandler: function(fileName) { - fileList.showDetailsView(fileName, 'shareTabView'); + actionHandler: function(fileName, context) { + // do not open sidebar if no permission on the file + var permissions = parseInt(context.$file.data('share-permissions'), 10); + if (permissions > 0) { + fileList.showDetailsView(fileName, 'shareTabView'); + } }, render: function(actionSpec, isDefault, context) { - var permissions = parseInt(context.$file.attr('data-permissions'), 10); + var permissions = parseInt(context.$file.data('permissions'), 10); // if no share permissions but share owner exists, still show the link if ((permissions & OC.PERMISSION_SHARE) !== 0 || context.$file.attr('data-share-owner')) { return fileActions._defaultRenderAction.call(fileActions, actionSpec, isDefault, context); diff --git a/apps/files_sharing/js/sharedfilelist.js b/apps/files_sharing/js/sharedfilelist.js index aaa04ca12f..973d2120b1 100644 --- a/apps/files_sharing/js/sharedfilelist.js +++ b/apps/files_sharing/js/sharedfilelist.js @@ -82,6 +82,10 @@ var permission = parseInt($tr.attr('data-permissions')) | OC.PERMISSION_DELETE; $tr.attr('data-permissions', permission); } + if (this._showDeleted) { + var permission = fileData.permissions; + $tr.attr('data-share-permissions', permission); + } // add row with expiration date for link only shares - influenced by _createRow of filelist if (this._linksOnly) { diff --git a/apps/files_sharing/lib/Controller/DeletedShareAPIController.php b/apps/files_sharing/lib/Controller/DeletedShareAPIController.php index bd00d1a261..3c352c1c02 100644 --- a/apps/files_sharing/lib/Controller/DeletedShareAPIController.php +++ b/apps/files_sharing/lib/Controller/DeletedShareAPIController.php @@ -79,7 +79,7 @@ class DeletedShareAPIController extends OCSController { 'share_type' => $share->getShareType(), 'uid_owner' => $share->getSharedBy(), 'displayname_owner' => $this->userManager->get($share->getSharedBy())->getDisplayName(), - 'permissions' => $share->getPermissions(), + 'permissions' => 0, 'stime' => $share->getShareTime()->getTimestamp(), 'parent' => null, 'expiration' => null, From 33f85f64c7b2ac258bf10f4e17e417decfaae638 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20Molakvo=C3=A6=20=28skjnldsv=29?= Date: Tue, 26 Jun 2018 08:21:28 +0200 Subject: [PATCH 5/9] Always show menu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: John Molakvoæ (skjnldsv) --- apps/files_sharing/appinfo/app.php | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/apps/files_sharing/appinfo/app.php b/apps/files_sharing/appinfo/app.php index 40a103edb9..4f5cf09bef 100644 --- a/apps/files_sharing/appinfo/app.php +++ b/apps/files_sharing/appinfo/app.php @@ -62,19 +62,17 @@ if ($config->getAppValue('core', 'shareapi_enabled', 'yes') === 'yes') { 'name' => $l->t('Shared with you'), ]; }); - $deletedShares = $shareManager->getDeletedSharedWith($userSession->getUser()->getUID(), \OCP\Share::SHARE_TYPE_GROUP, null, -1, 0); - if (count($deletedShares) > 0) { - \OCA\Files\App::getNavigationManager()->add(function () { - $l = \OC::$server->getL10N('files_sharing'); - return [ - 'id' => 'deletedshares', - 'appname' => 'files_sharing', - 'script' => 'list.php', - 'order' => 18, - 'name' => $l->t('Deleted shares'), - ]; - }); - } + + \OCA\Files\App::getNavigationManager()->add(function () { + $l = \OC::$server->getL10N('files_sharing'); + return [ + 'id' => 'deletedshares', + 'appname' => 'files_sharing', + 'script' => 'list.php', + 'order' => 18, + 'name' => $l->t('Deleted shares'), + ]; + }); if (\OCP\Util::isSharingDisabledForUser() === false) { \OCA\Files\App::getNavigationManager()->add(function () { From ced8789f5c96cf491ce4774cc3f419081000ae0b Mon Sep 17 00:00:00 2001 From: Morris Jobke Date: Tue, 26 Jun 2018 08:59:59 +0200 Subject: [PATCH 6/9] Fix PHPDoc Signed-off-by: Morris Jobke --- lib/public/Share/IManager.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/public/Share/IManager.php b/lib/public/Share/IManager.php index c8abab378e..d4fc3e1474 100644 --- a/lib/public/Share/IManager.php +++ b/lib/public/Share/IManager.php @@ -159,7 +159,7 @@ interface IManager { * @param int $limit The maximum number of shares returned, -1 for all * @param int $offset * @return IShare[] - * @since 9.0.0 + * @since 14.0.0 */ public function getDeletedSharedWith($userId, $shareType, $node = null, $limit = 50, $offset = 0); From a1a845693ba5cf1622b60962f73d211a0807a390 Mon Sep 17 00:00:00 2001 From: Roeland Jago Douma Date: Tue, 26 Jun 2018 09:30:40 +0200 Subject: [PATCH 7/9] Fix tests Signed-off-by: Roeland Jago Douma --- .../files/tests/Controller/ViewControllerTest.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/apps/files/tests/Controller/ViewControllerTest.php b/apps/files/tests/Controller/ViewControllerTest.php index a739e26bd9..eae627fd6a 100644 --- a/apps/files/tests/Controller/ViewControllerTest.php +++ b/apps/files/tests/Controller/ViewControllerTest.php @@ -206,6 +206,17 @@ class ViewControllerTest extends TestCase { 'type' => 'link', 'classes' => '', ], + [ + 'id' => 'deletedshares', + 'appname' => 'files_sharing', + 'script' => 'list.php', + 'order' => 18, + 'name' => \OC::$server->getL10N('files_sharing')->t('Deleted shares'), + 'active' => false, + 'icon' => '', + 'type' => 'link', + 'classes' => '', + ], [ 'id' => 'systemtagsfilter', 'appname' => 'systemtags', @@ -269,6 +280,10 @@ class ViewControllerTest extends TestCase { 'id' => 'sharinglinks', 'content' => null, ], + [ + 'id' => 'deletedshares', + 'content' => null, + ], [ 'id' => 'systemtagsfilter', 'content' => null, From b49bc11fb3c267ef32af62b429d5259a5187c86f Mon Sep 17 00:00:00 2001 From: Roeland Jago Douma Date: Tue, 26 Jun 2018 09:32:29 +0200 Subject: [PATCH 8/9] Fix exception Signed-off-by: Roeland Jago Douma --- apps/files_sharing/lib/Controller/DeletedShareAPIController.php | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/files_sharing/lib/Controller/DeletedShareAPIController.php b/apps/files_sharing/lib/Controller/DeletedShareAPIController.php index 3c352c1c02..d95b434e48 100644 --- a/apps/files_sharing/lib/Controller/DeletedShareAPIController.php +++ b/apps/files_sharing/lib/Controller/DeletedShareAPIController.php @@ -31,6 +31,7 @@ use OCP\AppFramework\OCS\OCSException; use OCP\AppFramework\OCS\OCSNotFoundException; use OCP\AppFramework\OCSController; use OCP\Files\IRootFolder; +use OCP\Files\NotFoundException; use OCP\IGroupManager; use OCP\IRequest; use OCP\IUserManager; From f39dfc7ab8ced56de4935a56d87334b211df9b5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?John=20Molakvo=C3=A6=20=28skjnldsv=29?= Date: Thu, 5 Jul 2018 12:43:52 +0200 Subject: [PATCH 9/9] Fixed jsunit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: John Molakvoæ (skjnldsv) --- apps/files_sharing/js/share.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/files_sharing/js/share.js b/apps/files_sharing/js/share.js index 5f7705a836..a925920f3b 100644 --- a/apps/files_sharing/js/share.js +++ b/apps/files_sharing/js/share.js @@ -165,9 +165,9 @@ iconClass: 'icon-shared', type: OCA.Files.FileActions.TYPE_INLINE, actionHandler: function(fileName, context) { - // do not open sidebar if no permission on the file + // do not open sidebar if permission is set and equal to 0 var permissions = parseInt(context.$file.data('share-permissions'), 10); - if (permissions > 0) { + if (isNaN(permissions) || permissions > 0) { fileList.showDetailsView(fileName, 'shareTabView'); } },