Add abstract implementation for DBO lists
authorMarcel Werk <burntime@woltlab.com>
Wed, 11 Sep 2024 13:04:28 +0000 (15:04 +0200)
committerMarcel Werk <burntime@woltlab.com>
Tue, 12 Nov 2024 11:51:53 +0000 (12:51 +0100)
com.woltlab.wcf/templates/shared_gridView.tpl
wcfsetup/install/files/acp/templates/userRankList.tpl
wcfsetup/install/files/js/WoltLabSuite/Core/Api/GridViews/GetRows.js
wcfsetup/install/files/lib/acp/page/UserRankListPage.class.php
wcfsetup/install/files/lib/page/AbstractGridViewPage.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/endpoint/controller/core/gridViews/GetRows.class.php
wcfsetup/install/files/lib/system/view/grid/AbstractGridView.class.php
wcfsetup/install/files/lib/system/view/grid/DatabaseObjectListGridView.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/view/grid/UserRankGridView.class.php

index 8957dad2acd4084e014c69b2acbf9e351c2506f4..328d58e383f3538c9660246ffa2763da513e8314 100644 (file)
@@ -1,33 +1,37 @@
-<div class="paginationTop">
-       <woltlab-core-pagination id="{$view->getID()}_topPagination" page="{$view->getPageNo()}" count="{$view->countPages()}"></woltlab-core-pagination>
-</div>
+{if $view->countRows()}
+       <div class="paginationTop">
+               <woltlab-core-pagination id="{$view->getID()}_topPagination" page="{$view->getPageNo()}" count="{$view->countPages()}"></woltlab-core-pagination>
+       </div>
 
-<div class="section tabularBox">
-       <table class="table" id="{$view->getID()}_table">
-               <thead>
-                       <tr>
-                               {unsafe:$view->renderHeader()}
-                       </td>
-               </thead>
-               <tbody>
-                       {unsafe:$view->renderRows()}
-               </tbody>
-       </table>
-</div>
+       <div class="section tabularBox">
+               <table class="table" id="{$view->getID()}_table">
+                       <thead>
+                               <tr>
+                                       {unsafe:$view->renderHeader()}
+                               </td>
+                       </thead>
+                       <tbody>
+                               {unsafe:$view->renderRows()}
+                       </tbody>
+               </table>
+       </div>
 
-<div class="paginationBottom">
-       <woltlab-core-pagination id="{$view->getID()}_bottomPagination" page="{$view->getPageNo()}" count="{$view->countPages()}"></woltlab-core-pagination>
-</div>
+       <div class="paginationBottom">
+               <woltlab-core-pagination id="{$view->getID()}_bottomPagination" page="{$view->getPageNo()}" count="{$view->countPages()}"></woltlab-core-pagination>
+       </div>
 
-<script data-relocate="true">
-       require(['WoltLabSuite/Core/Component/GridView'], ({ GridView }) => {
-               new GridView(
-                       '{unsafe:$view->getID()|encodeJs}',
-                       '{unsafe:$view->getClassName()|encodeJS}',
-                       {$view->getPageNo()},
-                       '{unsafe:$view->getBaseUrl()|encodeJS}',
-                       '{unsafe:$view->getSortField()|encodeJS}',
-                       '{unsafe:$view->getSortOrder()|encodeJS}'
-               );
-       });
-</script>
+       <script data-relocate="true">
+               require(['WoltLabSuite/Core/Component/GridView'], ({ GridView }) => {
+                       new GridView(
+                               '{unsafe:$view->getID()|encodeJs}',
+                               '{unsafe:$view->getClassName()|encodeJS}',
+                               {$view->getPageNo()},
+                               '{unsafe:$view->getBaseUrl()|encodeJS}',
+                               '{unsafe:$view->getSortField()|encodeJS}',
+                               '{unsafe:$view->getSortOrder()|encodeJS}'
+                       );
+               });
+       </script>
+{else}
+       <woltlab-core-notice type="info">{lang}wcf.global.noItems{/lang}</woltlab-core-notice>
+{/if}
index 8fabc66d57d7f5f3dc1af4e90f6501a372820256..e4917f8cf807a196ba45ef6b036a3bb4d234b7e5 100644 (file)
@@ -19,7 +19,7 @@
 
 <header class="contentHeader">
        <div class="contentHeaderTitle">
