From f72493adab19081bcee662be4e9c5dc8c734c798 Mon Sep 17 00:00:00 2001 From: panlatent Date: Sat, 22 Jun 2024 17:20:52 +0800 Subject: [PATCH] Update --- src/Plugin.php | 46 ++++----- src/actions/Command.php | 38 +++++++ src/actions/Console.php | 41 ++++---- src/base/Timer.php | 2 +- src/controllers/SchedulesController.php | 2 +- src/errors/ActionException.php | 10 ++ src/models/Schedule.php | 64 ++++++++++-- src/records/Action.php | 11 ++- src/records/Schedule.php | 1 - src/records/Timer.php | 7 +- src/services/Actions.php | 37 +++---- src/services/Schedules.php | 63 ++++++++---- src/services/Timers.php | 61 ++++-------- .../_components/actions/Command/settings.twig | 30 ++++++ .../_components/actions/Console/settings.twig | 4 +- src/templates/_components/timers/Cron.twig | 9 +- .../_components/utilities/ActionRunner.twig | 10 ++ src/templates/_edit.twig | 98 ++++++++----------- src/templates/_includes/forms.twig | 6 +- src/templates/_includes/forms/action.twig | 67 +++++++++++++ src/templates/index.twig | 54 +++++----- src/timers/Cron.php | 11 +++ src/utilities/ActionRunner.php | 25 +++++ 23 files changed, 456 insertions(+), 241 deletions(-) create mode 100644 src/errors/ActionException.php create mode 100644 src/templates/_components/actions/Command/settings.twig create mode 100644 src/templates/_components/utilities/ActionRunner.twig create mode 100644 src/templates/_includes/forms/action.twig create mode 100644 src/utilities/ActionRunner.php diff --git a/src/Plugin.php b/src/Plugin.php index a635a2f..6178a04 100644 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -10,11 +10,13 @@ use Craft; use craft\base\Model; use craft\events\RebuildConfigEvent; +use craft\events\RegisterComponentTypesEvent; use craft\events\RegisterUrlRulesEvent; use craft\events\RegisterUserPermissionsEvent; use craft\helpers\UrlHelper; use craft\services\ProjectConfig; use craft\services\UserPermissions; +use craft\services\Utilities; use craft\web\Response; use craft\web\twig\variables\CraftVariable; use craft\web\UrlManager; @@ -25,6 +27,7 @@ use panlatent\schedule\services\Schedules; use panlatent\schedule\services\Timers; use panlatent\schedule\user\Permissions; +use panlatent\schedule\utilities\ActionRunner; use panlatent\schedule\web\twig\CraftVariableBehavior; use yii\base\Event; use yii\console\Application as ConsoleApplication; @@ -97,6 +100,7 @@ public function init(): void $this->_registerProjectConfigEvents(); $this->_registerUserPermissions(); $this->_registerVariables(); + $this->_registerUtilities(); if ($this->settings->enabledWebCron) { $this->_registerWebCron(); @@ -111,29 +115,6 @@ public function getCpNavItem(): ?array { $ret = parent::getCpNavItem(); $ret['label'] = $this->getSettings()->getCustomCpNavName() ?? $this->name; - - $user = Craft::$app->getUser(); - if ($user->checkPermission(Permissions::MANAGE_SCHEDULES)) { - $ret['subnav']['schedules'] = [ - 'label' => Craft::t('schedule', 'Schedules'), - 'url' => 'schedule', - ]; - } - - if ($user->checkPermission(Permissions::MANAGE_LOGS)) { - $ret['subnav']['logs'] = [ - 'label' => Craft::t('schedule', 'Logs'), - 'url' => 'schedule/logs', - ]; - } - - if (Craft::$app->getConfig()->getGeneral()->allowAdminChanges && $user->getIsAdmin()) { - $ret['subnav']['settings'] = [ - 'label' => Craft::t('schedule', 'Settings'), - 'url' => 'schedule/settings', - ]; - } - return $ret; } @@ -216,11 +197,11 @@ public function _registerCpRoutes(): void 'schedule/groups/' => ['template' => 'schedule'], 'schedule/new' => 'schedule/schedules/edit-schedule', 'schedule/' => 'schedule/schedules/edit-schedule', - 'schedule//timers' => ['template' => 'schedule/timers'], - 'schedule//timers/new' => 'schedule/timers/edit-timer', - 'schedule//timers/' => 'schedule/timers/edit-timer', - 'schedule//logs' => ['template' => 'schedule/_logs'], - 'schedule//logs/' => ['template' => 'schedule/logs/_view'], +// 'schedule//timers' => ['template' => 'schedule/timers'], +// 'schedule//timers/new' => 'schedule/timers/edit-timer', +// 'schedule//timers/' => 'schedule/timers/edit-timer', +// 'schedule//logs' => ['template' => 'schedule/_logs'], +// 'schedule//logs/' => ['template' => 'schedule/logs/_view'], ]); }); } @@ -237,10 +218,17 @@ private function _registerVariables(): void }); } - private function _registerWebCron() + private function _registerWebCron(): void { Event::on(UrlManager::class, UrlManager::EVENT_REGISTER_SITE_URL_RULES, function (RegisterUrlRulesEvent $event) { $event->rules[$this->settings->endpoint] = 'schedule/web-cron/trigger'; }); } + + private function _registerUtilities(): void + { + Event::on(Utilities::class, Utilities::EVENT_REGISTER_UTILITIES, function(RegisterComponentTypesEvent $event) { + $event->types[] = ActionRunner::class; + }); + } } \ No newline at end of file diff --git a/src/actions/Command.php b/src/actions/Command.php index 2eb9375..21ba383 100644 --- a/src/actions/Command.php +++ b/src/actions/Command.php @@ -5,6 +5,7 @@ use Craft; use panlatent\craft\actions\abstract\Action; use panlatent\craft\actions\abstract\ContextInterface; +use panlatent\schedule\Plugin; use Symfony\Component\Process\Process; class Command extends Action @@ -52,4 +53,41 @@ public function execute(ContextInterface $context): bool return $process->isSuccessful(); } + + public function getSettingsHtml(): ?string + { + $suggestions = []; + + $process = new Process([Plugin::getInstance()->getSettings()->getCliPath(), 'craft', 'help/list'], Craft::getAlias('@root')); + $process->run(); + + if ($process->isSuccessful()) { + $lines = explode("\n", mb_convert_encoding($process->getOutput(), mb_internal_encoding())); + + $data = []; + foreach ($lines as $line) { + if (($pos = strpos($line, '/')) === false) { + $data[$line] = []; + continue; + } + + $data[substr($line, 0, $pos)][] = [ + 'name' => $line, + 'hint' => $line, + ]; + } + + foreach ($data as $label => $commandSuggestions) { + $suggestions[] = [ + 'label' => $label, + 'data' => $commandSuggestions, + ]; + } + } + + return Craft::$app->getView()->renderTemplate('schedule/_components/actions/Command/settings', [ + 'schedule' => $this, + 'suggestions' => $suggestions, + ]); + } } \ No newline at end of file diff --git a/src/actions/Console.php b/src/actions/Console.php index 1d78754..25120b4 100644 --- a/src/actions/Console.php +++ b/src/actions/Console.php @@ -31,38 +31,35 @@ public function execute(ContextInterface $context): bool public function getSettingsHtml(): ?string { - $suggestions = []; + return Craft::$app->getView()->renderTemplate('schedule/_components/actions/Console/settings', [ + 'schedule' => $this, + ]); + } - $process = new Process([Plugin::getInstance()->getSettings()->getCliPath(), 'craft', 'help/list'], Craft::getAlias('@root')); + public function getCommandOptions(): array + { + $options = []; + + $process = new Process([Plugin::getInstance()->getSettings()->getCliPath(), 'craft', 'help'], Craft::getAlias('@root')); $process->run(); if ($process->isSuccessful()) { $lines = explode("\n", mb_convert_encoding($process->getOutput(), mb_internal_encoding())); - - $data = []; foreach ($lines as $line) { - if (($pos = strpos($line, '/')) === false) { - $data[$line] = []; + if (str_starts_with($line, '-')) { + $options[] = ['optgroup' => substr($line, 2)]; continue; } - - $data[substr($line, 0, $pos)][] = [ - 'name' => $line, - 'hint' => $line, - ]; - } - - foreach ($data as $label => $commandSuggestions) { - $suggestions[] = [ - 'label' => $label, - 'data' => $commandSuggestions, - ]; + if (preg_match('#^\s*(\w+/\w+)\s*(?:\(\w+\)|)\s+(.+)\s*$#', $line, $match)) { + $options[] = [ + 'label' => $match[1] . ' - ' . $match[2], //substr($line, 0, $pos), + 'value' => $match[1], + ]; + } } } - return Craft::$app->getView()->renderTemplate('schedule/_components/schedules/Console/settings', [ - 'schedule' => $this, - 'suggestions' => $suggestions, - ]); + return $options; } + } \ No newline at end of file diff --git a/src/base/Timer.php b/src/base/Timer.php index 2f35826..3a61c5d 100644 --- a/src/base/Timer.php +++ b/src/base/Timer.php @@ -44,7 +44,7 @@ abstract class Timer extends SavableComponent implements TimerInterface public function rules(): array { $rules = parent::rules(); - $rules[] = [['scheduleId', 'enabled'], 'required']; + $rules[] = [['enabled'], 'required']; $rules[] = [['scheduleId', 'sortOrder'], 'integer']; $rules[] = [['enabled'], 'boolean']; return $rules; diff --git a/src/controllers/SchedulesController.php b/src/controllers/SchedulesController.php index 41341e7..b1de28c 100644 --- a/src/controllers/SchedulesController.php +++ b/src/controllers/SchedulesController.php @@ -115,7 +115,7 @@ public function actionEditSchedule(int $scheduleId = null, Schedule $schedule = } } - $isNewSchedule = !$schedule; + $isNewSchedule = !$schedule->id; $allGroups = $schedules->getAllGroups(); $allActionTypes = Plugin::getInstance()->actions->getAllActionTypes(); diff --git a/src/errors/ActionException.php b/src/errors/ActionException.php new file mode 100644 index 0000000..64cfee3 --- /dev/null +++ b/src/errors/ActionException.php @@ -0,0 +1,10 @@ +_info; } + private ?TimerInterface $_timer = null; + + public function getTimer(): TimerInterface + { + if ($this->_timer === null) { + $this->_timer = Plugin::getInstance()->timers->getTimerByScheduleId($this->id); + } + return $this->_timer; + } + + public function setTimer(TimerInterface $timer): void + { + + } + public function canRun(): bool { return true; @@ -115,4 +145,26 @@ protected function getLogger(): LoggerInterface { return new LogAdapter(Craft::$app->getLog()->getLogger(), 'schedule'); } + + protected function defineRules(): array + { + return [ + [['name', 'handle'], 'required'], + [['id', 'groupId'], 'integer'], + [['name', 'handle', 'description'], 'string'], + [['handle'], UniqueValidator::class, 'targetClass' => ScheduleRecord::class, 'targetAttribute' => 'handle'], + [['handle'], HandleValidator::class], + [['static'], 'boolean'], + [['action'], function($attribute) {if (!$this->$attribute->validate()) { + $this->addError($attribute, array_first($this->$attribute->getFirstErrors())); + }}], + + [['timer'], function($attribute) { + if (!$this->$attribute->validate()) { + $errors = $this->$attribute->getFirstErrors(); + $this->addError($attribute, reset($errors)); + } + }], + ]; + } } \ No newline at end of file diff --git a/src/records/Action.php b/src/records/Action.php index 5cb02b6..dc8575e 100644 --- a/src/records/Action.php +++ b/src/records/Action.php @@ -3,8 +3,17 @@ namespace panlatent\schedule\records; use craft\db\ActiveRecord; +use panlatent\schedule\db\Table; +/** + * @property int $id + * @property string $type + * @property string $settings + */ class Action extends ActiveRecord { - + public static function tableName(): string + { + return Table::ACTIONS; + } } \ No newline at end of file diff --git a/src/records/Schedule.php b/src/records/Schedule.php index 3d03156..ff11f49 100644 --- a/src/records/Schedule.php +++ b/src/records/Schedule.php @@ -23,7 +23,6 @@ * @property int $onSuccess * @property int $onFailed * @property bool $enabled - * @property bool $enabledLog * @property int $sortOrder * @author Panlatent */ diff --git a/src/records/Timer.php b/src/records/Timer.php index b8a1519..6d2e238 100644 --- a/src/records/Timer.php +++ b/src/records/Timer.php @@ -17,11 +17,6 @@ * @property int $id * @property int $scheduleId * @property string $type - * @property string $minute - * @property string $hour - * @property string $day - * @property string $month - * @property string $week * @property string $settings * @property bool $enabled * @property int $sortOrder @@ -34,6 +29,6 @@ class Timer extends ActiveRecord */ public static function tableName(): string { - return Table::SCHEDULETIMERS; + return Table::TIMERS; } } \ No newline at end of file diff --git a/src/services/Actions.php b/src/services/Actions.php index ebcc55e..6392212 100644 --- a/src/services/Actions.php +++ b/src/services/Actions.php @@ -17,11 +17,12 @@ use panlatent\schedule\actions\HttpRequest; use panlatent\schedule\actions\SendEmail; use panlatent\schedule\db\Table; +use panlatent\schedule\errors\ActionException; use panlatent\schedule\events\ActionEvent; use panlatent\schedule\log\LogAdapter; use panlatent\schedule\models\Context; +use panlatent\schedule\records\Action as ActionRecord; use yii\base\Component; -use yii\web\Request; class Actions extends Component { @@ -136,25 +137,19 @@ public function saveAction(ActionInterface $action, bool $runValidation = true): $transaction = Craft::$app->getDb()->beginTransaction(); try { - Craft::$app->getDb() - ->createCommand() - ->upsert(Table::ACTIONS, [ - 'type' => get_class($action), - 'settings' => $action->getSettings(), - 'dateUpdated' => $action->dateUpdated, - 'dateCreated' => $action->dateCreated, - 'uid' => $action->uid, - ], [ - 'type' => get_class($action), - 'settings' => $action->getSettings(), - 'dateUpdated' => $action->dateUpdated, - ]) - ->execute(); - - if ($isNew) { - $action->id = $record->id; + if (!$isNew) { + $record = ActionRecord::findOne(['id' => $action->id]); + if (!$record) { + throw new ActionException("No action exists with the ID: “{$action->id}“."); + } + } else { + $record = new ActionRecord(); } + $record->type = get_class($action); + $record->settings = json_encode($action->getSettings(), JSON_THROW_ON_ERROR); + $record->save(false); + $transaction->commit(); } catch (\Throwable $exception) { $transaction->rollBack(); @@ -162,9 +157,9 @@ public function saveAction(ActionInterface $action, bool $runValidation = true): throw $exception; } - // If has some respositories ... - // $this->__METHOD__[$action->id] = $action; - // $this->__METHOD__[$action->handle] = $action; + if ($isNew) { + $action->id = $record->id; + } if ($this->hasEventHandlers(self::EVENT_AFTER_SAVE_ACTION)) { $this->trigger(self::EVENT_AFTER_SAVE_ACTION, new ActionEvent([ diff --git a/src/services/Schedules.php b/src/services/Schedules.php index a5a1950..1de377a 100644 --- a/src/services/Schedules.php +++ b/src/services/Schedules.php @@ -16,8 +16,10 @@ use craft\helpers\StringHelper; use craft\web\Request; use panlatent\schedule\db\Table; +use panlatent\schedule\errors\ActionException; use panlatent\schedule\errors\ScheduleException; use panlatent\schedule\errors\ScheduleGroupException; +use panlatent\schedule\errors\TimerException; use panlatent\schedule\events\ScheduleEvent; use panlatent\schedule\events\ScheduleGroupEvent; use panlatent\schedule\models\Schedule; @@ -419,13 +421,17 @@ public function createScheduleFromRequest(Request $request = null): Schedule $request = Craft::$app->getRequest(); } - $actionType = $request->getRequiredBodyParam('actionType'); - $actionConfig = $request->getBodyParam('actionTypes.' . $actionType) ?? []; - $action = Plugin::getInstance()->actions->createAction(['type' => $actionType] + $actionConfig); +// try { + $actionType = $request->getRequiredBodyParam('actionType'); + $actionConfig = $request->getBodyParam('actionTypes.' . $actionType) ?? []; + $action = Plugin::getInstance()->actions->createAction(['type' => $actionType] + $actionConfig); - $timerType = $request->getBodyParam('timerType'); - $timerConfig = $request->getBodyParam('timeTypes.' . $timerType) ?? []; - $timer = Plugin::getInstance()->timers->createTimer(['type' => $timerType] + $timerConfig); + $timerType = $request->getRequiredBodyParam('timerType'); + $timerConfig = $request->getBodyParam('timerTypes.' . $timerType) ?? []; + $timer = Plugin::getInstance()->timers->createTimer(['type' => $timerType] + $timerConfig); +// } catch () { +// +// } return $this->createSchedule([ 'id' => $request->getBodyParam('scheduleId'), @@ -437,7 +443,7 @@ public function createScheduleFromRequest(Request $request = null): Schedule 'timer' => $timer, 'action' => $action, 'enabled' => (bool)$request->getBodyParam('enabled'), - 'enabledLog' => $request->getBodyParam('enabledLog'), + //'enabledLog' => $request->getBodyParam('enabledLog'), ]); } @@ -448,6 +454,13 @@ public function createScheduleFromRequest(Request $request = null): Schedule */ public function createSchedule(mixed $config): Schedule { + if (empty($config['action'])) { + $config['action'] = Plugin::getInstance()->actions->createAction([ + 'id' => ArrayHelper::remove($config, 'actionId'), + 'type' => ArrayHelper::remove($config, 'actionType'), + 'settings' => ArrayHelper::remove($config, 'actionSettings'), + ]); + } return new Schedule($config); } @@ -458,16 +471,16 @@ public function createSchedule(mixed $config): Schedule */ public function saveSchedule(Schedule $schedule, bool $runValidation = true): bool { - $isNewSchedule = !$schedule->id; + $isNew = !$schedule->id; if ($this->hasEventHandlers(self::EVENT_BEFORE_SAVE_SCHEDULE)) { $this->trigger(self::EVENT_BEFORE_SAVE_SCHEDULE, new ScheduleEvent([ 'schedule' => $schedule, - 'isNew' => $isNewSchedule, + 'isNew' => $isNew, ])); } - if ($isNewSchedule) { + if ($isNew) { $schedule->uid = StringHelper::UUID(); } elseif ($schedule->uid === null) { $schedule->uid = Db::uidById(Table::SCHEDULES, $schedule->id); @@ -481,7 +494,7 @@ public function saveSchedule(Schedule $schedule, bool $runValidation = true): bo if ($schedule->static) { $path = "schedule.schedules.$schedule->uid"; $config = $this->getScheduleConfig($schedule); - if (!$isNewSchedule) { + if (!$isNew) { $config['timers'] = []; foreach ($schedule->getTimers() as $timer) { $config['timers'][$timer->uid] = Plugin::$plugin->getTimers()->getTimerConfig($timer); @@ -489,14 +502,14 @@ public function saveSchedule(Schedule $schedule, bool $runValidation = true): bo } Craft::$app->getProjectConfig()->set($path, $config); - if ($isNewSchedule) { + if ($isNew) { $schedule->id = Db::idByUid(Table::SCHEDULES, $schedule->uid); } } else { $transaction = Craft::$app->getDb()->beginTransaction(); try { $deleteConfig = false; - if (!$isNewSchedule) { + if (!$isNew) { $record = ScheduleRecord::findOne(['id' => $schedule->id]); if (!$record) { throw new ScheduleException("No schedule exists with the ID: “{$schedule->id}“."); @@ -508,7 +521,11 @@ public function saveSchedule(Schedule $schedule, bool $runValidation = true): bo $record = new ScheduleRecord(); } - Plugin::getInstance()->actions->saveAction($schedule->action); + + + if (!Plugin::getInstance()->actions->saveAction($schedule->action)) { + throw new ActionException(); + } $record->groupId = $schedule->groupId; $record->name = $schedule->name; @@ -518,9 +535,15 @@ public function saveSchedule(Schedule $schedule, bool $runValidation = true): bo $record->onSuccess = null; $record->onFailed = null; $record->enabled = (bool)$schedule->enabled; - $record->enabledLog = (bool)$schedule->enabledLog; +// $record->enabledLog = (bool)$schedule->enabledLog; $record->save(false); + + $schedule->timer->scheduleId = $record->id; + if (!Plugin::getInstance()->timers->saveTimer($schedule->timer)) { + throw new TimerException(); + } + $transaction->commit(); if ($deleteConfig) { @@ -534,19 +557,19 @@ public function saveSchedule(Schedule $schedule, bool $runValidation = true): bo } - if ($isNewSchedule) { + if ($isNew) { $schedule->id = $record->id; } } - if ($isNewSchedule) { + if ($isNew) { $this->_schedules[] = $schedule; } if ($this->hasEventHandlers(self::EVENT_AFTER_SAVE_SCHEDULE)) { $this->trigger(self::EVENT_AFTER_SAVE_SCHEDULE, new ScheduleEvent([ 'schedule' => $schedule, - 'isNew' => $isNewSchedule, + 'isNew' => $isNew, ])); } @@ -743,9 +766,11 @@ private function _createScheduleQuery(): Query //'schedules.dateCreated', //'schedules.dateUpdated', 'schedules.uid', + 'actionType' => 'actions.type', + 'actionSettings' => 'actions.settings', ]) ->from(['schedules' => Table::SCHEDULES]) - //->innerJoin(['actions' => Table::ACTIONS], '[[schedules.actionId]] = [[actions.id]]') + ->innerJoin(['actions' => Table::ACTIONS], '[[schedules.actionId]] = [[actions.id]]') ->orderBy('schedules.sortOrder'); } diff --git a/src/services/Timers.php b/src/services/Timers.php index 10540c5..e00df82 100644 --- a/src/services/Timers.php +++ b/src/services/Timers.php @@ -128,27 +128,12 @@ public function getActiveTimers(): array }); } - /** - * @param int $scheduleId - * @return TimerInterface[] - */ - public function getTimersByScheduleId(int $scheduleId): array + public function getTimerByScheduleId(int $scheduleId): ?TimerInterface { - if ($this->_timers !== null) { - return ArrayHelper::firstWhere($this->_timers, 'scheduleId', $scheduleId); - } - - $res = []; - $results = $this->_createQuery() - ->where(['scheduleId' => $scheduleId]) - ->all(); - foreach ($results as $result) { - $res[] = $this->createTimer($result); - } - - return $res; + return ArrayHelper::firstWhere($this->getAllTimers(), 'scheduleId', $scheduleId); } + /** * @param int $id * @return TimerInterface|null @@ -219,7 +204,7 @@ public function saveTimer(TimerInterface $timer, bool $runValidation = true): bo if ($isNewTimer) { $timer->uid = StringHelper::UUID(); } elseif ($timer->uid === null) { - $timer->uid = Db::uidById(Table::SCHEDULETIMERS, $timer->id); + $timer->uid = Db::uidById(Table::TIMERS, $timer->id); } if ($runValidation && !$timer->validate()) { @@ -227,10 +212,10 @@ public function saveTimer(TimerInterface $timer, bool $runValidation = true): bo return false; } - if ($timer->getSchedule()->static) { - $path = "schedule.schedules.{$timer->getSchedule()->uid}.timers.$timer->uid"; - Craft::$app->getProjectConfig()->set($path, $this->getTimerConfig($timer)); - } else { +// if ($timer->getSchedule()->static) { +// $path = "schedule.schedules.{$timer->getSchedule()->uid}.timers.$timer->uid"; +// Craft::$app->getProjectConfig()->set($path, $this->getTimerConfig($timer)); +// } else { $transaction = Craft::$app->getDb()->beginTransaction(); try { if (!$isNewTimer) { @@ -247,18 +232,13 @@ public function saveTimer(TimerInterface $timer, bool $runValidation = true): bo } $record->type = get_class($timer); - $record->minute = $this->_normalizeCronExpress($timer->minute); - $record->hour = $this->_normalizeCronExpress($timer->hour); - $record->day = $this->_normalizeCronExpress($timer->day); - $record->month = $this->_normalizeCronExpress($timer->month); - $record->week = $this->_normalizeCronExpress($timer->week); - $record->enabled = $timer->enabled; $record->settings = Json::encode($timer->getSettings()); + $record->enabled = $timer->enabled; if ($isNewTimer) { $lastSortOrder = (new Query()) ->select('sortOrder') - ->from(Table::SCHEDULETIMERS) + ->from(Table::TIMERS) ->where([ 'scheduleId' => $timer->scheduleId, ]) @@ -284,7 +264,7 @@ public function saveTimer(TimerInterface $timer, bool $runValidation = true): bo if ($isNewTimer && $this->_timers !== null) { $this->_timers[$timer->id] = $timer; } - } +// } $timer->afterSave($isNewTimer); @@ -320,7 +300,7 @@ public function deleteTimer(TimerInterface $timer): bool $transaction = $db->beginTransaction(); try { $db->createCommand() - ->delete(Table::SCHEDULETIMERS, [ + ->delete(Table::TIMERS, [ 'id' => $timer->id, ]) ->execute(); @@ -354,7 +334,7 @@ public function reorderTimers(array $timerIds): bool try { foreach ($timerIds as $order => $id) { $db->createCommand() - ->update(Table::SCHEDULETIMERS, [ + ->update(Table::TIMERS, [ 'sortOrder' => $order + 1, ], [ 'id' => $id, @@ -382,7 +362,7 @@ public function handleChangeTimer(ConfigEvent $event): void $scheduleUid = $event->tokenMatches[0]; $uid = $event->tokenMatches[1]; - $id = Db::idByUid(Table::SCHEDULETIMERS, $uid); + $id = Db::idByUid(Table::TIMERS, $uid); $isNew = empty($id); $config = [ @@ -400,9 +380,9 @@ public function handleChangeTimer(ConfigEvent $event): void $scheduleId= DB::idByUid(Table::SCHEDULES, $scheduleUid); $config['scheduleId'] = $scheduleId; $config['uid'] = $uid; - Db::insert(Table::SCHEDULETIMERS, $config); + Db::insert(Table::TIMERS, $config); } else { - Db::update(Table::SCHEDULETIMERS, $config, ['id' => $id]); + Db::update(Table::TIMERS, $config, ['id' => $id]); } if ($this->hasEventHandlers(self::EVENT_AFTER_SAVE_TIMER)) { @@ -432,7 +412,7 @@ public function handleDeleteTimer(ConfigEvent $event): void ])); } - Db::delete(Table::SCHEDULETIMERS, ['id' => $timer->id]); + Db::delete(Table::TIMERS, ['id' => $timer->id]); if ($this->hasEventHandlers(self::EVENT_AFTER_DELETE_TIMER)) { $this->trigger(self::EVENT_AFTER_DELETE_TIMER, new TimerEvent([ @@ -468,17 +448,12 @@ private function _createQuery(): Query 'id', 'scheduleId', 'type', - 'minute', - 'hour', - 'day', - 'month', - 'week', 'settings', 'enabled', 'sortOrder', 'uid', ]) - ->from(Table::SCHEDULETIMERS) + ->from(Table::TIMERS) ->orderBy(['sortOrder' => SORT_ASC]); } diff --git a/src/templates/_components/actions/Command/settings.twig b/src/templates/_components/actions/Command/settings.twig new file mode 100644 index 0000000..8f06f2d --- /dev/null +++ b/src/templates/_components/actions/Command/settings.twig @@ -0,0 +1,30 @@ +{% import "_includes/forms" as forms %} + +{% set scheduleType = className(schedule) %} + +{{ forms.autosuggestField({ + label: 'Command'|t('schedule'), + required: true, + id: 'command', + name: 'command', + value: schedule.command, + errors: schedule.getErrors('command'), + suggestions: suggestions, +}) }} + +{{ forms.textField({ + label: 'Arguments'|t('schedule'), + id: 'arguments', + name: 'arguments', + value: schedule.arguments, + errors: schedule.getErrors('arguments'), +}) }} + +{{ forms.textField({ + label: 'Timeout'|t('schedule'), + id: 'timeout', + name: 'timeout', + value: schedule.timeout ?: '', + size: 5, + errors: schedule.getErrors('timeout'), +}) }} diff --git a/src/templates/_components/actions/Console/settings.twig b/src/templates/_components/actions/Console/settings.twig index 8f06f2d..943a914 100644 --- a/src/templates/_components/actions/Console/settings.twig +++ b/src/templates/_components/actions/Console/settings.twig @@ -2,14 +2,14 @@ {% set scheduleType = className(schedule) %} -{{ forms.autosuggestField({ +{{ forms.selectField({ label: 'Command'|t('schedule'), required: true, id: 'command', name: 'command', value: schedule.command, + options: schedule.commandOptions, errors: schedule.getErrors('command'), - suggestions: suggestions, }) }} {{ forms.textField({ diff --git a/src/templates/_components/timers/Cron.twig b/src/templates/_components/timers/Cron.twig index 12cc48d..976a515 100644 --- a/src/templates/_components/timers/Cron.twig +++ b/src/templates/_components/timers/Cron.twig @@ -4,7 +4,8 @@ {{ forms.radio({ label: 'Every'|t('schedule'), name: 'mode', - value: 'every' + value: 'every', + checked: timer.mode == 'every', }) }} {% set options = [] %} @@ -31,7 +32,8 @@ {{ forms.radio({ label: 'Datetime'|t('schedule'), name: 'mode', - value: 'datetime' + value: 'datetime', + checked: timer.mode == 'datetime', }) }} {% include "_includes/forms/datetime.twig" with { @@ -45,7 +47,8 @@ {{ forms.radio({ label: 'Cron Expression'|t('schedule'), name: 'mode', - value: 'expression' + value: 'expression', + checked: timer.mode == 'expression', }) }}
diff --git a/src/templates/_components/utilities/ActionRunner.twig b/src/templates/_components/utilities/ActionRunner.twig new file mode 100644 index 0000000..67f8b2b --- /dev/null +++ b/src/templates/_components/utilities/ActionRunner.twig @@ -0,0 +1,10 @@ +{% import "schedule/_includes/forms.twig" as forms %} + +
+ +{{ forms.action({ + action: action, +}) }} + + +
\ No newline at end of file diff --git a/src/templates/_edit.twig b/src/templates/_edit.twig index 4d9a885..92dc065 100644 --- a/src/templates/_edit.twig +++ b/src/templates/_edit.twig @@ -58,6 +58,7 @@ {{ forms.selectField({ label: 'When to execute'|t('schedule'), disabled: not allowChange, + id: 'timerType', name: 'timerType', value: className(schedule.timer), options: timerTypeOptions, @@ -80,31 +81,38 @@
{% endfor %} + {{ forms.errorList(schedule.getErrors('timer')) }} +
- {{ forms.selectField({ + {{ forms.action({ label: 'What to do'|t('schedule'), - disabled: not allowChange, - name: 'actionType', - value: className(schedule.action), - options: actionTypeOptions, - toggle: true, + value: schedule.action, }) }} - {% for actionType in actionTypes %} - {% set isCurrent = (actionType == className(schedule.action)) %} - - {% endfor %} +{# {{ forms.selectField({#} +{# label: 'What to do'|t('schedule'),#} +{# disabled: not allowChange,#} +{# name: 'actionType',#} +{# value: className(schedule.action),#} +{# options: actionTypeOptions,#} +{# toggle: true,#} +{# }) }}#} + +{# {% for actionType in actionTypes %}#} +{# {% set isCurrent = (actionType == className(schedule.action)) %}#} +{# #} +{# {% endfor %}#} {% endblock %} @@ -116,8 +124,6 @@ name: 'timeout', errors: schedule.getErrors('timeout'), }) }} - - {% if craft.app.config.general.allowAdminChanges or not isNewSchedule %} @@ -183,13 +189,13 @@ errors: schedule.getErrors('enabled'), }) }} - {{ forms.lightswitchField({ - label: 'Enabled Log'|t('schedule'), - id: 'enabledLog', - name: 'enabledLog', - on: schedule.enabledLog, - errors: schedule.getErrors('enabledLog'), - }) }} +{# {{ forms.lightswitchField({#} +{# label: 'Enabled Log'|t('schedule'),#} +{# id: 'enabledLog',#} +{# name: 'enabledLog',#} +{# on: schedule.enabledLog,#} +{# errors: schedule.getErrors('enabledLog'),#} +{# }) }}#} @@ -203,6 +209,7 @@ }) }}
+ {% if not isNewSchedule %}
{{ "Created at"|t('app') }}
{{ schedule.dateCreated|datetime('short') }}
@@ -211,6 +218,7 @@
{{ "Updated at"|t('app') }}
{{ schedule.dateUpdated|datetime('short') }}
+ {% endif %} {# {% if schedule.lastFinishedDate is not empty %}#} {#
#} {#
{{ "Last running at"|t('schedule') }}
#} @@ -227,12 +235,12 @@
{% endblock %} -{% set actionTypeIds = [] %} -{% for actionType in actionTypes %} - {% if actionType != className(schedule.action) %} - {% set actionTypeIds = actionTypeIds|merge({(actionType): actionType|id}) %} - {% endif %} -{% endfor %} +{#{% set actionTypeIds = [] %}#} +{#{% for actionType in actionTypes %}#} +{# {% if actionType != className(schedule.action) %}#} +{# {% set actionTypeIds = actionTypeIds|merge({(actionType): actionType|id}) %}#} +{# {% endif %}#} +{#{% endfor %}#} {% set timerTypeIds = [] %} {% for timerType in timerTypes %} @@ -242,26 +250,6 @@ {% endfor %} {% js %} - var actionTypes = {{ actionTypeIds|json_encode|raw }}; - $('select[name=actionType]').on('change', function(value) { - var actionType = $(this).val(); - if (actionType in actionTypes) { - var id = '#' + actionTypes[actionType]; - let _cancelToken = axios.CancelToken.source(); - Craft.sendActionRequest("POST", "schedule/actions/render-settings", { - cancelToken: _cancelToken.token, - data: { - type: actionType, - }, - }).then(function(response) { - let $settings = $(response.data.settingsHtml || ''); - $(id).html(response.data.settingsHtml); - Craft.appendHeadHtml(response.data.headHtml); - Craft.appendBodyHtml(response.data.bodyHtml); - }) - delete actionTypes[actionType] - } - }); var timerTypes = {{ timerTypeIds|json_encode|raw }}; $('select[name=timerType]').on('change', function(value) { var timerType = $(this).val(); diff --git a/src/templates/_includes/forms.twig b/src/templates/_includes/forms.twig index 49339c6..d61be15 100644 --- a/src/templates/_includes/forms.twig +++ b/src/templates/_includes/forms.twig @@ -1,6 +1,10 @@ {% extends "_includes/forms.twig" %} +{% macro action(config) %} + {% include "schedule/_includes/forms/action.twig" with config only %} +{% endmacro %} + {% macro actionTypeField(config) %} - {% set config = config|merge({id: config.id ?? "entrytype#{random()}"}) %} + {% set config = config|merge({id: config.id ?? "actiontype#{random()}"}) %} {{ _self.field(config, 'template:schedule/_includes/forms/actionType') }} {% endmacro %} \ No newline at end of file diff --git a/src/templates/_includes/forms/action.twig b/src/templates/_includes/forms/action.twig new file mode 100644 index 0000000..81338d5 --- /dev/null +++ b/src/templates/_includes/forms/action.twig @@ -0,0 +1,67 @@ +{% import "_includes/forms.twig" as forms %} + +{% set action = action ?? value %} +{% set actionTypes = actionTypes ?? craft.schedule.actions.getAllActionTypes() %} + +{% if actionTypeOptions is not defined %} + {% set actionTypeOptions = [] %} + {% for type in actionTypes %} + {% set actionTypeOptions = actionTypeOptions|merge([{ + label: create(type).displayName(), + value: type, + }]) %} + {% endfor %} +{% endif %} + +{{ forms.selectField({ + label: label ?? 'What to do'|t('schedule'), + name: 'actionType', + value: className(action), + options: actionTypeOptions, + toggle: true, +}) }} + +{% for actionType in actionTypes %} + {% set isCurrent = (actionType == className(action)) %} + +{% endfor %} + +{% set actionTypeIds = [] %} +{% for actionType in actionTypes %} + {% if actionType != className(action) %} + {% set actionTypeIds = actionTypeIds|merge({(actionType): actionType|id}) %} + {% endif %} +{% endfor %} + +{% js %} +var actionTypes = {{ actionTypeIds|json_encode|raw }}; +$('select[name=actionType]').on('change', function(value) { + var actionType = $(this).val(); + if (actionType in actionTypes) { + var id = '#' + actionTypes[actionType]; + let _cancelToken = axios.CancelToken.source(); + Craft.sendActionRequest("POST", "schedule/actions/render-settings", { + cancelToken: _cancelToken.token, + data: { + type: actionType, + }, + }).then(function(response) { + let $settings = $(response.data.settingsHtml || ''); + $(id).html(response.data.settingsHtml); + Craft.appendHeadHtml(response.data.headHtml); + Craft.appendBodyHtml(response.data.bodyHtml); + }) + delete actionTypes[actionType] + } +}); +{% endjs %} \ No newline at end of file diff --git a/src/templates/index.twig b/src/templates/index.twig index 82cf597..8c77702 100644 --- a/src/templates/index.twig +++ b/src/templates/index.twig @@ -66,10 +66,10 @@ {% if groupId == null %} {{ "Group"|t('app') }} {% endif %} - {{ "Type"|t('app') }} - {{ "Timers"|t('schedule') }} - {{ "Last Running Date"|t('schedule') }} - {{ "Logs"|t('schedule') }} + {{ "Action"|t('schedule') }} + {{ "When"|t('schedule') }} + {{ "Status"|t('schedule') }} + {{ "Instances"|t('schedule') }} {{ 'Enabled'|t('schedule') }} {% if sortable %}{% endif %} @@ -97,37 +97,31 @@ {% if schedule is missing %} {{ schedule.expectedType }} {% else %} - {{ schedule.displayName() }} + {{ schedule.action.displayName() }} {% endif %} - - {% if schedule.timers|length == 0 %} - {{ allowChange ? "Edit Timers"|t('schedule') : "View Timers"|t('schedule') }} - {% else %} - {{ allowChange ? "Edit Timers ({count})"|t('schedule', { count: schedule.timers|length }) : "View Timers ({count})"|t('schedule', { count: schedule.timers|length }) }} - - - {% endif %} + + {{ schedule.timer.cronDescription }} +{# #} - {% if schedule.lastFinishedDate is not empty %} - {% if schedule.lastStatus %} - - {% else %} - - {% endif %} - {{ schedule.lastFinishedDate|datetime('short') }} - {% endif %} +{# {% if schedule.lastFinishedDate is not empty %}#} +{# {% if schedule.lastStatus %}#} +{# #} +{# {% else %}#} +{# #} +{# {% endif %}#} +{# {{ schedule.lastFinishedDate|datetime('short') }}#} +{# {% endif %}#} + + +{# {% if schedule.enabledLog %}{% endif %}#} - {% if schedule.enabledLog %}{% endif %} {% if allowChange %} {{ forms.lightswitchField({ diff --git a/src/timers/Cron.php b/src/timers/Cron.php index 50541b3..c26ded3 100644 --- a/src/timers/Cron.php +++ b/src/timers/Cron.php @@ -130,6 +130,9 @@ public function getDatetime(): ?\DateTime public function setDateTime(mixed $datetime): void { $datetime = DateTimeHelper::toDateTime($datetime); + if (!$datetime) { + return; + } $this->timezone = $datetime->getTimezone()->getName(); $this->year = $datetime->format('Y'); $this->month = $datetime->format('m'); @@ -176,4 +179,12 @@ public function getEveryOptions(): array Craft::t('schedule', 'Weekly') => self::EVERY_WEEKLY, ]; } + + protected function defineRules(): array + { + return [ + [['mode'], 'required'], + [['minute', 'hour', 'day', 'month', 'week', 'year'], 'string'], + ]; + } } \ No newline at end of file diff --git a/src/utilities/ActionRunner.php b/src/utilities/ActionRunner.php new file mode 100644 index 0000000..1247b1e --- /dev/null +++ b/src/utilities/ActionRunner.php @@ -0,0 +1,25 @@ +actions->getAllActionTypes(); + return \Craft::$app->getView()->renderTemplate('schedule/_components/utilities/ActionRunner', [ + 'action' => new HttpRequest(), + 'actionTypes' => $allActionTypes, + 'actionTypeOptions' => array_map(static fn($class) => ['label' => $class::displayName(), 'value' => $class], $allActionTypes), + ]); + } +} \ No newline at end of file