Skip to content

Commit

Permalink
Merge pull request #27 from owncloud/report-pagination
Browse files Browse the repository at this point in the history
Add pagination/search through REPORT
  • Loading branch information
Vincent Petry authored Mar 1, 2017
2 parents 3d9330b + fdab5a5 commit 3f66a22
Show file tree
Hide file tree
Showing 12 changed files with 713 additions and 104 deletions.
2 changes: 1 addition & 1 deletion lib/CustomGroupsBackend.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ public function getUserGroups($uid) {
* @return array an array of group names
*/
public function getGroups($search = '', $limit = -1, $offset = 0) {
$groups = $this->handler->searchGroups($search, $limit, $offset);
$groups = $this->handler->searchGroups(new Search($search, $offset, $limit));
return array_map(function ($groupInfo) {
return $this->formatGroupId($groupInfo['group_id']);
}, $groups);
Expand Down
73 changes: 45 additions & 28 deletions lib/CustomGroupsDatabaseHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

use OCP\IDBConnection;
use OCP\ILogger;
use OCP\DB\QueryBuilder\IQueryBuilder;

/**
* Database handler for custom groups
Expand Down Expand Up @@ -84,11 +85,11 @@ public function inGroup($uid, $numericGroupId) {
* Get all group memberships of the given user
*
* @param string $uid Name of the user
* @param null|int $roleFilter optional role filter
* @param Search $search search
* @return array an array of member info
* @throws \Doctrine\DBAL\Exception\DriverException in case of database exception
*/
public function getUserMemberships($uid, $roleFilter = null) {
public function getUserMemberships($uid, $search = null) {
$qb = $this->dbConn->getQueryBuilder();
$qb->select('m.group_id', 'm.user_id', 'm.role', 'g.uri', 'g.display_name')
->from('custom_group_member', 'm')
Expand All @@ -97,9 +98,7 @@ public function getUserMemberships($uid, $roleFilter = null) {
->andWhere($qb->expr()->eq('user_id', $qb->createNamedParameter($uid)))
->orderBy('m.group_id', 'ASC');

if (!is_null($roleFilter)) {
$qb->andWhere($qb->expr()->eq('role', $qb->createNamedParameter($roleFilter)));
}
$this->applySearch($qb, $search, 'display_name');

$cursor = $qb->execute();

Expand All @@ -120,30 +119,17 @@ public function getUserMemberships($uid, $roleFilter = null) {
* search string can appear anywhere within the display name
* field.
*
* @param string $search search string
* @param int $limit limit or -1 to disable
* @param int $offset offset
* @param Search $search search
* @return array an array of group info
* @throws \Doctrine\DBAL\Exception\DriverException in case of database exception
*/
public function searchGroups($search = '', $limit = -1, $offset = 0) {
public function searchGroups($search = null) {
$qb = $this->dbConn->getQueryBuilder();
$qb->select(['group_id', 'uri', 'display_name'])
->from('custom_group')
->orderBy('display_name', 'ASC');

if ($search !== '') {
$likeString = '%' . $this->dbConn->escapeLikeParameter(strtolower($search)) . '%';
$qb->where($qb->expr()->like($qb->createFunction('LOWER(`display_name`)'), $qb->createNamedParameter($likeString)));
}

if ($limit > 0) {
$qb->setMaxResults($limit);
}

if ($offset !== 0) {
$qb->setFirstResult($offset);
}
$this->applySearch($qb, $search, 'display_name');

$cursor = $qb->execute();
$groups = $cursor->fetchAll();
Expand Down Expand Up @@ -200,11 +186,13 @@ private function getGroupBy($field, $numericGroupId) {
/**
* Returns the info for all groups.
*
* @param Search $search search
*
* @return array array of group info
* @throws \Doctrine\DBAL\Exception\DriverException in case of database exception
*/
public function getGroups() {
return $this->searchGroups();
public function getGroups($search = null) {
return $this->searchGroups($search);
}

/**
Expand Down Expand Up @@ -322,21 +310,20 @@ public function removeFromGroup($uid, $gid) {
* Returns the group members
*
* @param int $gid numeric group id
* @param null|bool $roleFilter optional role filter, set to true or false to
* filter by non-admin-only or admin-only
* @param Search $search search
* @return array array of member info
* @throws \Doctrine\DBAL\Exception\DriverException in case of database exception
*/
public function getGroupMembers($gid, $roleFilter = null) {
public function getGroupMembers($gid, $search = null) {
$qb = $this->dbConn->getQueryBuilder();
$qb->select(['user_id', 'group_id', 'role'])
->from('custom_group_member')
->where($qb->expr()->eq('group_id', $qb->createNamedParameter($gid)))
->orderBy('user_id', 'ASC');

if (!is_null($roleFilter)) {
$qb->andWhere($qb->expr()->eq('role', $qb->createNamedParameter($roleFilter)));
}
// TODO: also by display name
$this->applySearch($qb, $search, 'user_id');

$cursor = $qb->execute();

Expand Down Expand Up @@ -409,4 +396,34 @@ private function formatMemberInfo(array $row) {
'role' => (int)$row['role'],
];
}

/**
* Apply search to the given query
*
* @param IQueryBuilder $qb query builder
* @param Search $search search
* @param string $property property to apply search on
* @return IQueryBuilder query builder
*/
private function applySearch(IQueryBuilder $qb, $search, $property = null) {
if ($search !== null) {
if ($search->getPattern() !== null && $property !== null) {
$likeString = '%' . $this->dbConn->escapeLikeParameter(strtolower($search->getPattern())) . '%';
$qb->andWhere($qb->expr()->like($qb->createFunction('LOWER(`' . $property . '`)'), $qb->createNamedParameter($likeString)));
}

if ($search->getLimit() !== null) {
$qb->setMaxResults($search->getLimit());
}

if ($search->getOffset() !== null) {
$qb->setFirstResult($search->getOffset());
}

if ($search->getRoleFilter() !== null) {
$qb->andWhere($qb->expr()->eq('role', $qb->createNamedParameter($search->getRoleFilter())));
}
}
return $qb;
}
}
88 changes: 28 additions & 60 deletions lib/Dav/CustomGroupsPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@

use OCA\CustomGroups\CustomGroupsDatabaseHandler;
use OCP\IUserSession;
use OCA\CustomGroups\Search;
use OCA\CustomGroups\Dav\ReportRequest;
use OCA\CustomGroups\Dav\RootCollection;
use OCA\CustomGroups\Dav\MembershipNode;
use OCA\CustomGroups\Dav\GroupsCollection;
Expand All @@ -31,20 +33,14 @@
use Sabre\DAV\Exception\BadRequest;
use Sabre\DAV\Xml\Element\Response;
use Sabre\DAV\Xml\Response\MultiStatus;
use Sabre\DAV\PropFind;
/**
* Sabre plugin to handle custom groups
*/
class CustomGroupsPlugin extends ServerPlugin {
const NS_OWNCLOUD = 'http://owncloud.org/ns';

const REPORT_NAME = '{http://owncloud.org/ns}filter-members';

/**
* Custom groups handler
*
* @var CustomGroupsDatabaseHandler
*/
protected $groupsHandler;
const REPORT_NAME = '{http://owncloud.org/ns}search-query';

/**
* Sabre server
Expand All @@ -63,11 +59,9 @@ class CustomGroupsPlugin extends ServerPlugin {
/**
* Custom groups plugin
*
* @param CustomGroupsDatabaseHandler $groupsHandler custom groups handler
* @param IUserSession $userSession user session
*/
public function __construct(CustomGroupsDatabaseHandler $groupsHandler, IUserSession $userSession) {
$this->groupsHandler = $groupsHandler;
public function __construct(IUserSession $userSession) {
$this->userSession = $userSession;
}

Expand Down Expand Up @@ -101,6 +95,8 @@ public function initialize(\Sabre\DAV\Server $server) {
$this->server->protectedProperties[] = $ns . 'user-id';
$this->server->protectedProperties[] = $ns . 'group-uri';

$this->server->xml->elementMap[self::REPORT_NAME] = ReportRequest::class;

$this->server->on('report', [$this, 'onReport']);
}

Expand All @@ -114,77 +110,49 @@ public function initialize(\Sabre\DAV\Server $server) {
*/
public function getSupportedReportSet($uri) {
$node = $this->server->tree->getNodeForPath($uri);
if (!$node instanceof RootCollection) {
if ($this->isSupportedNode($node)) {
return [self::REPORT_NAME];
}
return [];
}

private function isSupportedNode($node) {
return (
$node instanceof GroupsCollection
|| $node instanceof GroupMembershipCollection
);
}

/**
* REPORT operations to look for comments
*
* @param string $reportName report name
* @param array $report report data
* @param ReportRequest $report report data
* @param string $uri URI
* @return bool true if processed
* @throws BadRequest if missing properties
*/
public function onReport($reportName, $report, $uri) {
$node = $this->server->tree->getNodeForPath($uri);
if (!$node instanceof RootCollection || $reportName !== self::REPORT_NAME) {
if (!$this->isSupportedNode($node) || $reportName !== self::REPORT_NAME) {
return;
}

$requestedProps = [];
$filterRules = [];
$results = $node->search($report->getSearch());

$ns = '{' . self::NS_OWNCLOUD . '}';
foreach ($report as $reportProps) {
$name = $reportProps['name'];
if ($name === $ns . 'filter-rules') {
$filterRules = $reportProps['value'];
} else if ($name === '{DAV:}prop') {
// propfind properties
foreach ($reportProps['value'] as $propVal) {
$requestedProps[] = $propVal['name'];
}
}
}

$filterUserId = null;
$filterAdminFlag = null;
foreach ($filterRules as $filterRule) {
if ($filterRule['name'] === $ns . 'user-id') {
$filterUserId = $filterRule['value'];
} else if ($filterRule['name'] === $ns . 'role') {
$filterAdminFlag = $filterRule['value'];
}
}

if (is_null($filterUserId)) {
// an empty filter would return all existing users which would be useless
throw new BadRequest('Missing user-id property');
}
$responses = [];
$nodeProps = [];
foreach ($results as $result) {
$nodePath = $this->server->getRequestUri() . '/' . $result->getName();
$propFind = new PropFind($nodePath, $report->getProperties());
$this->server->getPropertiesByNode($propFind, $result);

$memberInfos = $this->groupsHandler->getUserMemberships($filterUserId, $filterAdminFlag);
$resultSet = $propFind->getResultForMultiStatus();
$resultSet['href'] = $nodePath;

$responses = [];
foreach ($memberInfos as $memberInfo) {
$node = new CustomGroupMemberNode($memberInfo, $this->groupsHandler, $this->userSession);
$uri = $memberInfo['uri'];
$nodePath = $this->server->getRequestUri() . '/' . $uri . '/' . $node->getName();
$resultSet = $node->getProperties($requestedProps);
$responses[] = new Response(
$this->server->getBaseUri() . $nodePath,
[200 => $resultSet],
200
);
$nodeProps[] = $resultSet;
}

$xml = $this->server->xml->write(
'{DAV:}multistatus',
new MultiStatus($responses)
);
$xml = $this->server->generateMultiStatus($nodeProps);

$this->server->httpResponse->setStatus(207);
$this->server->httpResponse->setHeader('Content-Type', 'application/xml; charset=utf-8');
Expand Down
7 changes: 6 additions & 1 deletion lib/Dav/GroupMembershipCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
use Sabre\DAV\Exception\Forbidden;
use Sabre\DAV\Exception\PreconditionFailed;
use OCA\CustomGroups\Dav\Roles;
use OCA\CustomGroups\Search;

/**
* Group memberships collection for a given group
Expand Down Expand Up @@ -214,12 +215,16 @@ public function getChild($userId) {
* @throws Forbidden if the current user has insufficient permissions
*/
public function getChildren() {
return $this->search();
}

public function search(Search $search = null) {
$groupId = $this->groupInfo['group_id'];
if (!$this->helper->isUserMember($groupId)
&& !$this->helper->isUserAdmin($groupId)) {
throw new Forbidden("No permission to list members of group \"$groupId\"");
}
$members = $this->groupsHandler->getGroupMembers($groupId);
$members = $this->groupsHandler->getGroupMembers($groupId, $search);
return array_map(function ($memberInfo) {
return $this->createCustomGroupMemberNode($memberInfo);
}, $members);
Expand Down
17 changes: 14 additions & 3 deletions lib/Dav/GroupsCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@
namespace OCA\CustomGroups\Dav;

use Sabre\DAV\ICollection;
use OCA\CustomGroups\CustomGroupsDatabaseHandler;
use Sabre\DAV\Exception\NotFound;
use Sabre\DAV\Exception\MethodNotAllowed;

use OCA\CustomGroups\CustomGroupsDatabaseHandler;
use OCA\CustomGroups\Search;

/**
* Collection of custom groups
*/
Expand Down Expand Up @@ -116,10 +118,19 @@ public function getChild($name) {
* @return GroupMembershipCollection[] custom group nodes
*/
public function getChildren() {
return $this->search();
}

/**
* Search nodes
*
* @param Search $search search
*/
public function search(Search $search = null) {
if ($this->userId !== null) {
$allGroups = $this->groupsHandler->getUserMemberships($this->userId);
$allGroups = $this->groupsHandler->getUserMemberships($this->userId, $search);
} else {
$allGroups = $this->groupsHandler->getGroups();
$allGroups = $this->groupsHandler->getGroups($search);
}
return array_map(function ($groupInfo) {
return $this->createMembershipsCollection($groupInfo);
Expand Down
Loading

0 comments on commit 3f66a22

Please sign in to comment.