diff --git a/wcfsetup/install/files/acp/templates/paidSubscriptionList.tpl b/wcfsetup/install/files/acp/templates/paidSubscriptionList.tpl index 454f281d930..6f67ffa5183 100644 --- a/wcfsetup/install/files/acp/templates/paidSubscriptionList.tpl +++ b/wcfsetup/install/files/acp/templates/paidSubscriptionList.tpl @@ -2,7 +2,7 @@
-

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

+

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

-{hascontent} -
- {content}{pages print=true assign=pagesLinks controller='PaidSubscriptionList' link="pageNo=%d&sortField=$sortField&sortOrder=$sortOrder"}{/content} -
-{/hascontent} - -{if $objects|count} -
- - - - - - - - - - {event name='columnHeads'} - - - - - {foreach from=$objects item=subscription} - - - - - - - - - {event name='columns'} - - {/foreach} - -
{lang}wcf.global.objectID{/lang}{lang}wcf.global.title{/lang}{lang}wcf.acp.paidSubscription.cost{/lang}{lang}wcf.acp.paidSubscription.subscriptionLength{/lang}{lang}wcf.global.showOrder{/lang}
- {objectAction action="toggle" isDisabled=$subscription->isDisabled} - {icon name='pencil'} - {objectAction action="delete" objectTitle=$subscription->getTitle()} - {icon name='plus'} - - {event name='itemButtons'} - {@$subscription->subscriptionID}{$subscription->getTitle()}{@$subscription->currency} {$subscription->cost|currency}{if $subscription->subscriptionLength}{@$subscription->subscriptionLength} {lang}wcf.acp.paidSubscription.subscriptionLengthUnit.{@$subscription->subscriptionLengthUnit}{/lang}{else}∞{/if}{@$subscription->showOrder}
-
- - -{else} - {lang}wcf.global.noItems{/lang} -{/if} +
+ {unsafe:$gridView->render()} +
{include file='footer'} diff --git a/wcfsetup/install/files/lib/acp/page/PaidSubscriptionListPage.class.php b/wcfsetup/install/files/lib/acp/page/PaidSubscriptionListPage.class.php index 1480b069527..b6bebb788a6 100644 --- a/wcfsetup/install/files/lib/acp/page/PaidSubscriptionListPage.class.php +++ b/wcfsetup/install/files/lib/acp/page/PaidSubscriptionListPage.class.php @@ -2,19 +2,20 @@ namespace wcf\acp\page; -use wcf\data\paid\subscription\PaidSubscriptionList; -use wcf\page\SortablePage; +use wcf\page\AbstractGridViewPage; +use wcf\system\gridView\AbstractGridView; +use wcf\system\gridView\admin\PaidSubscriptionGridView; /** * Shows the list of paid subscriptions. * - * @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 * - * @property PaidSubscriptionList $objectList + * @property PaidSubscriptionGridView $gridView */ -class PaidSubscriptionListPage extends SortablePage +class PaidSubscriptionListPage extends AbstractGridViewPage { /** * @inheritDoc @@ -31,18 +32,9 @@ class PaidSubscriptionListPage extends SortablePage */ public $neededPermissions = ['admin.paidSubscription.canManageSubscription']; - /** - * @inheritDoc - */ - public $defaultSortField = 'showOrder'; - - /** - * @inheritDoc - */ - public $validSortFields = ['subscriptionID', 'title', 'showOrder', 'cost', 'subscriptionLength']; - - /** - * @inheritDoc - */ - public $objectListClassName = PaidSubscriptionList::class; + #[\Override] + protected function createGridViewController(): AbstractGridView + { + return new PaidSubscriptionGridView(); + } } diff --git a/wcfsetup/install/files/lib/bootstrap/com.woltlab.wcf.php b/wcfsetup/install/files/lib/bootstrap/com.woltlab.wcf.php index fe9029f8cd7..8bcd3f5d221 100644 --- a/wcfsetup/install/files/lib/bootstrap/com.woltlab.wcf.php +++ b/wcfsetup/install/files/lib/bootstrap/com.woltlab.wcf.php @@ -193,6 +193,9 @@ static function (\wcf\event\endpoint\ControllerCollecting $event) { $event->register(new \wcf\system\endpoint\controller\core\packages\updates\servers\DisableServer()); $event->register(new \wcf\system\endpoint\controller\core\packages\updates\servers\DeleteServer()); $event->register(new \wcf\system\endpoint\controller\core\packages\updates\servers\EnableServer()); + $event->register(new \wcf\system\endpoint\controller\core\paid\subscriptions\DeleteSubscription()); + $event->register(new \wcf\system\endpoint\controller\core\paid\subscriptions\DisableSubscription()); + $event->register(new \wcf\system\endpoint\controller\core\paid\subscriptions\EnableSubscription()); } ); diff --git a/wcfsetup/install/files/lib/data/paid/subscription/I18nPaidSubscriptionList.class.php b/wcfsetup/install/files/lib/data/paid/subscription/I18nPaidSubscriptionList.class.php new file mode 100644 index 00000000000..ddb2dccfea3 --- /dev/null +++ b/wcfsetup/install/files/lib/data/paid/subscription/I18nPaidSubscriptionList.class.php @@ -0,0 +1,31 @@ + + * + * @method PaidSubscription current() + * @method PaidSubscription[] getObjects() + * @method PaidSubscription|null getSingleObject() + * @method PaidSubscription|null search($objectID) + * @property PaidSubscription[] $objects + */ +class I18nPaidSubscriptionList extends I18nDatabaseObjectList +{ + /** + * @inheritDoc + */ + public $i18nFields = ['title' => 'titleI18n']; + + /** + * @inheritDoc + */ + public $className = PaidSubscription::class; +} diff --git a/wcfsetup/install/files/lib/event/gridView/admin/PaidSubscriptionGridViewInitialized.class.php b/wcfsetup/install/files/lib/event/gridView/admin/PaidSubscriptionGridViewInitialized.class.php new file mode 100644 index 00000000000..c7ba098dcbe --- /dev/null +++ b/wcfsetup/install/files/lib/event/gridView/admin/PaidSubscriptionGridViewInitialized.class.php @@ -0,0 +1,21 @@ + + * @since 6.2 + */ +final class PaidSubscriptionGridViewInitialized implements IPsr14Event +{ + public function __construct(public readonly PaidSubscriptionGridView $gridView) + { + } +} diff --git a/wcfsetup/install/files/lib/event/interaction/admin/PaidSubscriptionInteractionCollecting.class.php b/wcfsetup/install/files/lib/event/interaction/admin/PaidSubscriptionInteractionCollecting.class.php new file mode 100644 index 00000000000..2650962bc0e --- /dev/null +++ b/wcfsetup/install/files/lib/event/interaction/admin/PaidSubscriptionInteractionCollecting.class.php @@ -0,0 +1,21 @@ + + * @since 6.2 + */ +final class PaidSubscriptionInteractionCollecting implements IPsr14Event +{ + public function __construct(public readonly PaidSubscriptionInteractions $provider) + { + } +} diff --git a/wcfsetup/install/files/lib/system/endpoint/controller/core/paid/subscriptions/DeleteSubscription.class.php b/wcfsetup/install/files/lib/system/endpoint/controller/core/paid/subscriptions/DeleteSubscription.class.php new file mode 100644 index 00000000000..4e8eaf6aa80 --- /dev/null +++ b/wcfsetup/install/files/lib/system/endpoint/controller/core/paid/subscriptions/DeleteSubscription.class.php @@ -0,0 +1,42 @@ + + * @since 6.2 + */ +#[DeleteRequest('/core/paid/subscriptions/{id:\d+}')] +final class DeleteSubscription implements IController +{ + #[\Override] + public function __invoke(ServerRequestInterface $request, array $variables): ResponseInterface + { + $subscription = Helper::fetchObjectFromRequestParameter($variables['id'], PaidSubscription::class); + + $this->assertSubscriptionCanBeDeleted(); + + (new PaidSubscriptionAction([$subscription], 'delete'))->executeAction(); + + return new JsonResponse([]); + } + + private function assertSubscriptionCanBeDeleted(): void + { + WCF::getSession()->checkPermissions(['admin.paidSubscription.canManageSubscription']); + } +} diff --git a/wcfsetup/install/files/lib/system/endpoint/controller/core/paid/subscriptions/DisableSubscription.class.php b/wcfsetup/install/files/lib/system/endpoint/controller/core/paid/subscriptions/DisableSubscription.class.php new file mode 100644 index 00000000000..cec5d8842fc --- /dev/null +++ b/wcfsetup/install/files/lib/system/endpoint/controller/core/paid/subscriptions/DisableSubscription.class.php @@ -0,0 +1,47 @@ + + * @since 6.2 + */ +#[PostRequest('/core/paid/subscriptions/{id:\d+}/disable')] +final class DisableSubscription implements IController +{ + #[\Override] + public function __invoke(ServerRequestInterface $request, array $variables): ResponseInterface + { + $subscription = Helper::fetchObjectFromRequestParameter($variables['id'], PaidSubscription::class); + + $this->assertSubscriptionCanBeDisabled($subscription); + + (new PaidSubscriptionAction([$subscription], 'toggle'))->executeAction(); + + return new JsonResponse([]); + } + + private function assertSubscriptionCanBeDisabled(PaidSubscription $subscription): void + { + WCF::getSession()->checkPermissions(['admin.paidSubscription.canManageSubscription']); + + if ($subscription->isDisabled) { + throw new PermissionDeniedException(); + } + } +} diff --git a/wcfsetup/install/files/lib/system/endpoint/controller/core/paid/subscriptions/EnableSubscription.class.php b/wcfsetup/install/files/lib/system/endpoint/controller/core/paid/subscriptions/EnableSubscription.class.php new file mode 100644 index 00000000000..d503d86571b --- /dev/null +++ b/wcfsetup/install/files/lib/system/endpoint/controller/core/paid/subscriptions/EnableSubscription.class.php @@ -0,0 +1,47 @@ + + * @since 6.2 + */ +#[PostRequest('/core/paid/subscriptions/{id:\d+}/enable')] +final class EnableSubscription implements IController +{ + #[\Override] + public function __invoke(ServerRequestInterface $request, array $variables): ResponseInterface + { + $subscription = Helper::fetchObjectFromRequestParameter($variables['id'], PaidSubscription::class); + + $this->assertSubscriptionCanBeEnabled($subscription); + + (new PaidSubscriptionAction([$subscription], 'toggle'))->executeAction(); + + return new JsonResponse([]); + } + + private function assertSubscriptionCanBeEnabled(PaidSubscription $subscription): void + { + WCF::getSession()->checkPermissions(['admin.paidSubscription.canManageSubscription']); + + if (!$subscription->isDisabled) { + throw new PermissionDeniedException(); + } + } +} diff --git a/wcfsetup/install/files/lib/system/gridView/admin/PaidSubscriptionGridView.class.php b/wcfsetup/install/files/lib/system/gridView/admin/PaidSubscriptionGridView.class.php new file mode 100644 index 00000000000..63c22885eba --- /dev/null +++ b/wcfsetup/install/files/lib/system/gridView/admin/PaidSubscriptionGridView.class.php @@ -0,0 +1,161 @@ + + * @since 6.2 + */ +final class PaidSubscriptionGridView extends AbstractGridView +{ + public function __construct() + { + $this->addColumns([ + GridViewColumn::for('subscriptionID') + ->label('wcf.global.objectID') + ->renderer(new ObjectIdColumnRenderer()) + ->filter(new ObjectIdFilter()) + ->sortable(), + GridViewColumn::for('title') + ->label('wcf.global.title') + ->titleColumn() + ->renderer(new PhraseColumnRenderer()) + ->filter(new I18nTextFilter()) + ->sortable(sortByDatabaseColumn: 'titleI18n'), + GridViewColumn::for('description') + ->label('wcf.global.description') + ->filter(new I18nTextFilter()) + ->hidden(), + GridViewColumn::for('cost') + ->label('wcf.acp.paidSubscription.cost') + ->sortable() + ->filter(new NumericFilter()) + ->renderer( + new class extends NumberColumnRenderer { + #[\Override] + public function render(mixed $value, DatabaseObject $row): string + { + \assert($row instanceof PaidSubscription); + $locale = WCF::getLanguage()->getLocale(); + + return \NumberFormatter::create($locale, \NumberFormatter::CURRENCY) + ->formatCurrency($row->cost, $row->currency); + } + } + ), + GridViewColumn::for('currency') + ->label('wcf.acp.paidSubscription.currency') + ->filter(new SelectFilter($this->getAvailableCurrencies())) + ->hidden(), + GridViewColumn::for('subscriptionLength') + ->label('wcf.acp.paidSubscription.subscriptionLength') + ->sortable() + ->filter(new NumericFilter()) + ->renderer( + new class extends NumberColumnRenderer { + #[\Override] + public function render(mixed $value, DatabaseObject $row): string + { + \assert($row instanceof PaidSubscription); + if (!$row->subscriptionLength) { + return '∞'; + } + + return \sprintf( + "%s %d", + WCF::getLanguage()->get( + "wcf.acp.paidSubscription.subscriptionLengthUnit." . $row->subscriptionLengthUnit + ), + $row->subscriptionLength + ); + } + } + ), + GridViewColumn::for('showOrder') + ->label('wcf.global.showOrder') + ->sortable() + ->renderer(new NumberColumnRenderer()) + ->filter(new NumericFilter()) + ]); + + $provider = new PaidSubscriptionInteractions(); + $provider->addInteractions([ + new Divider(), + new EditInteraction(PaidSubscriptionEditForm::class) + ]); + $this->setInteractionProvider($provider); + $this->addQuickInteraction( + new ToggleInteraction( + "enable", + "core/paid/subscriptions/%s/enable", + "core/paid/subscriptions/%s/disable" + ) + ); + + $this->setSortField('showOrder'); + $this->addRowLink(new GridViewRowLink(PaidSubscriptionEditForm::class)); + } + + private function getAvailableCurrencies(): array + { + $availableCurrencies = []; + foreach (PaymentMethodHandler::getInstance()->getPaymentMethods() as $paymentMethod) { + $availableCurrencies = \array_merge( + $availableCurrencies, + $paymentMethod->getSupportedCurrencies() + ); + } + + $availableCurrencies = \array_unique($availableCurrencies); + \sort($availableCurrencies); + + return \array_combine($availableCurrencies, $availableCurrencies); + } + + #[\Override] + public function isAccessible(): bool + { + return \MODULE_PAID_SUBSCRIPTION + && WCF::getSession()->getPermission('admin.paidSubscription.canManageSubscription'); + } + + #[\Override] + protected function createObjectList(): DatabaseObjectList + { + return new I18nPaidSubscriptionList(); + } + + #[\Override] + protected function getInitializedEvent(): ?IPsr14Event + { + return new PaidSubscriptionGridViewInitialized($this); + } +} diff --git a/wcfsetup/install/files/lib/system/interaction/admin/PaidSubscriptionInteractions.class.php b/wcfsetup/install/files/lib/system/interaction/admin/PaidSubscriptionInteractions.class.php new file mode 100644 index 00000000000..a7a2bbad670 --- /dev/null +++ b/wcfsetup/install/files/lib/system/interaction/admin/PaidSubscriptionInteractions.class.php @@ -0,0 +1,37 @@ + + * @since 6.2 + */ +final class PaidSubscriptionInteractions extends AbstractInteractionProvider +{ + public function __construct() + { + $this->addInteractions([ + new DeleteInteraction("core/paid/subscriptions/%s"), + ]); + + EventHandler::getInstance()->fire( + new PaidSubscriptionInteractionCollecting($this) + ); + } + + #[\Override] + public function getObjectClassName(): string + { + return PaidSubscription::class; + } +} diff --git a/wcfsetup/install/lang/de.xml b/wcfsetup/install/lang/de.xml index 746b0707546..27719d2aae2 100644 --- a/wcfsetup/install/lang/de.xml +++ b/wcfsetup/install/lang/de.xml @@ -2119,6 +2119,7 @@ Die Datenbestände werden sorgfältig gepflegt, aber es ist nicht ausgeschlossen + diff --git a/wcfsetup/install/lang/en.xml b/wcfsetup/install/lang/en.xml index 442bb687ed4..20126ff15cd 100644 --- a/wcfsetup/install/lang/en.xml +++ b/wcfsetup/install/lang/en.xml @@ -2049,6 +2049,7 @@ If you have already bought the licenses for the listed apps, th +