diff --git a/wcfsetup/install/files/acp/js/WCF.ACP.js b/wcfsetup/install/files/acp/js/WCF.ACP.js index c34a1349ee2..989bea0b5ff 100644 --- a/wcfsetup/install/files/acp/js/WCF.ACP.js +++ b/wcfsetup/install/files/acp/js/WCF.ACP.js @@ -16,110 +16,6 @@ WCF.ACP = { }; */ WCF.ACP.Application = { }; -/** - * Namespace for ACP cronjob management. - */ -WCF.ACP.Cronjob = { }; - -/** - * Handles the manual execution of cronjobs. - */ -WCF.ACP.Cronjob.ExecutionHandler = Class.extend({ - /** - * notification object - * @var WCF.System.Notification - */ - _notification: null, - - /** - * action proxy - * @var WCF.Action.Proxy - */ - _proxy: null, - - /** - * Initializes WCF.ACP.Cronjob.ExecutionHandler object. - */ - init: function() { - this._proxy = new WCF.Action.Proxy({ - success: $.proxy(this._success, this) - }); - - $('.jsCronjobRow .jsExecuteButton').click($.proxy(this._click, this)); - - this._notification = new WCF.System.Notification(WCF.Language.get('wcf.global.success'), 'success'); - }, - - /** - * Handles a click on an execute button. - * - * @param object event - */ - _click: function(event) { - this._proxy.setOption('data', { - actionName: 'execute', - className: 'wcf\\data\\cronjob\\CronjobAction', - objectIDs: [ $(event.target).data('objectID') ] - }); - - this._proxy.sendRequest(); - }, - - /** - * Handles successful cronjob execution. - * - * @param object data - * @param string textStatus - * @param jQuery jqXHR - */ - _success: function(data, textStatus, jqXHR) { - $('.jsCronjobRow').each($.proxy(function(index, row) { - var $button = $(row).find('.jsExecuteButton'); - var $objectID = ($button).data('objectID'); - - if (WCF.inArray($objectID, data.objectIDs)) { - if (data.returnValues[$objectID]) { - // insert feedback here - $(row).find('td.columnNextExec').html(data.returnValues[$objectID].formatted); - $(row).wcfHighlight(); - } - - this._notification.show(); - - return false; - } - }, this)); - } -}); - -/** - * Handles the cronjob log list. - */ -WCF.ACP.Cronjob.LogList = Class.extend({ - /** - * Initializes WCF.ACP.Cronjob.LogList object. - */ - init: function() { - // bind event listener to delete cronjob log button - $('.jsCronjobLogDelete').click(function() { - WCF.System.Confirmation.show(WCF.Language.get('wcf.acp.cronjob.log.clear.confirm'), function(action) { - if (action == 'confirm') { - new WCF.Action.Proxy({ - autoSend: true, - data: { - actionName: 'clearAll', - className: 'wcf\\data\\cronjob\\log\\CronjobLogAction' - }, - success: function() { - window.location.reload(); - } - }); - } - }); - }); - } -}); - /** * Namespace for ACP package management. */ diff --git a/wcfsetup/install/files/acp/templates/cronjobList.tpl b/wcfsetup/install/files/acp/templates/cronjobList.tpl index 1a910d16fdd..a5e21562a15 100644 --- a/wcfsetup/install/files/acp/templates/cronjobList.tpl +++ b/wcfsetup/install/files/acp/templates/cronjobList.tpl @@ -1,14 +1,8 @@ {include file='header' pageTitle='wcf.acp.cronjob.list'} - -
-

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

+

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

{lang}wcf.acp.cronjob.subtitle{/lang}

@@ -21,112 +15,8 @@
-{hascontent} -
- {content}{pages print=true assign=pagesLinks controller="CronjobList" link="pageNo=%d&sortField=$sortField&sortOrder=$sortOrder"}{/content} -
-{/hascontent} - -{hascontent} -
- - - - - - - - - - {event name='columnHeads'} - - - - - {content} - {foreach from=$objects item=cronjob} - - - - - - - - - {event name='columns'} - - {/foreach} - {/content} - -
{lang}wcf.global.objectID{/lang}{lang}wcf.acp.cronjob.expression{/lang}{lang}wcf.acp.cronjob.description{/lang}{lang}wcf.acp.package.name{/lang}{lang}wcf.acp.cronjob.nextExec{/lang}
- - - {if $cronjob->canBeDisabled()} - {objectAction action="toggle" isDisabled=$cronjob->isDisabled} - {else} - {if !$cronjob->isDisabled} - - {icon name='square-check'} - - {else} - - {icon name='square'} - - {/if} - {/if} - - {if $cronjob->isEditable()} - {icon name='pencil'} - {else} - - {icon name='pencil'} - - {/if} - {if $cronjob->isDeletable()} - {objectAction action="delete" objectTitle=$cronjob->getDescription()} - {else} - - {icon name='xmark'} - - {/if} - - {event name='rowButtons'} - {@$cronjob->cronjobID} - {$cronjob->getExpression()} - - {if $cronjob->isEditable()} - {$cronjob->getDescription()} - {else} - {$cronjob->getDescription()} - {/if} - - {$cronjob->getPackage()} - - {if !$cronjob->isDisabled && $cronjob->nextExec != 1} - {@$cronjob->nextExec|plainTime} - {/if} -
-
-{hascontentelse} - {lang}wcf.global.noItems{/lang} -{/hascontent} - - +
+ {unsafe:$gridView->render()} +
{include file='footer'} diff --git a/wcfsetup/install/files/lib/acp/page/CronjobListPage.class.php b/wcfsetup/install/files/lib/acp/page/CronjobListPage.class.php index 598a7c44f23..722c8f55921 100755 --- a/wcfsetup/install/files/lib/acp/page/CronjobListPage.class.php +++ b/wcfsetup/install/files/lib/acp/page/CronjobListPage.class.php @@ -2,19 +2,20 @@ namespace wcf\acp\page; -use wcf\data\cronjob\I18nCronjobList; -use wcf\page\SortablePage; +use wcf\page\AbstractGridViewPage; +use wcf\system\gridView\AbstractGridView; +use wcf\system\gridView\admin\CronjobGridView; /** * Shows information about configured cron jobs. * - * @author Alexander Ebert - * @copyright 2001-2019 WoltLab GmbH - * @license GNU Lesser General Public License + * @author Olaf Braun, Alexander Ebert + * @copyright 2001-2025 WoltLab GmbH + * @license GNU Lesser General Public License * - * @property I18nCronjobList $objectList + * @property CronjobGridView $gridView */ -class CronjobListPage extends SortablePage +class CronjobListPage extends AbstractGridViewPage { /** * @inheritDoc @@ -26,38 +27,9 @@ class CronjobListPage extends SortablePage */ public $neededPermissions = ['admin.management.canManageCronjob']; - /** - * @inheritDoc - */ - public $defaultSortField = 'descriptionI18n'; - - /** - * @inheritDoc - */ - public $itemsPerPage = 100; - - /** - * @inheritDoc - */ - public $validSortFields = [ - 'cronjobID', - 'nextExec', - 'descriptionI18n', - 'packageID', - ]; - - /** - * @inheritDoc - */ - public $objectListClassName = I18nCronjobList::class; - - /** - * @inheritDoc - */ - public function initObjectList() + #[\Override] + protected function createGridViewController(): AbstractGridView { - parent::initObjectList(); - - $this->sqlOrderBy = "cronjob." . $this->sortField . " " . $this->sortOrder; + return new CronjobGridView(); } } diff --git a/wcfsetup/install/files/lib/bootstrap/com.woltlab.wcf.php b/wcfsetup/install/files/lib/bootstrap/com.woltlab.wcf.php index 9b972a7fa94..e40f59fa0e6 100644 --- a/wcfsetup/install/files/lib/bootstrap/com.woltlab.wcf.php +++ b/wcfsetup/install/files/lib/bootstrap/com.woltlab.wcf.php @@ -162,6 +162,10 @@ static function (\wcf\event\endpoint\ControllerCollecting $event) { $event->register(new \wcf\system\endpoint\controller\core\articles\PublishArticle()); $event->register(new \wcf\system\endpoint\controller\core\articles\UnpublishArticle()); $event->register(new \wcf\system\endpoint\controller\core\attachments\DeleteAttachment()); + $event->register(new \wcf\system\endpoint\controller\core\cronjobs\EnableCronjob()); + $event->register(new \wcf\system\endpoint\controller\core\cronjobs\DisableCronjob()); + $event->register(new \wcf\system\endpoint\controller\core\cronjobs\DeleteCronjob()); + $event->register(new \wcf\system\endpoint\controller\core\cronjobs\ExecuteCronjob()); } ); diff --git a/wcfsetup/install/files/lib/event/gridView/admin/CronjobGridViewInitialized.class.php b/wcfsetup/install/files/lib/event/gridView/admin/CronjobGridViewInitialized.class.php new file mode 100644 index 00000000000..d52281f098b --- /dev/null +++ b/wcfsetup/install/files/lib/event/gridView/admin/CronjobGridViewInitialized.class.php @@ -0,0 +1,21 @@ + + * @since 6.2 + */ +final class CronjobGridViewInitialized implements IPsr14Event +{ + public function __construct(public readonly CronjobGridView $gridView) + { + } +} diff --git a/wcfsetup/install/files/lib/event/interaction/admin/CronjobInteractionCollecting.class.php b/wcfsetup/install/files/lib/event/interaction/admin/CronjobInteractionCollecting.class.php new file mode 100644 index 00000000000..78e13d6081a --- /dev/null +++ b/wcfsetup/install/files/lib/event/interaction/admin/CronjobInteractionCollecting.class.php @@ -0,0 +1,21 @@ + + * @since 6.2 + */ +final class CronjobInteractionCollecting implements IPsr14Event +{ + public function __construct(public readonly CronjobInteractions $provider) + { + } +} diff --git a/wcfsetup/install/files/lib/event/interaction/bulk/admin/CronjobBulkInteractionCollecting.class.php b/wcfsetup/install/files/lib/event/interaction/bulk/admin/CronjobBulkInteractionCollecting.class.php new file mode 100644 index 00000000000..738f2fd1c9d --- /dev/null +++ b/wcfsetup/install/files/lib/event/interaction/bulk/admin/CronjobBulkInteractionCollecting.class.php @@ -0,0 +1,21 @@ + + * @since 6.2 + */ +final class CronjobBulkInteractionCollecting implements IPsr14Event +{ + public function __construct(public readonly CronjobBulkInteractions $provider) + { + } +} diff --git a/wcfsetup/install/files/lib/system/endpoint/controller/core/cronjobs/DeleteCronjob.class.php b/wcfsetup/install/files/lib/system/endpoint/controller/core/cronjobs/DeleteCronjob.class.php new file mode 100644 index 00000000000..eeeb4335469 --- /dev/null +++ b/wcfsetup/install/files/lib/system/endpoint/controller/core/cronjobs/DeleteCronjob.class.php @@ -0,0 +1,47 @@ + + * @since 6.2 + */ +#[DeleteRequest('/core/cronjobs/{id:\d+}')] +final class DeleteCronjob implements IController +{ + #[\Override] + public function __invoke(ServerRequestInterface $request, array $variables): ResponseInterface + { + $cronjob = Helper::fetchObjectFromRequestParameter($variables['id'], Cronjob::class); + + $this->assertCronjobCanBeDeleted($cronjob); + + (new CronjobAction([$cronjob], 'delete'))->executeAction(); + + return new JsonResponse([]); + } + + private function assertCronjobCanBeDeleted(Cronjob $cronjob): void + { + WCF::getSession()->checkPermissions(['admin.management.canManageCronjob']); + + if (!$cronjob->isDeletable()) { + throw new PermissionDeniedException(); + } + } +} diff --git a/wcfsetup/install/files/lib/system/endpoint/controller/core/cronjobs/DisableCronjob.class.php b/wcfsetup/install/files/lib/system/endpoint/controller/core/cronjobs/DisableCronjob.class.php new file mode 100644 index 00000000000..b9607383788 --- /dev/null +++ b/wcfsetup/install/files/lib/system/endpoint/controller/core/cronjobs/DisableCronjob.class.php @@ -0,0 +1,51 @@ + + * @since 6.2 + */ +#[PostRequest('/core/cronjobs/{id:\d+}/disable')] +final class DisableCronjob implements IController +{ + #[\Override] + public function __invoke(ServerRequestInterface $request, array $variables): ResponseInterface + { + $cronjob = Helper::fetchObjectFromRequestParameter($variables['id'], Cronjob::class); + + $this->assertCronjobCanBeDisabled($cronjob); + + (new CronjobAction([$cronjob], 'toggle'))->executeAction(); + + return new JsonResponse([]); + } + + private function assertCronjobCanBeDisabled(Cronjob $cronjob): void + { + WCF::getSession()->checkPermissions(['admin.management.canManageCronjob']); + + if ($cronjob->canBeDisabled()) { + throw new PermissionDeniedException(); + } + + if ($cronjob->isDisabled) { + throw new PermissionDeniedException(); + } + } +} diff --git a/wcfsetup/install/files/lib/system/endpoint/controller/core/cronjobs/EnableCronjob.class.php b/wcfsetup/install/files/lib/system/endpoint/controller/core/cronjobs/EnableCronjob.class.php new file mode 100644 index 00000000000..c17b690a031 --- /dev/null +++ b/wcfsetup/install/files/lib/system/endpoint/controller/core/cronjobs/EnableCronjob.class.php @@ -0,0 +1,51 @@ + + * @since 6.2 + */ +#[PostRequest('/core/cronjobs/{id:\d+}/enable')] +final class EnableCronjob implements IController +{ + #[\Override] + public function __invoke(ServerRequestInterface $request, array $variables): ResponseInterface + { + $cronjob = Helper::fetchObjectFromRequestParameter($variables['id'], Cronjob::class); + + $this->assertCronjobCanBeEnabled($cronjob); + + (new CronjobAction([$cronjob], 'toggle'))->executeAction(); + + return new JsonResponse([]); + } + + private function assertCronjobCanBeEnabled(Cronjob $cronjob): void + { + WCF::getSession()->checkPermissions(['admin.management.canManageCronjob']); + + if ($cronjob->canBeDisabled()) { + throw new PermissionDeniedException(); + } + + if (!$cronjob->isDisabled) { + throw new PermissionDeniedException(); + } + } +} diff --git a/wcfsetup/install/files/lib/system/endpoint/controller/core/cronjobs/ExecuteCronjob.class.php b/wcfsetup/install/files/lib/system/endpoint/controller/core/cronjobs/ExecuteCronjob.class.php new file mode 100644 index 00000000000..233791e23c1 --- /dev/null +++ b/wcfsetup/install/files/lib/system/endpoint/controller/core/cronjobs/ExecuteCronjob.class.php @@ -0,0 +1,42 @@ + + * @since 6.2 + */ +#[PostRequest('/core/cronjobs/{id:\d+}/execute')] +final class ExecuteCronjob implements IController +{ + #[\Override] + public function __invoke(ServerRequestInterface $request, array $variables): ResponseInterface + { + $cronjob = Helper::fetchObjectFromRequestParameter($variables['id'], Cronjob::class); + + $this->assertCronjobCanBeExecuted(); + + (new CronjobAction([$cronjob], 'execute'))->executeAction(); + + return new JsonResponse([]); + } + + private function assertCronjobCanBeExecuted(): void + { + WCF::getSession()->checkPermissions(['admin.management.canManageCronjob']); + } +} diff --git a/wcfsetup/install/files/lib/system/gridView/admin/CronjobGridView.class.php b/wcfsetup/install/files/lib/system/gridView/admin/CronjobGridView.class.php new file mode 100644 index 00000000000..0da1f93a65f --- /dev/null +++ b/wcfsetup/install/files/lib/system/gridView/admin/CronjobGridView.class.php @@ -0,0 +1,141 @@ + + * @since 6.2 + */ +final class CronjobGridView extends AbstractGridView +{ + public function __construct() + { + $this->addColumns([ + GridViewColumn::for('cronjobID') + ->label('wcf.global.objectID') + ->renderer(new ObjectIdColumnRenderer()) + ->sortable(), + GridViewColumn::for('expression') + ->label('wcf.acp.cronjob.expression') + ->renderer( + new class extends AbstractColumnRenderer { + #[\Override] + public function render(mixed $value, DatabaseObject $row): string + { + \assert($row instanceof Cronjob); + + return \sprintf('%s', $row->getExpression()); + } + } + ), + GridViewColumn::for('description') + ->label('wcf.acp.cronjob.description') + ->sortable(sortByDatabaseColumn: 'descriptionI18n') + ->filter(new I18nTextFilter()) + ->renderer(new PhraseColumnRenderer()) + ->titleColumn(), + GridViewColumn::for('packageID') + ->label('wcf.acp.package.name') + ->filter(new SelectFilter(PackageCache::getInstance()->getPackages())) + ->renderer( + new class extends AbstractColumnRenderer { + #[\Override] + public function render(mixed $value, DatabaseObject $row): string + { + \assert($row instanceof Cronjob); + + return StringUtil::encodeHTML($row->getPackage()->getTitle()); + } + } + ) + ->sortable(), + GridViewColumn::for('nextExec') + ->label('wcf.acp.cronjob.nextExec') + ->renderer( + new class extends TimeColumnRenderer { + #[\Override] + public function render(mixed $value, DatabaseObject $row): string + { + \assert($row instanceof Cronjob); + + if ($row->isDisabled || $row->nextExec === 1) { + return ''; + } + + return parent::render($value, $row); + } + } + ) + ->filter(new TimeFilter()) + ->sortable(), + ]); + + $interaction = new CronjobInteractions(); + $interaction->addInteractions([ + new Divider(), + new EditInteraction(CronjobEditForm::class, static fn(Cronjob $cronjob) => $cronjob->isEditable()), + ]); + + $this->addQuickInteraction( + new ToggleInteraction( + 'enable', + 'core/cronjobs/%s/enable', + 'core/cronjobs/%s/disable', + isAvailableCallback: static fn(Cronjob $cronjob) => $cronjob->canBeDisabled() + ) + ); + $this->setInteractionProvider($interaction); + $this->setBulkInteractionProvider(new CronjobBulkInteractions()); + + $this->addRowLink(new GridViewRowLink(CronjobEditForm::class)); + $this->setSortField('description'); + } + + #[\Override] + public function isAccessible(): bool + { + return WCF::getSession()->getPermission('admin.management.canManageCronjob'); + } + + #[\Override] + protected function createObjectList(): DatabaseObjectList + { + return new I18nCronjobList(); + } + + #[\Override] + protected function getInitializedEvent(): ?IPsr14Event + { + return new CronjobGridViewInitialized($this); + } +} diff --git a/wcfsetup/install/files/lib/system/interaction/admin/CronjobInteractions.class.php b/wcfsetup/install/files/lib/system/interaction/admin/CronjobInteractions.class.php new file mode 100644 index 00000000000..114b764d0bd --- /dev/null +++ b/wcfsetup/install/files/lib/system/interaction/admin/CronjobInteractions.class.php @@ -0,0 +1,39 @@ + + * @since 6.2 + */ +final class CronjobInteractions extends AbstractInteractionProvider +{ + public function __construct() + { + $this->addInteractions([ + new DeleteInteraction('core/cronjobs/%s', static fn(Cronjob $cronjob) => $cronjob->isDeletable()), + new RpcInteraction('execute', 'core/cronjobs/%s/execute', 'wcf.acp.cronjob.execute') + ]); + + EventHandler::getInstance()->fire( + new CronjobInteractionCollecting($this) + ); + } + + #[\Override] + public function getObjectClassName(): string + { + return Cronjob::class; + } +} diff --git a/wcfsetup/install/files/lib/system/interaction/bulk/admin/CronjobBulkInteractions.class.php b/wcfsetup/install/files/lib/system/interaction/bulk/admin/CronjobBulkInteractions.class.php new file mode 100644 index 00000000000..2dc39fdadd2 --- /dev/null +++ b/wcfsetup/install/files/lib/system/interaction/bulk/admin/CronjobBulkInteractions.class.php @@ -0,0 +1,40 @@ + + * @since 6.2 + */ +final class CronjobBulkInteractions extends AbstractBulkInteractionProvider +{ + public function __construct() + { + $this->addInteractions([ + new BulkDeleteInteraction('core/cronjobs/%s', static fn(Cronjob $cronjob) => $cronjob->isDeletable()), + new BulkRpcInteraction('execute', 'core/cronjobs/%s/execute', 'wcf.acp.cronjob.execute') + ]); + + EventHandler::getInstance()->fire( + new CronjobBulkInteractionCollecting($this) + ); + } + + #[\Override] + public function getObjectListClassName(): string + { + return CronjobList::class; + } +}