-               <h1 class="contentTitle">{lang}wcf.acp.user.rank.list{/lang} <span class="badge badgeInverse">{#$items}</span></h1>
+               <h1 class="contentTitle">{lang}wcf.acp.user.rank.list{/lang} <span class="badge badgeInverse">{#$gridView->countRows()}</span></h1>
        </div>
        
        <nav class="contentHeaderNavigation">
        </nav>
 </header>
 
-{hascontent}
-       <div class="paginationTop">
-               {content}{pages print=true assign=pagesLinks controller="UserRankList" link="pageNo=%d&sortField=$sortField&sortOrder=$sortOrder"}{/content}
-       </div>
-{/hascontent}
-
-{if $objects|count}
-       {unsafe:$view->render()}
-       
-       {*<div class="section tabularBox">
-               <table class="table jsObjectActionContainer" data-object-action-class-name="wcf\data\user\rank\UserRankAction">
-                       <thead>
-                               <tr>
-                                       <th class="columnID columnRankID{if $sortField == 'rankID'} active {@$sortOrder}{/if}" colspan="2"><a href="{link controller='UserRankList'}pageNo={@$pageNo}&sortField=rankID&sortOrder={if $sortField == 'rankID' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.global.objectID{/lang}</a></th>
-                                       <th class="columnTitle columnRankTitle{if $sortField == 'rankTitleI18n'} active {@$sortOrder}{/if}"><a href="{link controller='UserRankList'}pageNo={@$pageNo}&sortField=rankTitleI18n&sortOrder={if $sortField == 'rankTitleI18n' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.acp.user.rank.title{/lang}</a></th>
-                                       <th class="columnText columnRankImage{if $sortField == 'rankImage'} active {@$sortOrder}{/if}"><a href="{link controller='UserRankList'}pageNo={@$pageNo}&sortField=rankImage&sortOrder={if $sortField == 'rankImage' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.acp.user.rank.image{/lang}</a></th>
-                                       <th class="columnText columnGroupID{if $sortField == 'groupID'} active {@$sortOrder}{/if}"><a href="{link controller='UserRankList'}pageNo={@$pageNo}&sortField=groupID&sortOrder={if $sortField == 'groupID' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.user.group{/lang}</a></th>
-                                       <th class="columnText columnRequiredGender{if $sortField == 'requiredGender'} active {@$sortOrder}{/if}"><a href="{link controller='UserRankList'}pageNo={@$pageNo}&sortField=requiredGender&sortOrder={if $sortField == 'requiredGender' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.user.option.gender{/lang}</a></th>
-                                       <th class="columnDigits columnRequiredPoints{if $sortField == 'requiredPoints'} active {@$sortOrder}{/if}"><a href="{link controller='UserRankList'}pageNo={@$pageNo}&sortField=requiredPoints&sortOrder={if $sortField == 'requiredPoints' && $sortOrder == 'ASC'}DESC{else}ASC{/if}{/link}">{lang}wcf.acp.user.rank.requiredPoints{/lang}</a></th>
-                                       
-                                       {event name='columnHeads'}
-                               </tr>
-                       </thead>
-                       
-                       <tbody class="jsReloadPageWhenEmpty">
-                               {foreach from=$objects item=userRank}
-                                       <tr class="jsUserRankRow jsObjectActionObject" data-object-id="{@$userRank->getObjectID()}">
-                                               <td class="columnIcon">
-                                                       <a href="{link controller='UserRankEdit' id=$userRank->rankID}{/link}" title="{lang}wcf.global.button.edit{/lang}" class="jsTooltip">{icon name='pencil'}</a>
-                                                       {objectAction action="delete" objectTitle=$userRank->getTitle()}
-                                                       
-                                                       {event name='rowButtons'}
-                                               </td>
-                                               <td class="columnID columnRankID">{@$userRank->rankID}</td>
-                                               <td class="columnTitle columnRankTitle"><a href="{link controller='UserRankEdit' id=$userRank->rankID}{/link}" title="{lang}wcf.acp.user.rank.edit{/lang}" class="badge label{if $userRank->cssClassName} {$userRank->cssClassName}{/if}">{$userRank->getTitle()}</a></td>
-                                               <td class="columnText columnRankImage">{if $userRank->rankImage}{@$userRank->getImage()}{/if}</td>
-                                               <td class="columnText columnGroupID">{$userRank->groupName|phrase}</td>
-                                               <td class="columnText columnRequiredGender">
-                                                       {if $userRank->requiredGender}
-                                                               {if $userRank->requiredGender == 1}
-                                                                       {lang}wcf.user.gender.male{/lang}
-                                                               {elseif $userRank->requiredGender == 2}
-                                                                       {lang}wcf.user.gender.female{/lang}
-                                                               {else}
-                                                                       {lang}wcf.user.gender.other{/lang}
-                                                               {/if}
-                                                       {/if}
-                                               </td>
-                                               <td class="columnDigits columnRequiredPoints">{#$userRank->requiredPoints}</td>
-                                               
-                                               {event name='columns'}
-                                       </tr>
-                               {/foreach}
-                       </tbody>
-               </table>
-       </div>*}
-       
-       <footer class="contentFooter">
-               {hascontent}
-                       <div class="paginationBottom">
-                               {content}{@$pagesLinks}{/content}
-                       </div>
-               {/hascontent}
-               
-               <nav class="contentFooterNavigation">
-                       <ul>
-                               <li><a href="{link controller='UserRankAdd'}{/link}" class="button">{icon name='plus'} <span>{lang}wcf.acp.user.rank.add{/lang}</span></a></li>
-                               
-                               {event name='contentFooterNavigation'}
-                       </ul>
-               </nav>
-       </footer>
-{else}
-       <woltlab-core-notice type="info">{lang}wcf.global.noItems{/lang}</woltlab-core-notice>
-{/if}
+{unsafe:$gridView->render()}
 
 {include file='footer'}
index b07327102da078782955e87d9e862ca8860c9fc3..ab4438e0fbc4c82226c01541b607d37426f4a795 100644 (file)
@@ -1,7 +1,7 @@
 define(["require", "exports", "WoltLabSuite/Core/Ajax/Backend", "../Result"], function (require, exports, Backend_1, Result_1) {
     "use strict";
     Object.defineProperty(exports, "__esModule", { value: true });
-    exports.getRows = void 0;
+    exports.getRows = getRows;
     async function getRows(gridViewClass, pageNo, sortField = "", sortOrder = "ASC") {
         const url = new URL(`${window.WSC_RPC_API_URL}core/gridViews/rows`);
         url.searchParams.set("gridView", gridViewClass);
@@ -17,5 +17,4 @@ define(["require", "exports", "WoltLabSuite/Core/Ajax/Backend", "../Result"], fu
         }
         return (0, Result_1.apiResultFromValue)(response);
     }
-    exports.getRows = getRows;
 });
index 4559ddd4c243eeec52776f3106639657569d3354..c6368082546e3bcaf67e240452850611653b8a43 100644 (file)
@@ -2,22 +2,19 @@
 
 namespace wcf\acp\page;
 
-use wcf\data\user\rank\I18nUserRankList;
-use wcf\page\SortablePage;
-use wcf\system\request\LinkHandler;
+use wcf\page\AbstractGridViewPage;
 use wcf\system\view\grid\UserRankGridView;
-use wcf\system\WCF;
 
 /**
  * Lists available user ranks.
  *
- * @author  Marcel Werk
- * @copyright   2001-2019 WoltLab GmbH
- * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @author      Marcel Werk
+ * @copyright   2001-2024 WoltLab GmbH
+ * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  *
- * @property    I18nUserRankList $objectList
+ * @property    UserRankGridView    $gridView
  */
-class UserRankListPage extends SortablePage
+class UserRankListPage extends AbstractGridViewPage
 {
     /**
      * @inheritDoc
@@ -37,40 +34,5 @@ class UserRankListPage extends SortablePage
     /**
      * @inheritDoc
      */
-    public $objectListClassName = I18nUserRankList::class;
-
-    /**
-     * @inheritDoc
-     */
-    public $defaultSortField = 'rankTitleI18n';
-
-    /**
-     * @inheritDoc
-     */
-    public $validSortFields = ['rankID', 'groupID', 'requiredPoints', 'rankTitleI18n', 'rankImage', 'requiredGender'];
-
-    /**
-     * @inheritDoc
-     */
-    protected function initObjectList()
-    {
-        parent::initObjectList();
-
-        $this->objectList->sqlSelects .= (!empty($this->objectList->sqlSelects) ? ', ' : '') . 'user_group.groupName';
-        $this->objectList->sqlJoins .= '
-            LEFT JOIN   wcf1_user_group user_group
-            ON          user_group.groupID = user_rank.groupID';
-    }
-
-    public function assignVariables()
-    {
-        parent::assignVariables();
-
-        $view = new UserRankGridView(isset($_GET['pageNo']) ? \intval($_GET['pageNo']) : 1);
-        $view->setBaseUrl(LinkHandler::getInstance()->getControllerLink(self::class));
-
-        WCF::getTPL()->assign([
-            'view' => $view,
-        ]);
-    }
+    protected string $gridViewClassName = UserRankGridView::class;
 }
diff --git a/wcfsetup/install/files/lib/page/AbstractGridViewPage.class.php b/wcfsetup/install/files/lib/page/AbstractGridViewPage.class.php
new file mode 100644 (file)
index 0000000..06b279f
--- /dev/null
@@ -0,0 +1,72 @@
+<?php
+
+namespace wcf\page;
+
+use wcf\system\exception\ParentClassException;
+use wcf\system\exception\SystemException;
+use wcf\system\request\LinkHandler;
+use wcf\system\view\grid\AbstractGridView;
+use wcf\system\WCF;
+
+abstract class AbstractGridViewPage extends AbstractPage
+{
+    protected string $gridViewClassName;
+    protected AbstractGridView $gridView;
+    protected int $pageNo = 1;
+    protected string $sortField = '';
+    protected string $sortOrder = 'ASC';
+
+    #[\Override]
+    public function readParameters()
+    {
+        parent::readParameters();
+
+        if (isset($_REQUEST['pageNo'])) {
+            $this->pageNo = \intval($_REQUEST['pageNo']);
+        }
+        if (isset($_REQUEST['sortField'])) {
+            $this->sortField = $_REQUEST['sortField'];
+        }
+        if (isset($_REQUEST['sortOrder']) && ($_REQUEST['sortOrder'] === 'ASC' || $_REQUEST['sortOrder'] === 'DESC')) {
+            $this->sortOrder = $_REQUEST['sortOrder'];
+        }
+    }
+
+    #[\Override]
+    public function readData()
+    {
+        parent::readData();
+
+        $this->initGridView();
+    }
+
+    #[\Override]
+    public function assignVariables()
+    {
+        parent::assignVariables();
+
+        WCF::getTPL()->assign([
+            'gridView' => $this->gridView,
+        ]);
+    }
+
+    protected function initGridView(): void
+    {
+        if (!isset($this->gridViewClassName)) {
+            throw new SystemException('Grid view class name not specified.');
+        }
+
+        if (!\is_subclass_of($this->gridViewClassName, AbstractGridView::class)) {
+            throw new ParentClassException($this->gridViewClassName, AbstractGridView::class);
+        }
+
+        $this->gridView = new $this->gridViewClassName;
+
+        if ($this->sortField) {
+            $this->gridView->setSortField($this->sortField);
+        }
+        $this->gridView->setSortOrder($this->sortOrder);
+        $this->gridView->setPageNo($this->pageNo);
+        $this->gridView->setBaseUrl(LinkHandler::getInstance()->getControllerLink(static::class));
+    }
+}
index 204e1ffb83c2c8d85f9ede76a229510d6e3e69a1..9d43b27c6eb4d7703a2e820e43531f98a5a6f0af 100644 (file)
@@ -24,13 +24,14 @@ final class GetRows implements IController
             throw new UserInputException('gridView', 'invalid');
         }
 
-        $view = new $parameters->gridView($parameters->pageNo);
+        $view = new $parameters->gridView();
         \assert($view instanceof AbstractGridView);
 
         if (!$view->isAccessible()) {
             throw new PermissionDeniedException();
         }
 
+        $view->setPageNo($parameters->pageNo);
         if ($parameters->sortField) {
             $view->setSortField($parameters->sortField);
         }
index 1ebf9b402143057521700efb4a66edcc3f1c1a1c..ee68dbe2c6f36d9115f11324452b97150eb753d6 100644 (file)
@@ -11,8 +11,9 @@ abstract class AbstractGridView
     private string $baseUrl = '';
     private string $sortField = '';
     private string $sortOrder = 'ASC';
+    private int $pageNo = 1;
 
-    public function __construct(private readonly int $pageNo = 1)
+    public function __construct()
     {
         $this->init();
     }
@@ -68,7 +69,7 @@ abstract class AbstractGridView
     {
         $result = '';
 
-        foreach ($this->getRows($this->rowsPerPage, ($this->pageNo - 1) * $this->rowsPerPage) as $row) {
+        foreach ($this->getRows() as $row) {
             $result .= <<<EOT
                 <tr>
             EOT;
@@ -94,17 +95,13 @@ abstract class AbstractGridView
         return $row[$identifer] ?? '';
     }
 
-    public abstract function getRows(int $limit, int $offset = 0): array;
+    protected abstract function getRows(): array;
 
-
-    public function getPageNo(): int
-    {
-        return $this->pageNo;
-    }
+    public abstract function countRows(): int;
 
     public function countPages(): int
     {
-        return 3;
+        return \ceil($this->countRows() / $this->getRowsPerPage());
     }
 
     public function getClassName(): string
@@ -169,4 +166,24 @@ abstract class AbstractGridView
     {
         return $this->sortOrder;
     }
+
+    public function getPageNo(): int
+    {
+        return $this->pageNo;
+    }
+
+    public function setPageNo(int $pageNo): void
+    {
+        $this->pageNo = $pageNo;
+    }
+
+    public function getRowsPerPage(): int
+    {
+        return $this->rowsPerPage;
+    }
+
+    public function setRowsPerPage(int $rowsPerPage): void
+    {
+        $this->rowsPerPage = $rowsPerPage;
+    }
 }
diff --git a/wcfsetup/install/files/lib/system/view/grid/DatabaseObjectListGridView.class.php b/wcfsetup/install/files/lib/system/view/grid/DatabaseObjectListGridView.class.php
new file mode 100644 (file)
index 0000000..0c3e536
--- /dev/null
@@ -0,0 +1,66 @@
+<?php
+
+namespace wcf\system\view\grid;
+
+use wcf\data\DatabaseObject;
+use wcf\data\DatabaseObjectList;
+use wcf\system\exception\ParentClassException;
+use wcf\system\exception\SystemException;
+
+abstract class DatabaseObjectListGridView extends AbstractGridView
+{
+    protected string $objectListClassName;
+    protected DatabaseObjectList $objectList;
+    private int $objectCount;
+
+    protected function getRows(): array
+    {
+        $this->getObjectList()->readObjects();
+
+        return $this->getObjectList()->getObjects();
+    }
+
+    public function countRows(): int
+    {
+        if (!isset($this->objectCount)) {
+            $this->objectCount = $this->getObjectList()->countObjects();
+        }
+
+        return $this->objectCount;
+    }
+
+    protected function getData(mixed $row, string $identifer): mixed
+    {
+        \assert($row instanceof DatabaseObject);
+
+        return $row->__get($identifer);
+    }
+
+    protected function initObjectList(): void
+    {
+        if (!isset($this->objectListClassName)) {
+            throw new SystemException('Database object list class name not specified.');
+        }
+
+        if (!\is_subclass_of($this->objectListClassName, DatabaseObjectList::class)) {
+            throw new ParentClassException($this->objectListClassName, DatabaseObjectList::class);
+        }
+
+        $this->objectList = new $this->objectListClassName;
+        $this->objectList->sqlLimit = $this->getRowsPerPage();
+        $this->objectList->sqlOffset = ($this->getPageNo() - 1) * $this->getRowsPerPage();
+        //wcfDebug($this->objectList->sqlLimit, $this->objectList->sqlOffset);
+        if ($this->getSortField()) {
+            $this->objectList->sqlOrderBy = $this->getSortField() . ' ' . $this->getSortOrder();
+        }
+    }
+
+    public function getObjectList(): DatabaseObjectList
+    {
+        if (!isset($this->objectList)) {
+            $this->initObjectList();
+        }
+
+        return $this->objectList;
+    }
+}
index dbc68c3ded3c9f269897c69459a97af325833b82..1389c7e5d644b9a49a6cdf0edb8cb5108228b9e9 100644 (file)
@@ -3,7 +3,6 @@
 namespace wcf\system\view\grid;
 
 use wcf\acp\form\UserRankEditForm;
-use wcf\data\DatabaseObject;
 use wcf\data\user\group\UserGroup;
 use wcf\data\user\rank\UserRank;
 use wcf\data\user\rank\UserRankList;
@@ -14,8 +13,10 @@ use wcf\system\view\grid\renderer\TitleColumnRenderer;
 use wcf\system\WCF;
 use wcf\util\StringUtil;
 
-final class UserRankGridView extends AbstractGridView
+final class UserRankGridView extends DatabaseObjectListGridView
 {
+    protected string $objectListClassName = UserRankList::class;
+
     #[\Override]
     protected function init(): void
     {
@@ -92,26 +93,6 @@ final class UserRankGridView extends AbstractGridView
         $this->setSortField('rankTitle');
     }
 
-    public function getRows(int $limit, int $offset = 0): array
-    {
-        $list = new UserRankList();
-        $list->sqlLimit = $limit;
-        $list->sqlOffset = $offset;
-        if ($this->getSortField()) {
-            $list->sqlOrderBy = $this->getSortField() . ' ' . $this->getSortOrder();
-        }
-        $list->readObjects();
-
-        return $list->getObjects();
-    }
-
-    protected function getData(mixed $row, string $identifer): mixed
-    {
-        \assert($row instanceof DatabaseObject);
-
-        return $row->__get($identifer);
-    }
-
     public function isAccessible(): bool
     {
         return \MODULE_USER_RANK && WCF::getSession()->getPermission('admin.user.rank.canManageRank');