diff --git a/wcfsetup/install/files/acp/templates/boxList.tpl b/wcfsetup/install/files/acp/templates/boxList.tpl index d6416cfb2ef..28e0c56d116 100644 --- a/wcfsetup/install/files/acp/templates/boxList.tpl +++ b/wcfsetup/install/files/acp/templates/boxList.tpl @@ -2,7 +2,7 @@
-

{lang}wcf.acp.box.list{/lang}{if $items} {#$items}{/if}

+

{lang}wcf.acp.box.list{/lang}

-
-
-

{lang}wcf.global.filter{/lang}

- -
-
-
-
- -
-
- -
-
-
- -
-
- -
-
-
- -
-
- -
-
-
- -
-
- -
-
-
- -
-
- -
-
-
- -
-
- - {event name='filterFields'} -
- -
- - {csrfToken} -
-
-
- -{hascontent} -
- {content} - {assign var='linkParameters' value=''} - {if $name}{capture append=linkParameters}&name={@$name|rawurlencode}{/capture}{/if} - {if $title}{capture append=linkParameters}&title={@$title|rawurlencode}{/capture}{/if} - {if $content}{capture append=linkParameters}&content={@$content|rawurlencode}{/capture}{/if} - {if $position}{capture append=linkParameters}&position={@$position}{/capture}{/if} - {if $boxType}{capture append=linkParameters}&boxType={@$boxType|rawurlencode}{/capture}{/if} - {if $originIsNotSystem}{capture append=linkParameters}&originIsNotSystem=1{/capture}{/if} - - {pages print=true assign=pagesLinks controller="BoxList" link="pageNo=%d&sortField=$sortField&sortOrder=$sortOrder$linkParameters"} - {/content} -
-{/hascontent} - -{if $objects|count} -
- - - - - - - - - - {event name='columnHeads'} - - - - - {foreach from=$objects item=box} - - - - - - - - - {event name='columns'} - - {/foreach} - -
{lang}wcf.global.objectID{/lang}{lang}wcf.global.name{/lang}{lang}wcf.acp.box.type{/lang}{lang}wcf.acp.box.position{/lang}{lang}wcf.global.showOrder{/lang}
- {objectAction action="toggle" isDisabled=$box->isDisabled} - {icon name='pencil'} - {if $box->canDelete()} - {objectAction action="delete" objectTitle=$box->name} - {else} - - {icon name='xmark'} - - {/if} - - {event name='rowButtons'} - {@$box->boxID}{$box->name}{lang}wcf.acp.box.type.{@$box->boxType}{/lang}{lang}wcf.acp.box.position.{@$box->position}{/lang}{#$box->showOrder}
-
- - -{else} - {lang}wcf.global.noItems{/lang} -{/if} +
+ {unsafe:$gridView->render()} +
{include file='boxAddDialog'} diff --git a/wcfsetup/install/files/lib/acp/page/BoxListPage.class.php b/wcfsetup/install/files/lib/acp/page/BoxListPage.class.php index 638257dac51..2c78aa5e285 100644 --- a/wcfsetup/install/files/lib/acp/page/BoxListPage.class.php +++ b/wcfsetup/install/files/lib/acp/page/BoxListPage.class.php @@ -2,188 +2,64 @@ namespace wcf\acp\page; -use wcf\data\box\Box; -use wcf\data\box\BoxList; -use wcf\page\SortablePage; +use wcf\page\AbstractGridViewPage; +use wcf\system\gridView\AbstractGridView; +use wcf\system\gridView\admin\BoxGridView; use wcf\system\language\LanguageFactory; use wcf\system\WCF; -use wcf\util\StringUtil; /** * Shows a list of boxes. * - * @author Marcel Werk - * @copyright 2001-2019 WoltLab GmbH - * @license GNU Lesser General Public License + * @author Olaf Braun, Marcel Werk + * @copyright 2001-2025 WoltLab GmbH + * @license GNU Lesser General Public License * @since 3.0 * - * @property BoxList $objectList + * @property BoxGridView $gridView */ -class BoxListPage extends SortablePage +class BoxListPage extends AbstractGridViewPage { /** * @inheritDoc */ public $activeMenuItem = 'wcf.acp.menu.link.cms.box.list'; - /** - * @inheritDoc - */ - public $objectListClassName = BoxList::class; - /** * @inheritDoc */ public $neededPermissions = ['admin.content.cms.canManageBox']; - /** - * @inheritDoc - */ - public $defaultSortField = 'name'; - - /** - * @inheritDoc - */ - public $validSortFields = ['boxID', 'name', 'boxType', 'position', 'showOrder']; - - /** - * @inheritDoc - */ - public $itemsPerPage = 50; - - /** - * name - * @var string - */ - public $name = ''; - - /** - * title - * @var string - */ - public $title = ''; - - /** - * content - * @var string - */ - public $content = ''; - - /** - * box type - * @var string - */ - public $boxType = ''; - - /** - * box position - * @var string - */ - public $position = ''; - /** * display 'Add Box' dialog on load * @var int */ public $showBoxAddDialog = 0; - /** - * filters the list of boxes showing only custom boxes - * @var bool - */ - public $originIsNotSystem = 0; - - /** - * @inheritDoc - */ + #[\Override] public function readParameters() { parent::readParameters(); - if (!empty($_REQUEST['name'])) { - $this->name = StringUtil::trim($_REQUEST['name']); - } - if (!empty($_REQUEST['title'])) { - $this->title = StringUtil::trim($_REQUEST['title']); - } - if (!empty($_REQUEST['content'])) { - $this->content = StringUtil::trim($_REQUEST['content']); - } - if (!empty($_REQUEST['boxType'])) { - $this->boxType = $_REQUEST['boxType']; - } - if (!empty($_REQUEST['position'])) { - $this->position = $_REQUEST['position']; - } if (!empty($_REQUEST['showBoxAddDialog'])) { $this->showBoxAddDialog = 1; } - if (!empty($_REQUEST['originIsNotSystem'])) { - $this->originIsNotSystem = 1; - } } - /** - * @inheritDoc - */ - protected function initObjectList() - { - parent::initObjectList(); - - // hide menu boxes - $this->objectList->getConditionBuilder()->add('box.boxType <> ?', ['menu']); - - if (!empty($this->name)) { - $this->objectList->getConditionBuilder()->add('box.name LIKE ?', ['%' . $this->name . '%']); - } - if (!empty($this->title)) { - $this->objectList->getConditionBuilder()->add( - 'box.boxID IN ( - SELECT boxID - FROM wcf1_box_content - WHERE title LIKE ? - )', - ['%' . $this->title . '%'] - ); - } - if (!empty($this->content)) { - $this->objectList->getConditionBuilder()->add( - 'box.boxID IN ( - SELECT boxID - FROM wcf1_box_content - WHERE content LIKE ? - )', - ['%' . $this->content . '%'] - ); - } - if (!empty($this->position)) { - $this->objectList->getConditionBuilder()->add('box.position = ?', [$this->position]); - } - if (!empty($this->boxType)) { - $this->objectList->getConditionBuilder()->add('box.boxType = ?', [$this->boxType]); - } - if ($this->originIsNotSystem) { - $this->objectList->getConditionBuilder()->add('box.originIsSystem = ?', [0]); - } - } - - /** - * @inheritDoc - */ + #[\Override] public function assignVariables() { parent::assignVariables(); WCF::getTPL()->assign([ - 'name' => $this->name, - 'title' => $this->title, - 'content' => $this->content, - 'boxType' => $this->boxType, - 'position' => $this->position, - 'availablePositions' => Box::$availablePositions, 'availableLanguages' => LanguageFactory::getInstance()->getLanguages(), 'showBoxAddDialog' => $this->showBoxAddDialog, - 'originIsNotSystem' => $this->originIsNotSystem, ]); } + + #[\Override] + protected function createGridViewController(): AbstractGridView + { + return new BoxGridView(); + } } diff --git a/wcfsetup/install/files/lib/bootstrap/com.woltlab.wcf.php b/wcfsetup/install/files/lib/bootstrap/com.woltlab.wcf.php index 7c9e84203a1..5d5a2acd410 100644 --- a/wcfsetup/install/files/lib/bootstrap/com.woltlab.wcf.php +++ b/wcfsetup/install/files/lib/bootstrap/com.woltlab.wcf.php @@ -169,6 +169,9 @@ static function (\wcf\event\endpoint\ControllerCollecting $event) { $event->register(new \wcf\system\endpoint\controller\core\captchas\questions\EnableQuestion()); $event->register(new \wcf\system\endpoint\controller\core\captchas\questions\DisableQuestion()); $event->register(new \wcf\system\endpoint\controller\core\captchas\questions\DeleteQuestion()); + $event->register(new \wcf\system\endpoint\controller\core\boxes\DisableBox()); + $event->register(new \wcf\system\endpoint\controller\core\boxes\EnableBox()); + $event->register(new \wcf\system\endpoint\controller\core\boxes\DeleteBox()); } ); diff --git a/wcfsetup/install/files/lib/event/gridView/admin/BoxGridViewInitialized.class.php b/wcfsetup/install/files/lib/event/gridView/admin/BoxGridViewInitialized.class.php new file mode 100644 index 00000000000..11aebc552c7 --- /dev/null +++ b/wcfsetup/install/files/lib/event/gridView/admin/BoxGridViewInitialized.class.php @@ -0,0 +1,21 @@ + + * @since 6.2 + */ +final class BoxGridViewInitialized implements IPsr14Event +{ + public function __construct(public readonly BoxGridView $gridView) + { + } +} diff --git a/wcfsetup/install/files/lib/event/interaction/admin/BoxInteractionCollecting.class.php b/wcfsetup/install/files/lib/event/interaction/admin/BoxInteractionCollecting.class.php new file mode 100644 index 00000000000..b0563161ec2 --- /dev/null +++ b/wcfsetup/install/files/lib/event/interaction/admin/BoxInteractionCollecting.class.php @@ -0,0 +1,21 @@ + + * @since 6.2 + */ +final class BoxInteractionCollecting implements IPsr14Event +{ + public function __construct(public readonly BoxInteractions $provider) + { + } +} diff --git a/wcfsetup/install/files/lib/system/endpoint/controller/core/boxes/DeleteBox.class.php b/wcfsetup/install/files/lib/system/endpoint/controller/core/boxes/DeleteBox.class.php new file mode 100644 index 00000000000..968d7efdaa0 --- /dev/null +++ b/wcfsetup/install/files/lib/system/endpoint/controller/core/boxes/DeleteBox.class.php @@ -0,0 +1,44 @@ + + * @since 6.2 + */ +#[DeleteRequest('/core/boxes/{id:\d+}')] +final class DeleteBox implements IController +{ + #[\Override] + public function __invoke(ServerRequestInterface $request, array $variables): ResponseInterface + { + $box = Helper::fetchObjectFromRequestParameter($variables['id'], Box::class); + + $this->assertBoxCanBeDeleted($box); + + (new BoxAction([$box], 'delete'))->executeAction(); + + return new JsonResponse([]); + } + + private function assertBoxCanBeDeleted(Box $box): void + { + if (!$box->canDelete()) { + throw new PermissionDeniedException(); + } + } +} diff --git a/wcfsetup/install/files/lib/system/endpoint/controller/core/boxes/DisableBox.class.php b/wcfsetup/install/files/lib/system/endpoint/controller/core/boxes/DisableBox.class.php new file mode 100644 index 00000000000..56e4a4d50ea --- /dev/null +++ b/wcfsetup/install/files/lib/system/endpoint/controller/core/boxes/DisableBox.class.php @@ -0,0 +1,47 @@ + + * @since 6.2 + */ +#[PostRequest('/core/boxes/{id:\d+}/disable')] +final class DisableBox implements IController +{ + #[\Override] + public function __invoke(ServerRequestInterface $request, array $variables): ResponseInterface + { + $box = Helper::fetchObjectFromRequestParameter($variables['id'], Box::class); + + $this->assertBoxCanBeDisabled($box); + + (new BoxAction([$box], 'toggle'))->executeAction(); + + return new JsonResponse([]); + } + + private function assertBoxCanBeDisabled(Box $box): void + { + WCF::getSession()->checkPermissions(['admin.content.cms.canManageBox']); + + if ($box->isDisabled) { + throw new PermissionDeniedException(); + } + } +} diff --git a/wcfsetup/install/files/lib/system/endpoint/controller/core/boxes/EnableBox.class.php b/wcfsetup/install/files/lib/system/endpoint/controller/core/boxes/EnableBox.class.php new file mode 100644 index 00000000000..41404b9f4c2 --- /dev/null +++ b/wcfsetup/install/files/lib/system/endpoint/controller/core/boxes/EnableBox.class.php @@ -0,0 +1,47 @@ + + * @since 6.2 + */ +#[PostRequest('/core/boxes/{id:\d+}/enable')] +final class EnableBox implements IController +{ + #[\Override] + public function __invoke(ServerRequestInterface $request, array $variables): ResponseInterface + { + $box = Helper::fetchObjectFromRequestParameter($variables['id'], Box::class); + + $this->assertBoxCanBeEnabled($box); + + (new BoxAction([$box], 'toggle'))->executeAction(); + + return new JsonResponse([]); + } + + private function assertBoxCanBeEnabled(Box $box): void + { + WCF::getSession()->checkPermissions(['admin.content.cms.canManageBox']); + + if (!$box->isDisabled) { + throw new PermissionDeniedException(); + } + } +} diff --git a/wcfsetup/install/files/lib/system/gridView/admin/BoxGridView.class.php b/wcfsetup/install/files/lib/system/gridView/admin/BoxGridView.class.php new file mode 100644 index 00000000000..beea29114f7 --- /dev/null +++ b/wcfsetup/install/files/lib/system/gridView/admin/BoxGridView.class.php @@ -0,0 +1,154 @@ + + * @since 6.2 + */ +final class BoxGridView extends AbstractGridView +{ + public function __construct() + { + $this->addColumns([ + GridViewColumn::for('boxID') + ->label('wcf.global.objectID') + ->renderer(new ObjectIdColumnRenderer()) + ->sortable(), + GridViewColumn::for('name') + ->label('wcf.global.name') + ->titleColumn() + ->filter(new TextFilter()) + ->sortable(), + GridViewColumn::for('title') + ->label('wcf.global.title') + ->filter($this->getBoxContentFilter('title')) + ->hidden(), + GridViewColumn::for('content') + ->label('wcf.acp.box.content') + ->filter($this->getBoxContentFilter('content')) + ->hidden(), + GridViewColumn::for('boxType') + ->label('wcf.acp.box.type') + ->filter( + new SelectFilter( + \array_combine( + Box::$availableBoxTypes, + \array_map( + static fn(string $type) => 'wcf.acp.box.type.' . $type, + Box::$availableBoxTypes + ) + ) + ) + ) + ->sortable(), + GridViewColumn::for('position') + ->label('wcf.acp.box.position') + ->filter( + new SelectFilter( + \array_combine( + Box::$availablePositions, + \array_map( + static fn(string $position) => 'wcf.acp.box.position.' . $position, + Box::$availablePositions + ) + ) + ) + ) + ->sortable(), + GridViewColumn::for('showOrder') + ->label('wcf.global.showOrder') + ->filter(new NumericFilter()) + ->sortable(), + GridViewColumn::for('originIsSystem') + ->label('wcf.acp.box.originIsNotSystem') + ->filter( + new class extends BooleanFilter { + #[\Override] + public function applyFilter(DatabaseObjectList $list, string $id, string $value): void + { + $columnName = $this->getDatabaseColumnName($list, $id); + + $list->getConditionBuilder()->add("{$columnName} = ?", [0]); + } + } + ) + ->hidden(), + ]); + + $provider = new BoxInteractions(); + $provider->addInteractions([ + new Divider(), + new EditInteraction(BoxEditForm::class) + ]); + $this->setInteractionProvider($provider); + + $this->addQuickInteraction(new ToggleInteraction('enable', 'core/boxes/%s/enable', 'core/boxes/%s/disable')); + + $this->setSortField('name'); + $this->addRowLink(new GridViewRowLink(BoxEditForm::class)); + } + + private function getBoxContentFilter(string $databaseColumn): TextFilter + { + return new class($databaseColumn) extends TextFilter { + #[\Override] + public function applyFilter(DatabaseObjectList $list, string $id, string $value): void + { + $list->getConditionBuilder()->add( + "box.boxID IN ( + SELECT boxID + FROM wcf1_box_content + WHERE {$this->databaseColumn} LIKE ? + )", + ['%' . WCF::getDB()->escapeLikeValue($value) . '%'] + ); + } + }; + } + + #[\Override] + public function isAccessible(): bool + { + return WCF::getSession()->getPermission('admin.content.cms.canManageBox'); + } + + #[\Override] + protected function createObjectList(): DatabaseObjectList + { + $boxList = new BoxList(); + $boxList->getConditionBuilder()->add('box.boxType <> ?', ['menu']); + + return $boxList; + } + + #[\Override] + protected function getInitializedEvent(): ?IPsr14Event + { + return new BoxGridViewInitialized($this); + } +} diff --git a/wcfsetup/install/files/lib/system/interaction/admin/BoxInteractions.class.php b/wcfsetup/install/files/lib/system/interaction/admin/BoxInteractions.class.php new file mode 100644 index 00000000000..f98f66271a2 --- /dev/null +++ b/wcfsetup/install/files/lib/system/interaction/admin/BoxInteractions.class.php @@ -0,0 +1,37 @@ + + * @since 6.2 + */ +final class BoxInteractions extends AbstractInteractionProvider +{ + public function __construct() + { + $this->addInteractions([ + new DeleteInteraction('core/boxes/%s', static fn(Box $object) => $object->canDelete()) + ]); + + EventHandler::getInstance()->fire( + new BoxInteractionCollecting($this) + ); + } + + #[\Override] + public function getObjectClassName(): string + { + return Box::class; + } +}