Skip to content

Commit a02d8a2

Browse files
authored
Merge pull request #6215 from WoltLab/6.2-UserGroupListPage-to-grid-view
Migrate `UserGroupListPage` to grid view
2 parents 06f5ca6 + 09c66e7 commit a02d8a2

File tree

10 files changed

+300
-170
lines changed

10 files changed

+300
-170
lines changed

wcfsetup/install/files/acp/templates/userGroupList.tpl

Lines changed: 3 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<header class="contentHeader">
44
<div class="contentHeaderTitle">
5-
<h1 class="contentTitle">{lang}wcf.acp.group.list{/lang} <span class="badge badgeInverse">{#$items}</span></h1>
5+
<h1 class="contentTitle">{lang}wcf.acp.group.list{/lang} <span class="badge badgeInverse">{#$gridView->countRows()}</span></h1>
66
</div>
77

88
{hascontent}
@@ -20,96 +20,8 @@
2020
{/hascontent}
2121
</header>
2222

23-
{hascontent}
24-
<div class="paginationTop">
25-
{content}{pages print=true assign=pagesLinks controller="UserGroupList" link="pageNo=%d&sortField=$sortField&sortOrder=$sortOrder"}{/content}
26-
</div>
27-
{/hascontent}
28-
29-
<div class="section tabularBox">
30-
<table class="table jsObjectActionContainer" data-object-action-class-name="wcf\data\user\group\UserGroupAction">
31-
<thead>
32-
<tr>
33-
<th class="columnID columnGroupID{if $sortField == 'groupID'} active {@$sortOrder}{/if}" colspan="2"><a href="{link controller='UserGroupList'}pageNo={@$pageNo}&sortField=groupID&sortOrder={if $sortField == 'groupID' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.global.objectID{/lang}</a></th>
34-
<th class="columnTitle columnGroupName{if $sortField == 'groupNameI18n'} active {@$sortOrder}{/if}"><a href="{link controller='UserGroupList'}pageNo={@$pageNo}&sortField=groupNameI18n&sortOrder={if $sortField == 'groupNameI18n' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.global.name{/lang}</a></th>
35-
<th class="columnDigits columnMembers{if $sortField == 'members'} active {@$sortOrder}{/if}"><a href="{link controller='UserGroupList'}pageNo={@$pageNo}&sortField=members&sortOrder={if $sortField == 'members' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.acp.group.members{/lang}</a></th>
36-
<th class="columnDigits columnPriority{if $sortField == 'priority'} active {@$sortOrder}{/if}"><a href="{link controller='UserGroupList'}pageNo={@$pageNo}&sortField=priority&sortOrder={if $sortField == 'priority' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.acp.group.priority{/lang}</a></th>
37-
38-
{event name='columnHeads'}
39-
</tr>
40-
</thead>
41-
42-
<tbody class="jsReloadPageWhenEmpty">
43-
{foreach from=$objects item=group}
44-
<tr id="groupContainer{@$group->groupID}" class="jsUserGroupRow jsObjectActionObject" data-object-id="{@$group->getObjectID()}">
45-
<td class="columnIcon">
46-
{if $group->isEditable()}
47-
<a href="{link controller='UserGroupEdit' id=$group->groupID}{/link}" title="{lang}wcf.global.button.edit{/lang}" class="jsTooltip">{icon name='pencil'}</a>
48-
{else}
49-
<span class="disabled" title="{lang}wcf.global.button.edit{/lang}">
50-
{icon name='pencil'}
51-
</span>
52-
{/if}
53-
{if $group->isDeletable()}
54-
{objectAction action="delete" objectTitle=$group->getTitle()}
55-
{else}
56-
<span class="disabled" title="{lang}wcf.global.button.delete{/lang}">
57-
{icon name='xmark'}
58-
</span>
59-
{/if}
60-
61-
{event name='rowButtons'}
62-
</td>
63-
<td class="columnID columnGroupID">{@$group->groupID}</td>
64-
<td class="columnTitle columnGroupName">
65-
{if $group->isEditable()}
66-
<a title="{lang}wcf.acp.group.edit{/lang}" href="{link controller='UserGroupEdit' id=$group->groupID}{/link}">{$group->getTitle()}</a>
67-
{else}
68-
{$group->getTitle()}
69-
{/if}
70-
{if $group->isOwner()}
71-
<span class="jsTooltip" title="{lang}wcf.acp.group.type.owner{/lang}">
72-
{icon name='shield-halved'}
73-
</span>
74-
{/if}
75-
</td>
76-
<td class="columnDigits columnMembers">
77-
{if $group->groupType == 1 ||$group->groupType == 2}
78-
{* dont't show search links for the everybody and the guest user group *}
79-
{#$group->members}
80-
{else}
81-
<a class="jsTooltip" title="{lang}wcf.acp.group.showMembers{/lang}" href="{link controller='UserSearch'}groupID={@$group->groupID}{/link}">{#$group->members}</a>
82-
{/if}
83-
</td>
84-
<td class="columnDigits columnPriority">{#$group->priority}</td>
85-
86-
{event name='columns'}
87-
</tr>
88-
{/foreach}
89-
</tbody>
90-
</table>
23+
<div class="section">
24+
{unsafe:$gridView->render()}
9125
</div>
9226

93-
<footer class="contentFooter">
94-
{hascontent}
95-
<div class="paginationBottom">
96-
{content}{@$pagesLinks}{/content}
97-
</div>
98-
{/hascontent}
99-
100-
{hascontent}
101-
<nav class="contentFooterNavigation">
102-
<ul>
103-
{content}
104-
{if $__wcf->getSession()->getPermission('admin.user.canAddGroup')}
105-
<li><a href="{link controller='UserGroupAdd'}{/link}" class="button">{icon name='plus'} <span>{lang}wcf.acp.group.add{/lang}</span></a></li>
106-
{/if}
107-
108-
{event name='contentFooterNavigation'}
109-
{/content}
110-
</ul>
111-
</nav>
112-
{/hascontent}
113-
</footer>
114-
11527
{include file='footer'}

wcfsetup/install/files/lib/acp/page/UserGroupListPage.class.php

Lines changed: 11 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,20 @@
22

33
namespace wcf\acp\page;
44

5-
use wcf\data\user\group\I18nUserGroupList;
6-
use wcf\page\SortablePage;
7-
use wcf\system\WCF;
5+
use wcf\page\AbstractGridViewPage;
6+
use wcf\system\gridView\AbstractGridView;
7+
use wcf\system\gridView\admin\UserGroupGridView;
88

99
/**
1010
* Shows a list of all user groups.
1111
*
12-
* @author Marcel Werk
13-
* @copyright 2001-2019 WoltLab GmbH
14-
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
12+
* @author Olaf Braun, Marcel Werk
13+
* @copyright 2001-2025 WoltLab GmbH
14+
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
1515
*
16-
* @property I18nUserGroupList $objectList
16+
* @property UserGroupGridView $gridView
1717
*/
18-
class UserGroupListPage extends SortablePage
18+
class UserGroupListPage extends AbstractGridViewPage
1919
{
2020
/**
2121
* @inheritDoc
@@ -27,76 +27,9 @@ class UserGroupListPage extends SortablePage
2727
*/
2828
public $neededPermissions = ['admin.user.canEditGroup', 'admin.user.canDeleteGroup'];
2929

30-
/**
31-
* @inheritDoc
32-
*/
33-
public $defaultSortField = 'groupNameI18n';
34-
35-
/**
36-
* @inheritDoc
37-
*/
38-
public $validSortFields = ['groupID', 'groupNameI18n', 'groupType', 'members', 'priority'];
39-
40-
/**
41-
* @inheritDoc
42-
*/
43-
public $objectListClassName = I18nUserGroupList::class;
44-
45-
/**
46-
* indicates if a group has just been deleted
47-
* @var int
48-
*/
49-
public $deletedGroups = 0;
50-
51-
/**
52-
* @inheritDoc
53-
*/
54-
public function readParameters()
55-
{
56-
parent::readParameters();
57-
58-
// detect group deletion
59-
if (isset($_REQUEST['deletedGroups'])) {
60-
$this->deletedGroups = \intval($_REQUEST['deletedGroups']);
61-
}
62-
}
63-
64-
/**
65-
* @inheritDoc
66-
*/
67-
protected function initObjectList()
68-
{
69-
parent::initObjectList();
70-
71-
if (!empty($this->objectList->sqlSelects)) {
72-
$this->objectList->sqlSelects .= ',';
73-
}
74-
$this->objectList->sqlSelects .= "(
75-
SELECT COUNT(*)
76-
FROM wcf1_user_to_group
77-
WHERE groupID = user_group.groupID
78-
) AS members";
79-
}
80-
81-
/**
82-
* @inheritDoc
83-
*/
84-
protected function readObjects()
85-
{
86-
$this->sqlOrderBy = (($this->sortField != 'members' && $this->sortField != 'groupNameI18n') ? 'user_group.' : '') . $this->sortField . " " . $this->sortOrder;
87-
88-
parent::readObjects();
89-
}
90-
91-
/**
92-
* @inheritDoc
93-
*/
94-
public function assignVariables()
30+
#[\Override]
31+
protected function createGridViewController(): AbstractGridView
9532
{
96-
parent::assignVariables();
97-
98-
WCF::getTPL()->assign([
99-
'deletedGroups' => $this->deletedGroups,
100-
]);
33+
return new UserGroupGridView();
10134
}
10235
}

wcfsetup/install/files/lib/bootstrap/com.woltlab.wcf.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ static function (\wcf\event\endpoint\ControllerCollecting $event) {
202202
$event->register(new \wcf\system\endpoint\controller\core\users\groups\assignment\DeleteAssignment());
203203
$event->register(new \wcf\system\endpoint\controller\core\users\groups\assignment\EnableAssignment());
204204
$event->register(new \wcf\system\endpoint\controller\core\users\groups\assignment\DisableAssignment());
205+
$event->register(new \wcf\system\endpoint\controller\core\users\groups\DeleteGroup());
205206
}
206207
);
207208

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
namespace wcf\event\gridView\admin;
4+
5+
use wcf\event\IPsr14Event;
6+
use wcf\system\gridView\admin\UserGroupGridView;
7+
8+
/**
9+
* Indicates that the user group grid view has been initialized.
10+
*
11+
* @author Olaf Braun
12+
* @copyright 2001-2025 WoltLab GmbH
13+
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
14+
* @since 6.2
15+
*/
16+
final class UserGroupGridViewInitialized implements IPsr14Event
17+
{
18+
public function __construct(public readonly UserGroupGridView $param)
19+
{
20+
}
21+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
namespace wcf\event\interaction\admin;
4+
5+
use wcf\event\IPsr14Event;
6+
use wcf\system\interaction\admin\UserGroupInteractions;
7+
8+
/**
9+
* Indicates that the provider for user group interactions is collecting interactions.
10+
*
11+
* @author Olaf Braun
12+
* @copyright 2001-2025 WoltLab GmbH
13+
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
14+
* @since 6.2
15+
*/
16+
final class UserGroupInteractionCollecting implements IPsr14Event
17+
{
18+
public function __construct(public readonly UserGroupInteractions $param)
19+
{
20+
}
21+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
namespace wcf\system\endpoint\controller\core\users\groups;
4+
5+
use Laminas\Diactoros\Response\JsonResponse;
6+
use Psr\Http\Message\ResponseInterface;
7+
use Psr\Http\Message\ServerRequestInterface;
8+
use wcf\data\user\group\UserGroup;
9+
use wcf\data\user\group\UserGroupAction;
10+
use wcf\http\Helper;
11+
use wcf\system\endpoint\DeleteRequest;
12+
use wcf\system\endpoint\IController;
13+
use wcf\system\exception\PermissionDeniedException;
14+
15+
/**
16+
* API endpoint for deleting user groups.
17+
*
18+
* @author Olaf Braun
19+
* @copyright 2001-2025 WoltLab GmbH
20+
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
21+
* @since 6.2
22+
*/
23+
#[DeleteRequest("/core/users/groups/{id:\d+}")]
24+
final class DeleteGroup implements IController
25+
{
26+
#[\Override]
27+
public function __invoke(ServerRequestInterface $request, array $variables): ResponseInterface
28+
{
29+
$userGroup = Helper::fetchObjectFromRequestParameter($variables['id'], UserGroup::class);
30+
31+
$this->assertGroupCanBeDeleted($userGroup);
32+
33+
(new UserGroupAction([$userGroup], 'delete'))->executeAction();
34+
35+
return new JsonResponse([]);
36+
}
37+
38+
private function assertGroupCanBeDeleted(UserGroup $userGroup): void
39+
{
40+
if (!$userGroup->isDeletable()) {
41+
throw new PermissionDeniedException();
42+
}
43+
}
44+
}

wcfsetup/install/files/lib/system/gridView/AbstractGridView.class.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,11 @@ public function renderColumn(GridViewColumn $column, DatabaseObject $row): strin
275275

276276
$value = $column->render($value, $row);
277277

278-
if (isset($this->rowLink) && $column->applyRowLink()) {
278+
if (
279+
isset($this->rowLink)
280+
&& $column->applyRowLink()
281+
&& $this->rowLink->isAvailable($row)
282+
) {
279283
$value = $this->rowLink->render($value, $row, $column->isTitleColumn());
280284
}
281285

wcfsetup/install/files/lib/system/gridView/GridViewRowLink.class.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public function __construct(
2323
private readonly array $parameters = [],
2424
private readonly string $cssClass = '',
2525
private readonly bool $isLinkableObject = false,
26+
private readonly ?\Closure $isAvailableCallback = null
2627
) {}
2728

2829
/**
@@ -75,4 +76,13 @@ private function getLink(DatabaseObject $object): string
7576

7677
throw new \BadMethodCallException("GridViewRowLink expects object to be an implementation of ILinkableObject.");
7778
}
79+
80+
public function isAvailable(DatabaseObject $row): bool
81+
{
82+
if ($this->isAvailableCallback === null) {
83+
return true;
84+
}
85+
86+
return ($this->isAvailableCallback)($row);
87+
}
7888
}

0 commit comments

Comments
 (0)