Skip to content

Commit 0b43213

Browse files
Merge pull request #4273 from nextcloud/backport/4263/stable31
2 parents 67f6177 + b55780b commit 0b43213

File tree

7 files changed

+80
-82
lines changed

7 files changed

+80
-82
lines changed

β€Žlib/Controller/FolderController.phpβ€Ž

Lines changed: 21 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,6 @@
3939
class FolderController extends OCSController {
4040
private ?IUser $user;
4141

42-
protected const ALLOWED_ORDER_BY = [
43-
'mount_point',
44-
'quota',
45-
'groups',
46-
'acl',
47-
];
48-
4942
public function __construct(
5043
string $AppName,
5144
IRequest $request,
@@ -102,7 +95,8 @@ private function formatFolder(array $folder): array {
10295
* @param bool $applicable Filter by applicable groups
10396
* @param non-negative-int $offset Number of items to skip.
10497
* @param ?positive-int $limit Number of items to return.
105-
* @param null|'mount_point'|'quota'|'groups'|'acl' $orderBy The key to order by
98+
* @param 'mount_point'|'quota'|'groups'|'acl' $orderBy The key to order by
99+
* @param 'asc'|'desc' $order Sort ascending or descending
106100
* @return DataResponse<Http::STATUS_OK, array<string, GroupFoldersFolder>, array{}>
107101
* @throws OCSNotFoundException Storage not found
108102
* @throws OCSBadRequestException Wrong limit used
@@ -111,58 +105,39 @@ private function formatFolder(array $folder): array {
111105
*/
112106
#[NoAdminRequired]
113107
#[FrontpageRoute(verb: 'GET', url: '/folders')]
114-
public function getFolders(bool $applicable = false, int $offset = 0, ?int $limit = null, ?string $orderBy = 'mount_point'): DataResponse {
108+
public function getFolders(bool $applicable = false, int $offset = 0, ?int $limit = null, string $orderBy = 'mount_point', string $order = 'asc'): DataResponse {
109+
/** @psalm-suppress DocblockTypeContradiction */
115110
if ($limit !== null && $limit <= 0) {
116111
throw new OCSBadRequestException('The limit must be greater than 0.');
117112
}
118113

114+
/** @psalm-suppress DocblockTypeContradiction */
115+
if (!in_array($orderBy, ['mount_point', 'quota', 'groups', 'acl'], true)) {
116+
throw new OCSBadRequestException('The orderBy is not allowed.');
117+
}
118+
119+
/** @psalm-suppress DocblockTypeContradiction */
120+
if (!in_array($order, ['asc', 'desc'], true)) {
121+
throw new OCSBadRequestException('The order is not allowed.');
122+
}
123+
119124
$storageId = $this->getRootFolderStorageId();
120125
if ($storageId === null) {
121126
throw new OCSNotFoundException();
122127
}
123128

124129
$folders = [];
125-
foreach ($this->manager->getAllFoldersWithSize($storageId) as $id => $folder) {
130+
$i = 0;
131+
foreach ($this->manager->getAllFoldersWithSize($storageId, $offset, $limit, $orderBy, $order) as $id => $folder) {
126132
// Make them string-indexed for OpenAPI JSON output
127-
$folders[(string)$id] = $this->formatFolder($folder);
128-
}
129-
130-
$orderBy = in_array($orderBy, self::ALLOWED_ORDER_BY, true)
131-
? $orderBy
132-
: 'mount_point';
133-
134-
// in case of equal orderBy value always fall back to the mount_point - same as on the frontend
135-
/**
136-
* @var GroupFoldersFolder $a
137-
* @var GroupFoldersFolder $b
138-
*/
139-
uasort($folders, function (array $a, array $b) use ($orderBy) {
140-
if ($orderBy === 'groups') {
141-
if (($value = count($a['groups']) - count($b['groups'])) !== 0) {
142-
return $value;
143-
}
144-
} else {
145-
if (($value = $this->compareFolderNames((string)($a[$orderBy] ?? ''), (string)($b[$orderBy] ?? ''))) !== 0) {
146-
return $value;
147-
}
148-
}
149-
150-
// fallback to mount_point
151-
if (($value = $this->compareFolderNames($a['mount_point'] ?? '', $b['mount_point'])) !== 0) {
152-
return $value;
153-
}
154-
155-
// fallback to id
156-
return $a['id'] - $b['id'];
157-
});
133+
// JavaScript doesn't preserve JSON object key orders, so we need to manually add this information.
134+
$folders[(string)$id] = array_merge($this->formatFolder($folder), [
135+
'sortIndex' => $offset + $i++,
136+
]);
137+
}
158138

159139
$isAdmin = $this->delegationService->isAdminNextcloud() || $this->delegationService->isDelegatedAdmin();
160140
if ($isAdmin && !$applicable) {
161-
// If only the default values are provided the pagination can be skipped.
162-
if ($offset !== 0 || $limit !== null) {
163-
$folders = array_slice($folders, $offset, $limit, true);
164-
}
165-
166141
return new DataResponse($folders);
167142
}
168143

@@ -174,11 +149,6 @@ public function getFolders(bool $applicable = false, int $offset = 0, ?int $limi
174149
$folders = array_filter(array_map($this->filterNonAdminFolder(...), $folders));
175150
}
176151

177-
// If only the default values are provided the pagination can be skipped.
178-
if ($offset !== 0 || $limit !== null) {
179-
$folders = array_slice($folders, $offset, $limit, true);
180-
}
181-
182152
return new DataResponse($folders);
183153
}
184154

β€Žlib/Folder/FolderManager.phpβ€Ž

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,15 +142,30 @@ private function joinQueryWithFileCache(IQueryBuilder $query, int $rootStorageId
142142
* @return array<int, InternalFolderOut>
143143
* @throws Exception
144144
*/
145-
public function getAllFoldersWithSize(int $rootStorageId): array {
145+
public function getAllFoldersWithSize(int $rootStorageId, int $offset = 0, ?int $limit = null, string $orderBy = 'mount_point', string $order = 'ASC'): array {
146146
$applicableMap = $this->getAllApplicable();
147147

148148
$query = $this->connection->getQueryBuilder();
149149

150-
$query->select('folder_id', 'mount_point', 'quota', 'c.size', 'acl')
150+
$query->select('f.folder_id', 'mount_point', 'quota', 'c.size', 'acl')
151151
->from('group_folders', 'f');
152152
$this->joinQueryWithFileCache($query, $rootStorageId);
153153

154+
$query->setFirstResult($offset);
155+
$query->setMaxResults($limit);
156+
if ($orderBy === 'groups') {
157+
$query
158+
->leftJoin('f', 'group_folders_groups', 'g', $query->expr()->eq('f.folder_id', 'g.folder_id'))
159+
->groupBy('f.folder_id')
160+
->orderBy($query->func()->count('g.applicable_id'), $order);
161+
} else {
162+
$query->orderBy($orderBy, $order);
163+
}
164+
// Fallback in case two rows are the same after ordering by the $orderBy
165+
if ($orderBy !== 'mount_point') {
166+
$query->addOrderBy('mount_point', 'ASC');
167+
}
168+
154169
$rows = $query->executeQuery()->fetchAll();
155170

156171
$folderMappings = $this->getAllFolderMappings();

β€Žlib/ResponseDefinitions.phpβ€Ž

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
* size: int,
5555
* acl: bool,
5656
* manage: list<GroupFoldersAclManage>,
57+
* sortIndex?: int,
5758
* }
5859
*/
5960
class ResponseDefinitions {

β€Žopenapi.jsonβ€Ž

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,10 @@
182182
"items": {
183183
"$ref": "#/components/schemas/AclManage"
184184
}
185+
},
186+
"sortIndex": {
187+
"type": "integer",
188+
"format": "int64"
185189
}
186190
}
187191
},
@@ -500,7 +504,6 @@
500504
"description": "The key to order by",
501505
"schema": {
502506
"type": "string",
503-
"nullable": true,
504507
"default": "mount_point",
505508
"enum": [
506509
"mount_point",
@@ -510,6 +513,19 @@
510513
]
511514
}
512515
},
516+
{
517+
"name": "order",
518+
"in": "query",
519+
"description": "Sort ascending or descending",
520+
"schema": {
521+
"type": "string",
522+
"default": "asc",
523+
"enum": [
524+
"asc",
525+
"desc"
526+
]
527+
}
528+
},
513529
{
514530
"name": "OCS-APIRequest",
515531
"in": "header",

β€Žsrc/settings/Api.tsβ€Ž

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,13 @@ export class Api {
1515
return generateUrl(`apps/groupfolders/${endpoint}`)
1616
}
1717

18-
async listFolders(offset = 0, limit?: number, orderBy?: string): Promise<Folder[]> {
18+
async listFolders(offset = 0, limit?: number, orderBy?: string, order?: string): Promise<Folder[]> {
1919
const response = await axios.get<OCSResponse<Folder[]>>(this.getUrl('folders'), {
2020
params: {
2121
offset,
2222
limit,
2323
orderBy,
24+
order,
2425
},
2526
})
2627
return Object.values(response.data.ocs.data)

β€Žsrc/settings/App.tsxβ€Ž

Lines changed: 18 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ export class App extends Component<unknown, AppState> implements OC.Plugin<OC.Se
7474

7575
componentDidMount() {
7676
// list first pageSize + 1 folders so we know if there are more pages
77-
this.api.listFolders(0, pageSize + 1, this.state.sort).then((folders) => {
77+
this.api.listFolders(0, pageSize + 1, this.state.sort, this.state.sortOrder === 1 ? 'asc' : 'desc').then((folders) => {
7878
this.setState({ folders })
7979
})
8080
this.api.listGroups().then((groups) => {
@@ -188,11 +188,25 @@ export class App extends Component<unknown, AppState> implements OC.Plugin<OC.Se
188188
}
189189

190190
onSortClick = (sort: SortKey) => {
191+
let sortOrder = this.state.sortOrder
191192
if (this.state.sort === sort) {
192-
this.setState({ sortOrder: -this.state.sortOrder })
193+
sortOrder = -sortOrder
193194
} else {
194-
this.setState({ sortOrder: 1, sort })
195+
sortOrder = 1
195196
}
197+
198+
this.setState({
199+
sortOrder,
200+
sort,
201+
})
202+
203+
// Reset ordering and go back to the first page
204+
this.api.listFolders(0, pageSize + 1, sort, sortOrder === 1 ? 'asc' : 'desc').then((folders) => {
205+
this.setState({
206+
folders,
207+
currentPage: 0,
208+
})
209+
})
196210
}
197211

198212
static supportACL(): boolean {
@@ -233,30 +247,7 @@ export class App extends Component<unknown, AppState> implements OC.Plugin<OC.Se
233247
}
234248
return folder.mount_point.toLowerCase().includes(this.state.filter.toLowerCase())
235249
})
236-
.sort((a, b) => {
237-
switch (this.state.sort) {
238-
case 'mount_point':
239-
return a.mount_point.localeCompare(b.mount_point) * this.state.sortOrder
240-
case 'quota':
241-
if (a.quota < 0 && b.quota >= 0) {
242-
return this.state.sortOrder
243-
}
244-
if (b.quota < 0 && a.quota >= 0) {
245-
return -this.state.sortOrder
246-
}
247-
return (a.quota - b.quota) * this.state.sortOrder
248-
case 'groups':
249-
return (Object.keys(a.groups).length - Object.keys(b.groups).length) * this.state.sortOrder
250-
case 'acl':
251-
if (a.acl && !b.acl) {
252-
return this.state.sortOrder
253-
}
254-
if (!a.acl && b.acl) {
255-
return -this.state.sortOrder
256-
}
257-
}
258-
return 0
259-
})
250+
.sort((a, b) => a.sortIndex! - b.sortIndex!)
260251
.slice(this.state.currentPage * pageSize, this.state.currentPage * pageSize + pageSize)
261252
.map(folder => {
262253
const id = folder.id

β€Žsrc/types/openapi/openapi.tsβ€Ž

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,8 @@ export type components = {
293293
size: number;
294294
acl: boolean;
295295
manage: components["schemas"]["AclManage"][];
296+
/** Format: int64 */
297+
sortIndex?: number;
296298
};
297299
Group: {
298300
gid: string;
@@ -416,6 +418,8 @@ export interface operations {
416418
limit?: number | null;
417419
/** @description The key to order by */
418420
orderBy?: "mount_point" | "quota" | "groups" | "acl";
421+
/** @description Sort ascending or descending */
422+
order?: "asc" | "desc";
419423
};
420424
header: {
421425
/** @description Required to be true for the API request to pass */

0 commit comments

Comments
Β (0)