Merge pull request #22789 from owncloud/dav-sharesproperty
Add webdav property for share info in PROPFIND response
This commit is contained in:
commit
8852fdaee3
|
@ -137,6 +137,12 @@ class ServerFactory {
|
|||
|
||||
if($this->userSession->isLoggedIn()) {
|
||||
$server->addPlugin(new \OCA\DAV\Connector\Sabre\TagsPlugin($objectTree, $this->tagManager));
|
||||
$server->addPlugin(new \OCA\DAV\Connector\Sabre\SharesPlugin(
|
||||
$objectTree,
|
||||
$this->userSession,
|
||||
$userFolder,
|
||||
\OC::$server->getShareManager()
|
||||
));
|
||||
$server->addPlugin(new \OCA\DAV\Connector\Sabre\CommentPropertiesPlugin(\OC::$server->getCommentsManager(), $this->userSession));
|
||||
$server->addPlugin(new \OCA\DAV\Connector\Sabre\FilesReportPlugin(
|
||||
$objectTree,
|
||||
|
|
|
@ -0,0 +1,176 @@
|
|||
<?php
|
||||
/**
|
||||
* @author Vincent Petry <pvince81@owncloud.com>
|
||||
*
|
||||
* @copyright Copyright (c) 2016, ownCloud, Inc.
|
||||
* @license AGPL-3.0
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
namespace OCA\DAV\Connector\Sabre;
|
||||
|
||||
use \Sabre\DAV\PropFind;
|
||||
use \Sabre\DAV\PropPatch;
|
||||
use OCP\IUserSession;
|
||||
use OCP\Share\IShare;
|
||||
use OCA\DAV\Connector\Sabre\ShareTypeList;
|
||||
|
||||
/**
|
||||
* Sabre Plugin to provide share-related properties
|
||||
*/
|
||||
class SharesPlugin extends \Sabre\DAV\ServerPlugin {
|
||||
|
||||
const NS_OWNCLOUD = 'http://owncloud.org/ns';
|
||||
const SHARETYPES_PROPERTYNAME = '{http://owncloud.org/ns}share-types';
|
||||
|
||||
/**
|
||||
* Reference to main server object
|
||||
*
|
||||
* @var \Sabre\DAV\Server
|
||||
*/
|
||||
private $server;
|
||||
|
||||
/**
|
||||
* @var \OCP\Share\IManager
|
||||
*/
|
||||
private $shareManager;
|
||||
|
||||
/**
|
||||
* @var \Sabre\DAV\Tree
|
||||
*/
|
||||
private $tree;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $userId;
|
||||
|
||||
/**
|
||||
* @var \OCP\Files\Folder
|
||||
*/
|
||||
private $userFolder;
|
||||
|
||||
/**
|
||||
* @var IShare[]
|
||||
*/
|
||||
private $cachedShareTypes;
|
||||
|
||||
/**
|
||||
* @param \Sabre\DAV\Tree $tree tree
|
||||
* @param IUserSession $userSession user session
|
||||
* @param \OCP\Files\Folder $userFolder user home folder
|
||||
* @param \OCP\Share\IManager $shareManager share manager
|
||||
*/
|
||||
public function __construct(
|
||||
\Sabre\DAV\Tree $tree,
|
||||
IUserSession $userSession,
|
||||
\OCP\Files\Folder $userFolder,
|
||||
\OCP\Share\IManager $shareManager
|
||||
) {
|
||||
$this->tree = $tree;
|
||||
$this->shareManager = $shareManager;
|
||||
$this->userFolder = $userFolder;
|
||||
$this->userId = $userSession->getUser()->getUID();
|
||||
$this->cachedShareTypes = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* This initializes the plugin.
|
||||
*
|
||||
* This function is called by \Sabre\DAV\Server, after
|
||||
* addPlugin is called.
|
||||
*
|
||||
* This method should set up the required event subscriptions.
|
||||
*
|
||||
* @param \Sabre\DAV\Server $server
|
||||
*/
|
||||
public function initialize(\Sabre\DAV\Server $server) {
|
||||
$server->xml->namespacesMap[self::NS_OWNCLOUD] = 'oc';
|
||||
$server->xml->elementMap[self::SHARETYPES_PROPERTYNAME] = 'OCA\\DAV\\Connector\\Sabre\\ShareTypeList';
|
||||
$server->protectedProperties[] = self::SHARETYPES_PROPERTYNAME;
|
||||
|
||||
$this->server = $server;
|
||||
$this->server->on('propFind', array($this, 'handleGetProperties'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of share types for outgoing shares
|
||||
*
|
||||
* @param \OCP\Files\Node $node file node
|
||||
*
|
||||
* @return int[] array of share types
|
||||
*/
|
||||
private function getShareTypes(\OCP\Files\Node $node) {
|
||||
$shareTypes = [];
|
||||
$requestedShareTypes = [
|
||||
\OCP\Share::SHARE_TYPE_USER,
|
||||
\OCP\Share::SHARE_TYPE_GROUP,
|
||||
\OCP\Share::SHARE_TYPE_LINK
|
||||
];
|
||||
foreach ($requestedShareTypes as $requestedShareType) {
|
||||
// one of each type is enough to find out about the types
|
||||
$shares = $this->shareManager->getSharesBy(
|
||||
$this->userId,
|
||||
$requestedShareType,
|
||||
$node,
|
||||
false,
|
||||
1
|
||||
);
|
||||
if (!empty($shares)) {
|
||||
$shareTypes[] = $requestedShareType;
|
||||
}
|
||||
}
|
||||
return $shareTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds shares to propfind response
|
||||
*
|
||||
* @param PropFind $propFind propfind object
|
||||
* @param \Sabre\DAV\INode $sabreNode sabre node
|
||||
*/
|
||||
public function handleGetProperties(
|
||||
PropFind $propFind,
|
||||
\Sabre\DAV\INode $sabreNode
|
||||
) {
|
||||
if (!($sabreNode instanceof \OCA\DAV\Connector\Sabre\Node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// need prefetch ?
|
||||
if ($sabreNode instanceof \OCA\DAV\Connector\Sabre\Directory
|
||||
&& $propFind->getDepth() !== 0
|
||||
&& !is_null($propFind->getStatus(self::SHARETYPES_PROPERTYNAME))
|
||||
) {
|
||||
$folderNode = $this->userFolder->get($propFind->getPath());
|
||||
$children = $folderNode->getDirectoryListing();
|
||||
|
||||
$this->cachedShareTypes[$folderNode->getId()] = $this->getShareTypes($folderNode);
|
||||
foreach ($children as $childNode) {
|
||||
$this->cachedShareTypes[$childNode->getId()] = $this->getShareTypes($childNode);
|
||||
}
|
||||
}
|
||||
|
||||
$propFind->handle(self::SHARETYPES_PROPERTYNAME, function() use ($sabreNode) {
|
||||
if (isset($this->cachedShareTypes[$sabreNode->getId()])) {
|
||||
$shareTypes = $this->cachedShareTypes[$sabreNode->getId()];
|
||||
} else {
|
||||
$node = $this->userFolder->get($sabreNode->getPath());
|
||||
$shareTypes = $this->getShareTypes($node);
|
||||
}
|
||||
|
||||
return new ShareTypeList($shareTypes);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
<?php
|
||||
/**
|
||||
* @author Vincent Petry <pvince81@owncloud.com>
|
||||
*
|
||||
* @copyright Copyright (c) 2016, ownCloud, Inc.
|
||||
* @license AGPL-3.0
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\DAV\Connector\Sabre;
|
||||
|
||||
use Sabre\Xml\Element;
|
||||
use Sabre\Xml\Reader;
|
||||
use Sabre\Xml\Writer;
|
||||
|
||||
/**
|
||||
* ShareTypeList property
|
||||
*
|
||||
* This property contains multiple "share-type" elements, each containing a share type.
|
||||
*/
|
||||
class ShareTypeList implements Element {
|
||||
const NS_OWNCLOUD = 'http://owncloud.org/ns';
|
||||
|
||||
/**
|
||||
* Share types
|
||||
*
|
||||
* @var int[]
|
||||
*/
|
||||
private $shareTypes;
|
||||
|
||||
/**
|
||||
* @param int[] $shareTypes
|
||||
*/
|
||||
public function __construct($shareTypes) {
|
||||
$this->shareTypes = $shareTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the share types
|
||||
*
|
||||
* @return int[]
|
||||
*/
|
||||
public function getShareTypes() {
|
||||
return $this->shareTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* The deserialize method is called during xml parsing.
|
||||
*
|
||||
* @param Reader $reader
|
||||
* @return mixed
|
||||
*/
|
||||
static function xmlDeserialize(Reader $reader) {
|
||||
$shareTypes = [];
|
||||
|
||||
foreach ($reader->parseInnerTree() as $elem) {
|
||||
if ($elem['name'] === '{' . self::NS_OWNCLOUD . '}share-type') {
|
||||
$shareTypes[] = (int)$elem['value'];
|
||||
}
|
||||
}
|
||||
return new self($shareTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* The xmlSerialize metod is called during xml writing.
|
||||
*
|
||||
* @param Writer $writer
|
||||
* @return void
|
||||
*/
|
||||
function xmlSerialize(Writer $writer) {
|
||||
foreach ($this->shareTypes as $shareType) {
|
||||
$writer->writeElement('{' . self::NS_OWNCLOUD . '}share-type', $shareType);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,257 @@
|
|||
<?php
|
||||
/**
|
||||
* @author Vincent Petry <pvince81@owncloud.com>
|
||||
*
|
||||
* @copyright Copyright (c) 2016, ownCloud, Inc.
|
||||
* @license AGPL-3.0
|
||||
*
|
||||
* This code is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License, version 3,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License, version 3,
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
*
|
||||
*/
|
||||
namespace OCA\DAV\Tests\Unit\Connector\Sabre;
|
||||
|
||||
class SharesPlugin extends \Test\TestCase {
|
||||
|
||||
const SHARETYPES_PROPERTYNAME = \OCA\DAV\Connector\Sabre\SharesPlugin::SHARETYPES_PROPERTYNAME;
|
||||
|
||||
/**
|
||||
* @var \Sabre\DAV\Server
|
||||
*/
|
||||
private $server;
|
||||
|
||||
/**
|
||||
* @var \Sabre\DAV\Tree
|
||||
*/
|
||||
private $tree;
|
||||
|
||||
/**
|
||||
* @var \OCP\Share\IManager
|
||||
*/
|
||||
private $shareManager;
|
||||
|
||||
/**
|
||||
* @var \OCP\Files\Folder
|
||||
*/
|
||||
private $userFolder;
|
||||
|
||||
/**
|
||||
* @var \OCA\DAV\Connector\Sabre\SharesPlugin
|
||||
*/
|
||||
private $plugin;
|
||||
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
$this->server = new \Sabre\DAV\Server();
|
||||
$this->tree = $this->getMockBuilder('\Sabre\DAV\Tree')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$this->shareManager = $this->getMock('\OCP\Share\IManager');
|
||||
$user = $this->getMock('\OCP\IUser');
|
||||
$user->expects($this->once())
|
||||
->method('getUID')
|
||||
->will($this->returnValue('user1'));
|
||||
$userSession = $this->getMock('\OCP\IUserSession');
|
||||
$userSession->expects($this->once())
|
||||
->method('getUser')
|
||||
->will($this->returnValue($user));
|
||||
|
||||
$this->userFolder = $this->getMock('\OCP\Files\Folder');
|
||||
|
||||
$this->plugin = new \OCA\DAV\Connector\Sabre\SharesPlugin(
|
||||
$this->tree,
|
||||
$userSession,
|
||||
$this->userFolder,
|
||||
$this->shareManager
|
||||
);
|
||||
$this->plugin->initialize($this->server);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider sharesGetPropertiesDataProvider
|
||||
*/
|
||||
public function testGetProperties($shareTypes) {
|
||||
$sabreNode = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\Node')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$sabreNode->expects($this->any())
|
||||
->method('getId')
|
||||
->will($this->returnValue(123));
|
||||
$sabreNode->expects($this->once())
|
||||
->method('getPath')
|
||||
->will($this->returnValue('/subdir'));
|
||||
|
||||
// node API nodes
|
||||
$node = $this->getMock('\OCP\Files\Folder');
|
||||
|
||||
$this->userFolder->expects($this->once())
|
||||
->method('get')
|
||||
->with('/subdir')
|
||||
->will($this->returnValue($node));
|
||||
|
||||
$this->shareManager->expects($this->any())
|
||||
->method('getSharesBy')
|
||||
->with(
|
||||
$this->equalTo('user1'),
|
||||
$this->anything(),
|
||||
$this->anything(),
|
||||
$this->equalTo(false),
|
||||
$this->equalTo(1)
|
||||
)
|
||||
->will($this->returnCallback(function($userId, $requestedShareType, $node, $flag, $limit) use ($shareTypes){
|
||||
if (in_array($requestedShareType, $shareTypes)) {
|
||||
return ['dummyshare'];
|
||||
}
|
||||
return [];
|
||||
}));
|
||||
|
||||
$propFind = new \Sabre\DAV\PropFind(
|
||||
'/dummyPath',
|
||||
[self::SHARETYPES_PROPERTYNAME],
|
||||
0
|
||||
);
|
||||
|
||||
$this->plugin->handleGetProperties(
|
||||
$propFind,
|
||||
$sabreNode
|
||||
);
|
||||
|
||||
$result = $propFind->getResultForMultiStatus();
|
||||
|
||||
$this->assertEmpty($result[404]);
|
||||
unset($result[404]);
|
||||
$this->assertEquals($shareTypes, $result[200][self::SHARETYPES_PROPERTYNAME]->getShareTypes());
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider sharesGetPropertiesDataProvider
|
||||
*/
|
||||
public function testPreloadThenGetProperties($shareTypes) {
|
||||
$sabreNode1 = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\File')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$sabreNode1->expects($this->any())
|
||||
->method('getId')
|
||||
->will($this->returnValue(111));
|
||||
$sabreNode1->expects($this->never())
|
||||
->method('getPath');
|
||||
$sabreNode2 = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\File')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$sabreNode2->expects($this->any())
|
||||
->method('getId')
|
||||
->will($this->returnValue(222));
|
||||
$sabreNode2->expects($this->never())
|
||||
->method('getPath');
|
||||
|
||||
$sabreNode = $this->getMockBuilder('\OCA\DAV\Connector\Sabre\Directory')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$sabreNode->expects($this->any())
|
||||
->method('getId')
|
||||
->will($this->returnValue(123));
|
||||
// never, because we use getDirectoryListing from the Node API instead
|
||||
$sabreNode->expects($this->never())
|
||||
->method('getChildren');
|
||||
$sabreNode->expects($this->any())
|
||||
->method('getPath')
|
||||
->will($this->returnValue('/subdir'));
|
||||
|
||||
// node API nodes
|
||||
$node = $this->getMock('\OCP\Files\Folder');
|
||||
$node->expects($this->any())
|
||||
->method('getId')
|
||||
->will($this->returnValue(123));
|
||||
$node1 = $this->getMock('\OCP\Files\File');
|
||||
$node1->expects($this->any())
|
||||
->method('getId')
|
||||
->will($this->returnValue(111));
|
||||
$node2 = $this->getMock('\OCP\Files\File');
|
||||
$node2->expects($this->any())
|
||||
->method('getId')
|
||||
->will($this->returnValue(222));
|
||||
$node->expects($this->once())
|
||||
->method('getDirectoryListing')
|
||||
->will($this->returnValue([$node1, $node2]));
|
||||
|
||||
$this->userFolder->expects($this->once())
|
||||
->method('get')
|
||||
->with('/subdir')
|
||||
->will($this->returnValue($node));
|
||||
|
||||
$this->shareManager->expects($this->any())
|
||||
->method('getSharesBy')
|
||||
->with(
|
||||
$this->equalTo('user1'),
|
||||
$this->anything(),
|
||||
$this->anything(),
|
||||
$this->equalTo(false),
|
||||
$this->equalTo(1)
|
||||
)
|
||||
->will($this->returnCallback(function($userId, $requestedShareType, $node, $flag, $limit) use ($shareTypes){
|
||||
if ($node->getId() === 111 && in_array($requestedShareType, $shareTypes)) {
|
||||
return ['dummyshare'];
|
||||
}
|
||||
|
||||
return [];
|
||||
}));
|
||||
|
||||
// simulate sabre recursive PROPFIND traversal
|
||||
$propFindRoot = new \Sabre\DAV\PropFind(
|
||||
'/subdir',
|
||||
[self::SHARETYPES_PROPERTYNAME],
|
||||
1
|
||||
);
|
||||
$propFind1 = new \Sabre\DAV\PropFind(
|
||||
'/subdir/test.txt',
|
||||
[self::SHARETYPES_PROPERTYNAME],
|
||||
0
|
||||
);
|
||||
$propFind2 = new \Sabre\DAV\PropFind(
|
||||
'/subdir/test2.txt',
|
||||
[self::SHARETYPES_PROPERTYNAME],
|
||||
0
|
||||
);
|
||||
|
||||
$this->plugin->handleGetProperties(
|
||||
$propFindRoot,
|
||||
$sabreNode
|
||||
);
|
||||
$this->plugin->handleGetProperties(
|
||||
$propFind1,
|
||||
$sabreNode1
|
||||
);
|
||||
$this->plugin->handleGetProperties(
|
||||
$propFind2,
|
||||
$sabreNode2
|
||||
);
|
||||
|
||||
$result = $propFind1->getResultForMultiStatus();
|
||||
|
||||
$this->assertEmpty($result[404]);
|
||||
unset($result[404]);
|
||||
$this->assertEquals($shareTypes, $result[200][self::SHARETYPES_PROPERTYNAME]->getShareTypes());
|
||||
}
|
||||
|
||||
function sharesGetPropertiesDataProvider() {
|
||||
return [
|
||||
[[]],
|
||||
[[\OCP\Share::SHARE_TYPE_USER]],
|
||||
[[\OCP\Share::SHARE_TYPE_GROUP]],
|
||||
[[\OCP\Share::SHARE_TYPE_LINK]],
|
||||
[[\OCP\Share::SHARE_TYPE_USER, \OCP\Share::SHARE_TYPE_GROUP]],
|
||||
[[\OCP\Share::SHARE_TYPE_USER, \OCP\Share::SHARE_TYPE_GROUP, \OCP\Share::SHARE_TYPE_LINK]],
|
||||
[[\OCP\Share::SHARE_TYPE_USER, \OCP\Share::SHARE_TYPE_LINK]],
|
||||
[[\OCP\Share::SHARE_TYPE_GROUP, \OCP\Share::SHARE_TYPE_LINK]],
|
||||
];
|
||||
}
|
||||
}
|
|
@ -40,8 +40,10 @@ class Application extends App {
|
|||
return new ApiController(
|
||||
$c->query('AppName'),
|
||||
$c->query('Request'),
|
||||
$server->getUserSession(),
|
||||
$c->query('TagService'),
|
||||
$server->getPreviewManager()
|
||||
$server->getPreviewManager(),
|
||||
$server->getShareManager()
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -34,6 +34,10 @@ use OCP\AppFramework\Http\DataResponse;
|
|||
use OCP\AppFramework\Http\DataDisplayResponse;
|
||||
use OCA\Files\Service\TagService;
|
||||
use OCP\IPreview;
|
||||
use OCP\Share\IManager;
|
||||
use OCP\Files\FileInfo;
|
||||
use OCP\Files\Node;
|
||||
use OCP\IUserSession;
|
||||
|
||||
/**
|
||||
* Class ApiController
|
||||
|
@ -43,8 +47,12 @@ use OCP\IPreview;
|
|||
class ApiController extends Controller {
|
||||
/** @var TagService */
|
||||
private $tagService;
|
||||
/** @var IManager **/
|
||||
private $shareManager;
|
||||
/** @var IPreview */
|
||||
private $previewManager;
|
||||
/** IUserSession */
|
||||
private $userSession;
|
||||
|
||||
/**
|
||||
* @param string $appName
|
||||
|
@ -54,11 +62,15 @@ class ApiController extends Controller {
|
|||
*/
|
||||
public function __construct($appName,
|
||||
IRequest $request,
|
||||
IUserSession $userSession,
|
||||
TagService $tagService,
|
||||
IPreview $previewManager){
|
||||
IPreview $previewManager,
|
||||
IManager $shareManager) {
|
||||
parent::__construct($appName, $request);
|
||||
$this->userSession = $userSession;
|
||||
$this->tagService = $tagService;
|
||||
$this->previewManager = $previewManager;
|
||||
$this->shareManager = $shareManager;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -132,8 +144,10 @@ class ApiController extends Controller {
|
|||
*/
|
||||
public function getFilesByTag($tagName) {
|
||||
$files = array();
|
||||
$fileInfos = $this->tagService->getFilesByTag($tagName);
|
||||
foreach ($fileInfos as &$fileInfo) {
|
||||
$nodes = $this->tagService->getFilesByTag($tagName);
|
||||
foreach ($nodes as &$node) {
|
||||
$shareTypes = $this->getShareTypes($node);
|
||||
$fileInfo = $node->getFileInfo();
|
||||
$file = \OCA\Files\Helper::formatFileInfo($fileInfo);
|
||||
$parts = explode('/', dirname($fileInfo->getPath()), 4);
|
||||
if(isset($parts[3])) {
|
||||
|
@ -142,9 +156,43 @@ class ApiController extends Controller {
|
|||
$file['path'] = '/';
|
||||
}
|
||||
$file['tags'] = [$tagName];
|
||||
if (!empty($shareTypes)) {
|
||||
$file['shareTypes'] = $shareTypes;
|
||||
}
|
||||
$files[] = $file;
|
||||
}
|
||||
return new DataResponse(['files' => $files]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of share types for outgoing shares
|
||||
*
|
||||
* @param Node $node file node
|
||||
*
|
||||
* @return int[] array of share types
|
||||
*/
|
||||
private function getShareTypes(Node $node) {
|
||||
$userId = $this->userSession->getUser()->getUID();
|
||||
$shareTypes = [];
|
||||
$requestedShareTypes = [
|
||||
\OCP\Share::SHARE_TYPE_USER,
|
||||
\OCP\Share::SHARE_TYPE_GROUP,
|
||||
\OCP\Share::SHARE_TYPE_LINK
|
||||
];
|
||||
foreach ($requestedShareTypes as $requestedShareType) {
|
||||
// one of each type is enough to find out about the types
|
||||
$shares = $this->shareManager->getSharesBy(
|
||||
$userId,
|
||||
$requestedShareType,
|
||||
$node,
|
||||
false,
|
||||
1
|
||||
);
|
||||
if (!empty($shares)) {
|
||||
$shareTypes[] = $requestedShareType;
|
||||
}
|
||||
}
|
||||
return $shareTypes;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
namespace OCA\Files\Service;
|
||||
|
||||
use OC\Files\FileInfo;
|
||||
use OCP\Files\Node;
|
||||
|
||||
/**
|
||||
* Service class to manage tags on files.
|
||||
|
@ -93,7 +94,7 @@ class TagService {
|
|||
* Get all files for the given tag
|
||||
*
|
||||
* @param string $tagName tag name to filter by
|
||||
* @return FileInfo[] list of matching files
|
||||
* @return Node[] list of matching files
|
||||
* @throws \Exception if the tag does not exist
|
||||
*/
|
||||
public function getFilesByTag($tagName) {
|
||||
|
@ -103,15 +104,11 @@ class TagService {
|
|||
return [];
|
||||
}
|
||||
|
||||
$fileInfos = [];
|
||||
$allNodes = [];
|
||||
foreach ($fileIds as $fileId) {
|
||||
$nodes = $this->homeFolder->getById((int) $fileId);
|
||||
foreach ($nodes as $node) {
|
||||
/** @var \OC\Files\Node\Node $node */
|
||||
$fileInfos[] = $node->getFileInfo();
|
||||
}
|
||||
$allNodes = array_merge($allNodes, $this->homeFolder->getById((int) $fileId));
|
||||
}
|
||||
return $fileInfos;
|
||||
return $allNodes;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -51,14 +51,27 @@ class ApiControllerTest extends TestCase {
|
|||
private $preview;
|
||||
/** @var ApiController */
|
||||
private $apiController;
|
||||
/** @var \OCP\Share\IManager */
|
||||
private $shareManager;
|
||||
|
||||
public function setUp() {
|
||||
$this->request = $this->getMockBuilder('\OCP\IRequest')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$user = $this->getMock('\OCP\IUser');
|
||||
$user->expects($this->any())
|
||||
->method('getUID')
|
||||
->will($this->returnValue('user1'));
|
||||
$userSession = $this->getMock('\OCP\IUserSession');
|
||||
$userSession->expects($this->any())
|
||||
->method('getUser')
|
||||
->will($this->returnValue($user));
|
||||
$this->tagService = $this->getMockBuilder('\OCA\Files\Service\TagService')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$this->shareManager = $this->getMockBuilder('\OCP\Share\IManager')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$this->preview = $this->getMockBuilder('\OCP\IPreview')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
@ -66,8 +79,10 @@ class ApiControllerTest extends TestCase {
|
|||
$this->apiController = new ApiController(
|
||||
$this->appName,
|
||||
$this->request,
|
||||
$userSession,
|
||||
$this->tagService,
|
||||
$this->preview
|
||||
$this->preview,
|
||||
$this->shareManager
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -101,10 +116,32 @@ class ApiControllerTest extends TestCase {
|
|||
->disableOriginalConstructor()
|
||||
->getMock()
|
||||
);
|
||||
$node = $this->getMockBuilder('\OC\Files\Node\File')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$node->expects($this->once())
|
||||
->method('getFileInfo')
|
||||
->will($this->returnValue($fileInfo));
|
||||
$this->tagService->expects($this->once())
|
||||
->method('getFilesByTag')
|
||||
->with($this->equalTo([$tagName]))
|
||||
->will($this->returnValue([$fileInfo]));
|
||||
->will($this->returnValue([$node]));
|
||||
|
||||
$this->shareManager->expects($this->any())
|
||||
->method('getSharesBy')
|
||||
->with(
|
||||
$this->equalTo('user1'),
|
||||
$this->anything(),
|
||||
$node,
|
||||
$this->equalTo(false),
|
||||
$this->equalTo(1)
|
||||
)
|
||||
->will($this->returnCallback(function($userId, $shareType) {
|
||||
if ($shareType === \OCP\Share::SHARE_TYPE_USER || $shareType === \OCP\Share::SHARE_TYPE_LINK) {
|
||||
return ['dummy_share'];
|
||||
}
|
||||
return [];
|
||||
}));
|
||||
|
||||
$expected = new DataResponse([
|
||||
'files' => [
|
||||
|
@ -124,6 +161,7 @@ class ApiControllerTest extends TestCase {
|
|||
'MyTagName'
|
||||
]
|
||||
],
|
||||
'shareTypes' => [\OCP\Share::SHARE_TYPE_USER, \OCP\Share::SHARE_TYPE_LINK]
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
@ -166,10 +204,22 @@ class ApiControllerTest extends TestCase {
|
|||
->disableOriginalConstructor()
|
||||
->getMock()
|
||||
);
|
||||
$node1 = $this->getMockBuilder('\OC\Files\Node\File')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$node1->expects($this->once())
|
||||
->method('getFileInfo')
|
||||
->will($this->returnValue($fileInfo1));
|
||||
$node2 = $this->getMockBuilder('\OC\Files\Node\File')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$node2->expects($this->once())
|
||||
->method('getFileInfo')
|
||||
->will($this->returnValue($fileInfo2));
|
||||
$this->tagService->expects($this->once())
|
||||
->method('getFilesByTag')
|
||||
->with($this->equalTo([$tagName]))
|
||||
->will($this->returnValue([$fileInfo1, $fileInfo2]));
|
||||
->will($this->returnValue([$node1, $node2]));
|
||||
|
||||
$expected = new DataResponse([
|
||||
'files' => [
|
||||
|
|
|
@ -60,6 +60,9 @@
|
|||
if (fileData.recipientsDisplayName) {
|
||||
tr.attr('data-share-recipients', fileData.recipientsDisplayName);
|
||||
}
|
||||
if (fileData.shareTypes) {
|
||||
tr.attr('data-share-types', fileData.shareTypes.join(','));
|
||||
}
|
||||
return tr;
|
||||
};
|
||||
|
||||
|
@ -77,6 +80,7 @@
|
|||
fileList._getWebdavProperties = function() {
|
||||
var props = oldGetWebdavProperties.apply(this, arguments);
|
||||
props.push('{' + NS_OC + '}owner-display-name');
|
||||
props.push('{' + NS_OC + '}share-types');
|
||||
return props;
|
||||
};
|
||||
|
||||
|
@ -88,40 +92,45 @@
|
|||
if (permissionsProp && permissionsProp.indexOf('S') >= 0) {
|
||||
data.shareOwner = props['{' + NS_OC + '}owner-display-name'];
|
||||
}
|
||||
|
||||
var shareTypesProp = props['{' + NS_OC + '}share-types'];
|
||||
if (shareTypesProp) {
|
||||
data.shareTypes = _.chain(shareTypesProp).filter(function(xmlvalue) {
|
||||
return (xmlvalue.namespaceURI === NS_OC && xmlvalue.nodeName.split(':')[1] === 'share-type');
|
||||
}).map(function(xmlvalue) {
|
||||
return parseInt(xmlvalue.textContent || xmlvalue.text, 10);
|
||||
}).value();
|
||||
}
|
||||
|
||||
return data;
|
||||
});
|
||||
|
||||
// use delegate to catch the case with multiple file lists
|
||||
fileList.$el.on('fileActionsReady', function(ev){
|
||||
var fileList = ev.fileList;
|
||||
var $files = ev.$files;
|
||||
|
||||
function updateIcons($files) {
|
||||
if (!$files) {
|
||||
// if none specified, update all
|
||||
$files = fileList.$fileList.find('tr');
|
||||
_.each($files, function(file) {
|
||||
var $tr = $(file);
|
||||
var shareTypes = $tr.attr('data-share-types');
|
||||
if (shareTypes) {
|
||||
var hasLink = false;
|
||||
var hasShares = false;
|
||||
_.each(shareTypes.split(',') || [], function(shareType) {
|
||||
shareType = parseInt(shareType, 10);
|
||||
if (shareType === OC.Share.SHARE_TYPE_LINK) {
|
||||
hasLink = true;
|
||||
} else if (shareType === OC.Share.SHARE_TYPE_USER) {
|
||||
hasShares = true;
|
||||
} else if (shareType === OC.Share.SHARE_TYPE_GROUP) {
|
||||
hasShares = true;
|
||||
}
|
||||
});
|
||||
OCA.Sharing.Util._updateFileActionIcon($tr, hasShares, hasLink);
|
||||
}
|
||||
_.each($files, function(file) {
|
||||
var $tr = $(file);
|
||||
var shareStatus = OC.Share.statuses[$tr.data('id')];
|
||||
OCA.Sharing.Util._updateFileActionIcon($tr, !!shareStatus, shareStatus && shareStatus.link);
|
||||
});
|
||||
}
|
||||
|
||||
if (!OCA.Sharing.sharesLoaded){
|
||||
OC.Share.loadIcons('file', fileList, function() {
|
||||
// since we don't know which files are affected, just refresh them all
|
||||
updateIcons();
|
||||
});
|
||||
// assume that we got all shares, so switching directories
|
||||
// will not invalidate that list
|
||||
OCA.Sharing.sharesLoaded = true;
|
||||
}
|
||||
else{
|
||||
updateIcons($files);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
fileList.$el.on('changeDirectory', function() {
|
||||
OCA.Sharing.sharesLoaded = false;
|
||||
});
|
||||
|
|
|
@ -286,6 +286,8 @@
|
|||
// using a hash to make them unique,
|
||||
// this is only a list to be displayed
|
||||
data.recipients = {};
|
||||
// share types
|
||||
data.shareTypes = {};
|
||||
// counter is cheaper than calling _.keys().length
|
||||
data.recipientsCount = 0;
|
||||
data.mtime = file.share.stime;
|
||||
|
@ -308,6 +310,8 @@
|
|||
data.recipientsCount++;
|
||||
}
|
||||
|
||||
data.shareTypes[file.share.type] = true;
|
||||
|
||||
delete file.share;
|
||||
return memo;
|
||||
}, {})
|
||||
|
@ -324,6 +328,12 @@
|
|||
data.recipientsCount
|
||||
);
|
||||
delete data.recipientsCount;
|
||||
if (self._sharedWithUser) {
|
||||
// only for outgoing shres
|
||||
delete data.shareTypes;
|
||||
} else {
|
||||
data.shareTypes = _.keys(data.shareTypes);
|
||||
}
|
||||
})
|
||||
// Finish the chain by getting the result
|
||||
.value();
|
||||
|
|
|
@ -53,35 +53,21 @@ describe('OCA.Sharing.Util tests', function() {
|
|||
permissions: OC.PERMISSION_ALL,
|
||||
etag: 'abc',
|
||||
shareOwner: 'User One',
|
||||
isShareMountPoint: false
|
||||
isShareMountPoint: false,
|
||||
shareTypes: [OC.Share.SHARE_TYPE_USER]
|
||||
}];
|
||||
|
||||
OCA.Sharing.sharesLoaded = true;
|
||||
OC.Share.statuses = {
|
||||
1: {link: false, path: '/subdir'}
|
||||
};
|
||||
});
|
||||
afterEach(function() {
|
||||
delete OCA.Sharing.sharesLoaded;
|
||||
delete OC.Share.droppedDown;
|
||||
fileList.destroy();
|
||||
fileList = null;
|
||||
OC.Share.statuses = {};
|
||||
OC.Share.currentShares = {};
|
||||
});
|
||||
|
||||
describe('Sharing data in table row', function() {
|
||||
// TODO: test data-permissions, data-share-owner, etc
|
||||
});
|
||||
describe('Share action icon', function() {
|
||||
beforeEach(function() {
|
||||
OC.Share.statuses = {1: {link: false, path: '/subdir'}};
|
||||
OCA.Sharing.sharesLoaded = true;
|
||||
});
|
||||
afterEach(function() {
|
||||
OC.Share.statuses = {};
|
||||
OCA.Sharing.sharesLoaded = false;
|
||||
});
|
||||
it('do not shows share text when not shared', function() {
|
||||
var $action, $tr;
|
||||
OC.Share.statuses = {};
|
||||
|
@ -93,7 +79,8 @@ describe('OCA.Sharing.Util tests', function() {
|
|||
mimetype: 'httpd/unix-directory',
|
||||
size: 12,
|
||||
permissions: OC.PERMISSION_ALL,
|
||||
etag: 'abc'
|
||||
etag: 'abc',
|
||||
shareTypes: []
|
||||
}]);
|
||||
$tr = fileList.$el.find('tbody tr:first');
|
||||
$action = $tr.find('.action-share');
|
||||
|
@ -111,7 +98,8 @@ describe('OCA.Sharing.Util tests', function() {
|
|||
mimetype: 'text/plain',
|
||||
size: 12,
|
||||
permissions: OC.PERMISSION_ALL,
|
||||
etag: 'abc'
|
||||
etag: 'abc',
|
||||
shareTypes: [OC.Share.SHARE_TYPE_USER]
|
||||
}]);
|
||||
$tr = fileList.$el.find('tbody tr:first');
|
||||
$action = $tr.find('.action-share');
|
||||
|
@ -131,7 +119,8 @@ describe('OCA.Sharing.Util tests', function() {
|
|||
mimetype: 'text/plain',
|
||||
size: 12,
|
||||
permissions: OC.PERMISSION_ALL,
|
||||
etag: 'abc'
|
||||
etag: 'abc',
|
||||
shareTypes: [OC.Share.SHARE_TYPE_LINK]
|
||||
}]);
|
||||
$tr = fileList.$el.find('tbody tr:first');
|
||||
$action = $tr.find('.action-share');
|
||||
|
@ -151,7 +140,8 @@ describe('OCA.Sharing.Util tests', function() {
|
|||
size: 12,
|
||||
permissions: OC.PERMISSION_ALL,
|
||||
shareOwner: 'User One',
|
||||
etag: 'abc'
|
||||
etag: 'abc',
|
||||
shareTypes: [OC.Share.SHARE_TYPE_USER]
|
||||
}]);
|
||||
$tr = fileList.$el.find('tbody tr:first');
|
||||
$action = $tr.find('.action-share');
|
||||
|
@ -171,7 +161,8 @@ describe('OCA.Sharing.Util tests', function() {
|
|||
size: 12,
|
||||
permissions: OC.PERMISSION_ALL,
|
||||
recipientsDisplayName: 'User One, User Two',
|
||||
etag: 'abc'
|
||||
etag: 'abc',
|
||||
shareTypes: [OC.Share.SHARE_TYPE_USER]
|
||||
}]);
|
||||
$tr = fileList.$el.find('tbody tr:first');
|
||||
$action = $tr.find('.action-share');
|
||||
|
|
|
@ -190,7 +190,7 @@ trait WebDav {
|
|||
*/
|
||||
public function theSingleResponseShouldContainAPropertyWithValue($key, $expectedValue) {
|
||||
$keys = $this->response;
|
||||
if (!isset($keys[$key])) {
|
||||
if (!array_key_exists($key, $keys)) {
|
||||
throw new \Exception("Cannot find property \"$key\" with \"$expectedValue\"");
|
||||
}
|
||||
|
||||
|
@ -200,6 +200,57 @@ trait WebDav {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then the response should contain a share-types property with
|
||||
*/
|
||||
public function theResponseShouldContainAShareTypesPropertyWith($table)
|
||||
{
|
||||
$keys = $this->response;
|
||||
if (!array_key_exists('{http://owncloud.org/ns}share-types', $keys)) {
|
||||
throw new \Exception("Cannot find property \"{http://owncloud.org/ns}share-types\"");
|
||||
}
|
||||
|
||||
$foundTypes = [];
|
||||
$data = $keys['{http://owncloud.org/ns}share-types'];
|
||||
foreach ($data as $item) {
|
||||
if ($item['name'] !== '{http://owncloud.org/ns}share-type') {
|
||||
throw new \Exception('Invalid property found: "' . $item['name'] . '"');
|
||||
}
|
||||
|
||||
$foundTypes[] = $item['value'];
|
||||
}
|
||||
|
||||
foreach ($table->getRows() as $row) {
|
||||
$key = array_search($row[0], $foundTypes);
|
||||
if ($key === false) {
|
||||
throw new \Exception('Expected type ' . $row[0] . ' not found');
|
||||
}
|
||||
|
||||
unset($foundTypes[$key]);
|
||||
}
|
||||
|
||||
if ($foundTypes !== []) {
|
||||
throw new \Exception('Found more share types then specified: ' . $foundTypes);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then the response should contain an empty property :property
|
||||
* @param string $property
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function theResponseShouldContainAnEmptyProperty($property) {
|
||||
$properties = $this->response;
|
||||
if (!array_key_exists($property, $properties)) {
|
||||
throw new \Exception("Cannot find property \"$property\"");
|
||||
}
|
||||
|
||||
if ($properties[$property] !== null) {
|
||||
throw new \Exception("Property \"$property\" is not empty");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*Returns the elements of a propfind, $folderDepth requires 1 to see elements without children*/
|
||||
public function listFolder($user, $path, $folderDepth, $properties = null){
|
||||
$fullUrl = substr($this->baseUrl, 0, -4);
|
||||
|
|
|
@ -168,3 +168,76 @@ Feature: webdav-related
|
|||
When As an "user0"
|
||||
And Downloading file "/myChunkedFile.txt"
|
||||
Then Downloaded content should be "AAAAABBBBBCCCCC"
|
||||
|
||||
Scenario: A file that is not shared does not have a share-types property
|
||||
Given user "user0" exists
|
||||
And user "user0" created a folder "/test"
|
||||
When as "user0" gets properties of folder "/test" with
|
||||
|{http://owncloud.org/ns}share-types|
|
||||
Then the response should contain an empty property "{http://owncloud.org/ns}share-types"
|
||||
|
||||
Scenario: A file that is shared to a user has a share-types property
|
||||
Given user "user0" exists
|
||||
And user "user1" exists
|
||||
And user "user0" created a folder "/test"
|
||||
And as "user0" creating a share with
|
||||
| path | test |
|
||||
| shareType | 0 |
|
||||
| permissions | 31 |
|
||||
| shareWith | user1 |
|
||||
When as "user0" gets properties of folder "/test" with
|
||||
|{http://owncloud.org/ns}share-types|
|
||||
Then the response should contain a share-types property with
|
||||
| 0 |
|
||||
|
||||
Scenario: A file that is shared to a group has a share-types property
|
||||
Given user "user0" exists
|
||||
And group "group1" exists
|
||||
And user "user0" created a folder "/test"
|
||||
And as "user0" creating a share with
|
||||
| path | test |
|
||||
| shareType | 1 |
|
||||
| permissions | 31 |
|
||||
| shareWith | group1 |
|
||||
When as "user0" gets properties of folder "/test" with
|
||||
|{http://owncloud.org/ns}share-types|
|
||||
Then the response should contain a share-types property with
|
||||
| 1 |
|
||||
|
||||
Scenario: A file that is shared by link has a share-types property
|
||||
Given user "user0" exists
|
||||
And user "user0" created a folder "/test"
|
||||
And as "user0" creating a share with
|
||||
| path | test |
|
||||
| shareType | 3 |
|
||||
| permissions | 31 |
|
||||
When as "user0" gets properties of folder "/test" with
|
||||
|{http://owncloud.org/ns}share-types|
|
||||
Then the response should contain a share-types property with
|
||||
| 3 |
|
||||
|
||||
Scenario: A file that is shared by user,group and link has a share-types property
|
||||
Given user "user0" exists
|
||||
And user "user1" exists
|
||||
And group "group2" exists
|
||||
And user "user0" created a folder "/test"
|
||||
And as "user0" creating a share with
|
||||
| path | test |
|
||||
| shareType | 0 |
|
||||
| permissions | 31 |
|
||||
| shareWith | user1 |
|
||||
And as "user0" creating a share with
|
||||
| path | test |
|
||||
| shareType | 1 |
|
||||
| permissions | 31 |
|
||||
| shareWith | group2 |
|
||||
And as "user0" creating a share with
|
||||
| path | test |
|
||||
| shareType | 3 |
|
||||
| permissions | 31 |
|
||||
When as "user0" gets properties of folder "/test" with
|
||||
|{http://owncloud.org/ns}share-types|
|
||||
Then the response should contain a share-types property with
|
||||
| 0 |
|
||||
| 1 |
|
||||
| 3 |
|
||||
|
|
Loading…
Reference in New Issue