diff --git a/wcfsetup/install/files/acp/js/WCF.ACP.js b/wcfsetup/install/files/acp/js/WCF.ACP.js index 989bea0b5ff..dc0c09ff2ca 100644 --- a/wcfsetup/install/files/acp/js/WCF.ACP.js +++ b/wcfsetup/install/files/acp/js/WCF.ACP.js @@ -474,9 +474,7 @@ WCF.ACP.Package.Uninstallation = WCF.ACP.Package.Installation.extend({ this._elements = elements; this._packageID = 0; - if (this._elements !== undefined && this._elements.length) { - this._super(0, 'UninstallPackage'); - } + this._super(0, 'UninstallPackage'); }, /** @@ -498,7 +496,15 @@ WCF.ACP.Package.Uninstallation = WCF.ACP.Package.Installation.extend({ * @see WCF.ACP.Package.Installation.init() */ _init: function() { - this._elements.click($.proxy(this._showConfirmationDialog, this)); + //this._elements.click($.proxy(this._showConfirmationDialog, this)); + + require(['WoltLabSuite/Core/Helper/Selector'], ({ wheneverFirstSeen }) => { + wheneverFirstSeen(".jsUninstallButton", (button) => { + button.addEventListener('click', (event) => { + this._showConfirmationDialog(event); + }); + }); + }); }, /** diff --git a/wcfsetup/install/files/acp/templates/packageList.tpl b/wcfsetup/install/files/acp/templates/packageList.tpl index b7d94374bd5..901b6d31eff 100644 --- a/wcfsetup/install/files/acp/templates/packageList.tpl +++ b/wcfsetup/install/files/acp/templates/packageList.tpl @@ -20,7 +20,7 @@
-

{lang}wcf.acp.package.list{/lang} {#$items}

+

{lang}wcf.acp.package.list{/lang} {#$gridView->countRows()}

{hascontent} @@ -72,85 +72,8 @@ {/if} {/if} -{hascontent} -
- {content}{pages print=true assign=pagesLinks controller='PackageList' link="pageNo=%d&sortField=$sortField&sortOrder=$sortOrder"}{/content} -
-{/hascontent} - -{if $objects|count} -
- - - - - - - - - - {event name='columnHeads'} - - - - - {foreach from=$objects item=$package} - - - - - - - - - {event name='columns'} - - {/foreach} - -
{lang}wcf.global.objectID{/lang}{lang}wcf.acp.package.name{/lang}{lang}wcf.acp.package.author{/lang}{lang}wcf.acp.package.version{/lang}{lang}wcf.acp.package.updateDate{/lang}
- {if $package->canUninstall()} - - {else} - - {icon name='xmark'} - - {/if} - - {event name='rowButtons'} - {@$package->packageID} - {$package} - {if $taintedApplications[$package->packageID]|isset} - - {icon name='triangle-exclamation'} - - {/if} - {if $package->authorURL}{$package->author}{else}{$package->author}{/if}{$package->packageVersion}{@$package->updateDate|time}
- -
- - -{/if} +
+ {unsafe:$gridView->render()} +
{include file='footer'} diff --git a/wcfsetup/install/files/lib/acp/page/PackageListPage.class.php b/wcfsetup/install/files/lib/acp/page/PackageListPage.class.php index b543c5999a3..f2729959a94 100644 --- a/wcfsetup/install/files/lib/acp/page/PackageListPage.class.php +++ b/wcfsetup/install/files/lib/acp/page/PackageListPage.class.php @@ -2,23 +2,23 @@ namespace wcf\acp\page; -use wcf\data\package\I18nPackageList; use wcf\data\package\update\server\PackageUpdateServer; -use wcf\page\SortablePage; +use wcf\page\AbstractGridViewPage; use wcf\system\application\ApplicationHandler; +use wcf\system\gridView\admin\PackageGridView; use wcf\system\language\LanguageFactory; use wcf\system\WCF; /** * Shows a list of all installed packages. * - * @author Marcel Werk - * @copyright 2001-2019 WoltLab GmbH - * @license GNU Lesser General Public License + * @author Marcel Werk + * @copyright 2001-2025 WoltLab GmbH + * @license GNU Lesser General Public License * - * @extends SortablePage + * @extends AbstractGridViewPage */ -class PackageListPage extends SortablePage +final class PackageListPage extends AbstractGridViewPage { /** * @inheritDoc @@ -33,47 +33,13 @@ class PackageListPage extends SortablePage 'admin.configuration.package.canUninstallPackage', ]; - /** - * @inheritDoc - */ - public $itemsPerPage = 50; - - /** - * @inheritDoc - */ - public $defaultSortField = 'packageType'; - - /** - * @inheritDoc - */ - public $defaultSortOrder = 'DESC'; - - /** - * @inheritDoc - */ - public $validSortFields = [ - 'packageID', - 'package', - 'packageDir', - 'packageNameI18n', - 'packageDescription', - 'packageDate', - 'packageURL', - 'isApplication', - 'author', - 'authorURL', - 'installDate', - 'updateDate', - ]; - - /** - * @inheritDoc - */ - public $objectListClassName = I18nPackageList::class; + #[\Override] + protected function createGridView(): PackageGridView + { + return new PackageGridView(); + } - /** - * @inheritDoc - */ + #[\Override] public function assignVariables() { parent::assignVariables(); @@ -94,14 +60,4 @@ public function assignVariables() 'upgradeOverrideEnabled' => PackageUpdateServer::isUpgradeOverrideEnabled(), ]); } - - /** - * @inheritDoc - */ - protected function readObjects() - { - $this->sqlOrderBy = ($this->sortField == 'packageNameI18n' ? '' : 'package.') . ($this->sortField == 'packageType' ? 'isApplication ' . $this->sortOrder : $this->sortField . ' ' . $this->sortOrder) . ($this->sortField != 'packageNameI18n' ? ', packageNameI18n ASC' : ''); - - parent::readObjects(); - } } diff --git a/wcfsetup/install/files/lib/data/package/Package.class.php b/wcfsetup/install/files/lib/data/package/Package.class.php index 0a5530e21c2..0bb787013af 100644 --- a/wcfsetup/install/files/lib/data/package/Package.class.php +++ b/wcfsetup/install/files/lib/data/package/Package.class.php @@ -5,6 +5,7 @@ use wcf\acp\page\PackagePage; use wcf\data\DatabaseObject; use wcf\data\ILinkableObject; +use wcf\system\application\ApplicationHandler; use wcf\system\package\PackageInstallationDispatcher; use wcf\system\request\IRouteController; use wcf\system\request\LinkHandler; @@ -460,4 +461,21 @@ public static function writeConfigFile($packageID) \file_put_contents($packageDir . PackageInstallationDispatcher::CONFIG_FILE, $content); } + + /** + * @since 6.2 + */ + public function isTainted(): bool + { + if (!$this->isApplication) { + return false; + } + + $package = ApplicationHandler::getInstance()->getApplicationByID($this->packageID); + if ($package === null) { + return false; + } + + return $package->isTainted; + } } diff --git a/wcfsetup/install/files/lib/event/gridView/admin/PackageGridViewInitialized.class.php b/wcfsetup/install/files/lib/event/gridView/admin/PackageGridViewInitialized.class.php new file mode 100644 index 00000000000..8df0d201455 --- /dev/null +++ b/wcfsetup/install/files/lib/event/gridView/admin/PackageGridViewInitialized.class.php @@ -0,0 +1,19 @@ + + * @since 6.2 + */ +final class PackageGridViewInitialized implements IPsr14Event +{ + public function __construct(public readonly PackageGridView $gridView) {} +} diff --git a/wcfsetup/install/files/lib/event/interaction/admin/PackageInteractionCollecting.class.php b/wcfsetup/install/files/lib/event/interaction/admin/PackageInteractionCollecting.class.php new file mode 100644 index 00000000000..ed1a7ff5fba --- /dev/null +++ b/wcfsetup/install/files/lib/event/interaction/admin/PackageInteractionCollecting.class.php @@ -0,0 +1,19 @@ + + * @since 6.2 + */ +final class PackageInteractionCollecting implements IPsr14Event +{ + public function __construct(public readonly PackageInteractions $provider) {} +} diff --git a/wcfsetup/install/files/lib/system/gridView/admin/PackageGridView.class.php b/wcfsetup/install/files/lib/system/gridView/admin/PackageGridView.class.php new file mode 100644 index 00000000000..36d2253abf3 --- /dev/null +++ b/wcfsetup/install/files/lib/system/gridView/admin/PackageGridView.class.php @@ -0,0 +1,137 @@ + + * @since 6.2 + * + * @extends AbstractGridView + */ +final class PackageGridView extends AbstractGridView +{ + public function __construct() + { + $this->addColumns([ + GridViewColumn::for('packageID') + ->label('wcf.global.objectID') + ->renderer(new ObjectIdColumnRenderer()) + ->sortable(), + GridViewColumn::for('packageName') + ->label('wcf.acp.package.name') + ->titleColumn() + ->filter(new I18nTextFilter()) + ->renderer( + new class extends PhraseColumnRenderer { + #[\Override] + public function render(mixed $value, DatabaseObject $row): string + { + $renderedValue = parent::render($value, $row); + + \assert($row instanceof Package); + + if ($row->isTainted()) { + $title = WCF::getLanguage()->getDynamicVariable("wcf.acp.package.application.isTainted"); + $icon = FontAwesomeIcon::fromString('triangle-exclamation;false')->toHtml(); + $renderedValue .= << + {$icon} + + HTML; + } + + return $renderedValue; + } + } + ) + ->renderer(new PhraseColumnRenderer()) + ->sortable(sortByDatabaseColumn: "packageNameI18n"), + GridViewColumn::for('author') + ->label('wcf.acp.package.author') + ->filter(new TextFilter()) + ->renderer([ + new class extends DefaultColumnRenderer implements ILinkColumnRenderer { + #[\Override] + public function render(mixed $value, DatabaseObject $row): string + { + \assert($row instanceof Package); + + if (!$row->authorURL) { + return $value; + } + + return \sprintf( + '%s', + StringUtil::encodeHTML($row->authorURL), + $value + ); + } + }, + ]) + ->sortable(), + GridViewColumn::for('packageVersion') + ->label('wcf.acp.package.version') + ->sortable(), + GridViewColumn::for('updateDate') + ->label('wcf.acp.package.updateDate') + ->sortable() + ->renderer(new TimeColumnRenderer()), + ]); + + $provider = new PackageInteractions(); + $provider->addInteractions([ + new Divider(), + new EditInteraction(PackagePage::class) + ]); + $this->setInteractionProvider($provider); + + $this->setRowsPerPage(50); + $this->setSortField('packageID'); + $this->addRowLink(new GridViewRowLink(PackagePage::class)); + } + + #[\Override] + public function isAccessible(): bool + { + return WCF::getSession()->getPermission('admin.configuration.package.canUpdatePackage') + || WCF::getSession()->getPermission('admin.configuration.package.canUninstallPackage'); + } + + #[\Override] + protected function createObjectList(): I18nPackageList + { + return new I18nPackageList(); + } + + #[\Override] + protected function getInitializedEvent(): PackageGridViewInitialized + { + return new PackageGridViewInitialized($this); + } +} diff --git a/wcfsetup/install/files/lib/system/interaction/admin/PackageInteractions.class.php b/wcfsetup/install/files/lib/system/interaction/admin/PackageInteractions.class.php new file mode 100644 index 00000000000..20ad18e3a5a --- /dev/null +++ b/wcfsetup/install/files/lib/system/interaction/admin/PackageInteractions.class.php @@ -0,0 +1,61 @@ + + * @since 6.2 + */ +final class PackageInteractions extends AbstractInteractionProvider +{ + public function __construct() + { + $this->addInteractions([ + new class( + 'uninstallation', + static fn(Package $package) => $package->canUninstall() + ) extends AbstractInteraction { + #[\Override] + public function render(DatabaseObject $object): string + { + \assert($object instanceof Package); + + $label = WCF::getLanguage()->get('wcf.acp.package.button.uninstall'); + $confirmMessage = StringUtil::encodeHTML(WCF::getLanguage()->getDynamicVariable( + 'wcf.acp.package.uninstallation.confirm', + ['package' => $object] + )); + + return << + {$label} + + HTML; + } + } + ]); + + EventHandler::getInstance()->fire( + new PackageInteractionCollecting($this) + ); + } + + #[\Override] + public function getObjectClassName(): string + { + return Package::class; + } +}