From fc0000d4a0504e32c7c69fe39f5db63efd735933 Mon Sep 17 00:00:00 2001 From: ferishili Date: Fri, 29 Sep 2023 16:03:23 +0200 Subject: [PATCH 01/13] init commit --- .../Conf/Workflows/class.xoctWorkflowGUI.php | 204 +++++- .../Workflows/class.xoctWorkflowTableGUI.php | 67 +- classes/Event/class.xoctEventGUI.php | 43 +- classes/Event/class.xoctEventRenderer.php | 8 +- classes/class.xoctMainGUI.php | 20 + lang/ilias_de.lang | 32 +- lang/ilias_en.lang | 32 +- plugin.php | 2 +- sql/dbupdate.php | 4 + src/Container/Init.php | 5 +- src/Model/Config/PluginConfig.php | 49 ++ src/Model/Workflow/WorkflowAR.php | 63 +- src/Model/Workflow/WorkflowDBRepository.php | 592 +++++++++++++++++- src/Model/Workflow/WorkflowRepository.php | 49 +- src/UI/Modal/EventModals.php | 101 ++- templates/default/startworkflow_modal.css | 38 ++ templates/default/tpl.icon.html | 3 + .../tpl.startworkflow_configpanel_block.html | 3 + ...tpl.startworkflow_configpanel_section.html | 4 + .../tpl.startworkflow_description_block.html | 3 + ...tpl.startworkflow_description_section.html | 4 + .../default/tpl.startworkflow_modal.html | 83 +++ 22 files changed, 1331 insertions(+), 78 deletions(-) create mode 100644 templates/default/startworkflow_modal.css create mode 100644 templates/default/tpl.icon.html create mode 100644 templates/default/tpl.startworkflow_configpanel_block.html create mode 100644 templates/default/tpl.startworkflow_configpanel_section.html create mode 100644 templates/default/tpl.startworkflow_description_block.html create mode 100644 templates/default/tpl.startworkflow_description_section.html create mode 100644 templates/default/tpl.startworkflow_modal.html diff --git a/classes/Conf/Workflows/class.xoctWorkflowGUI.php b/classes/Conf/Workflows/class.xoctWorkflowGUI.php index 160f8cd56..9f5d3bb01 100644 --- a/classes/Conf/Workflows/class.xoctWorkflowGUI.php +++ b/classes/Conf/Workflows/class.xoctWorkflowGUI.php @@ -5,6 +5,8 @@ use srag\Plugins\Opencast\Model\Workflow\WorkflowAR; use srag\Plugins\Opencast\Model\Workflow\WorkflowRepository; use srag\Plugins\Opencast\LegacyHelpers\OutputTrait; +use srag\Plugins\Opencast\LegacyHelpers\TranslatorTrait; +use srag\Plugins\Opencast\Model\Config\PluginConfig; /** * Class xoctWorkflowGUI @@ -15,9 +17,15 @@ */ class xoctWorkflowGUI extends xoctGUI { + use TranslatorTrait; use OutputTrait; + public const PLUGIN_CLASS_NAME = ilOpenCastPlugin::class; public const LANG_MODULE = 'workflow'; + public const CMD_SAVE_SETTINGS = 'saveSettings'; + public const CMD_UPDATE_WORKFLOWS = 'updateWorkflows'; + public const CMD_CONFIRM_RESET_WORKFLOWS = 'confirmResetWorkflows'; + public const CMD_RESET_WORKFLOWS = 'resetWorkflows'; /** * @var Factory */ @@ -38,6 +46,18 @@ class xoctWorkflowGUI extends xoctGUI * @var \ILIAS\HTTP\Services */ private $http; + /** + * @var \ilGlobalTemplateInterface + */ + private $main_tpl; + /** + * @var \ilTabs + */ + private $tabs; + /** + * @var string + */ + protected $wf_subtab_active; public function __construct(WorkflowRepository $workflow_repository) { @@ -46,9 +66,12 @@ public function __construct(WorkflowRepository $workflow_repository) $ui = $DIC->ui(); $this->toolbar = $DIC->toolbar(); $this->language = $DIC->language(); + $this->tabs = $DIC->tabs(); $this->http = $DIC->http(); $this->workflow_repository = $workflow_repository; $this->factory = $ui->factory(); + $this->main_tpl = $ui->mainTemplate(); + $this->setTab(); } /** @@ -57,18 +80,149 @@ public function __construct(WorkflowRepository $workflow_repository) */ protected function index() { - $this->initToolbar(); - $table = new xoctWorkflowTableGUI($this, self::CMD_STANDARD, $this->workflow_repository); - $this->output($table); + if ($this->wf_subtab_active === xoctMainGUI::SUBTAB_WORKFLOWS_LIST) { + $this->initToolbar(); + $table = new xoctWorkflowTableGUI($this, self::CMD_STANDARD, $this->workflow_repository); + $this->output($table); + } else { + $this->output($this->getWorkflowSettingsForm()); + } + } + + /** + * Helps setting the tabs at all time. + */ + public function setTab() + { + $this->ctrl->saveParameter($this, 'wf_subtab_active'); + $this->wf_subtab_active = $_GET['wf_subtab_active'] ?: xoctMainGUI::SUBTAB_WORKFLOWS_SETTINGS; + $this->tabs->setSubTabActive($this->wf_subtab_active); + } + + public function setTabParameter($tab = xoctMainGUI::SUBTAB_WORKFLOWS_SETTINGS) + { + $this->ctrl->setParameter( + $this, + 'wf_subtab_active', + $tab + ); + } + + /** + * + */ + protected function getWorkflowSettingsForm() + { + $tags = $this->factory->input()->field()->text($this->translate(PluginConfig::F_WORKFLOWS_TAGS)) + ->withByline($this->translate(PluginConfig::F_WORKFLOWS_TAGS . '_info')) + ->withValue(PluginConfig::getConfig(PluginConfig::F_WORKFLOWS_TAGS) ?? ''); + $excluded_roles = $this->factory->input()->field()->text($this->translate(PluginConfig::F_WORKFLOWS_EXCLUDE_ROLES)) + ->withByline($this->translate(PluginConfig::F_WORKFLOWS_EXCLUDE_ROLES . '_info')) + ->withValue(PluginConfig::getConfig(PluginConfig::F_WORKFLOWS_EXCLUDE_ROLES) ?? ''); + return $this->factory->input()->container()->form()->standard( + $this->ctrl->getFormAction($this, self::CMD_SAVE_SETTINGS), + [ + $this->factory->input()->field()->section( + [ + PluginConfig::F_WORKFLOWS_TAGS => $tags, + PluginConfig::F_WORKFLOWS_EXCLUDE_ROLES => $excluded_roles + ], + $this->txt('settings_header'), + $this->txt('settings_header_description') + ) + ] + ); + } + + protected function saveSettings() + { + $this->setTabParameter(); + $form = $this->getWorkflowSettingsForm()->withRequest($this->http->request()); + if ($data = $form->getData()) { + $data = reset($data); + + $current_tags = PluginConfig::getConfig(PluginConfig::F_WORKFLOWS_TAGS); + $current_roles = PluginConfig::getConfig(PluginConfig::F_WORKFLOWS_EXCLUDE_ROLES); + + $new_tags = $data[PluginConfig::F_WORKFLOWS_TAGS] ?? ''; + $new_roles = $data[PluginConfig::F_WORKFLOWS_EXCLUDE_ROLES] ?? ''; + + try { + $update_succeeded = $this->workflow_repository->updateList($new_tags, $new_roles); + if ($update_succeeded) { + ilUtil::sendSuccess($this->translate('msg_workflow_settings_saved'), true); + PluginConfig::set( + PluginConfig::F_WORKFLOWS_TAGS, + trim($new_tags) + ); + PluginConfig::set( + PluginConfig::F_WORKFLOWS_EXCLUDE_ROLES, + trim($new_roles) + ); + } else { + ilUtil::sendFailure($this->translate('msg_workflow_settings_saved_update_failed'), true); + // Reverting back! + PluginConfig::set(PluginConfig::F_WORKFLOWS_TAGS, $current_tags); + PluginConfig::set(PluginConfig::F_WORKFLOWS_EXCLUDE_ROLES, $current_roles); + } + } catch (xoctException $e) { + ilUtil::sendFailure($e->getMessage(), true); + } + $this->ctrl->redirect($this, self::CMD_STANDARD); + } else { + $this->output($form); + } } protected function initToolbar() { - $add_button = $this->factory->button()->primary( - $this->plugin->txt('config_button_add_workflow'), - $this->ctrl->getLinkTarget($this, self::CMD_ADD) + $update_workflows_button = $this->factory->button()->primary( + $this->plugin->txt('config_workflows_update_btn'), + $this->ctrl->getLinkTarget($this, self::CMD_UPDATE_WORKFLOWS) ); - $this->toolbar->addComponent($add_button); + $this->toolbar->addComponent($update_workflows_button); + $reset_workflows_button = $this->factory->button()->standard( + $this->plugin->txt('config_workflows_reset_btn'), + $this->ctrl->getLinkTarget($this, self::CMD_CONFIRM_RESET_WORKFLOWS) + ); + $this->toolbar->addComponent($reset_workflows_button); + } + + protected function updateWorkflows() + { + $this->setTabParameter(xoctMainGUI::SUBTAB_WORKFLOWS_LIST); + $update_succeeded = $this->workflow_repository->updateList(); + if ($update_succeeded) { + ilUtil::sendSuccess($this->translate('msg_workflow_list_update_success'), true); + } else { + ilUtil::sendFailure($this->translate('msg_workflow_list_update_failed'), true); + } + $this->ctrl->redirect($this, self::CMD_STANDARD); + } + + protected function confirmResetWorkflows() + { + $ilConfirmationGUI = new ilConfirmationGUI(); + $ilConfirmationGUI->setFormAction($this->ctrl->getFormAction($this)); + $ilConfirmationGUI->setCancel($this->language->txt('cancel'), self::CMD_STANDARD); + $ilConfirmationGUI->setConfirm($this->language->txt('confirm'), self::CMD_RESET_WORKFLOWS); + $ilConfirmationGUI->setHeaderText($this->plugin->txt('msg_confirm_reset_workflow_list')); + $this->main_tpl->setContent($ilConfirmationGUI->getHTML()); + } + + protected function resetWorkflows() + { + try { + $reset_succeeded = $this->workflow_repository->resetList(); + if ($reset_succeeded) { + ilUtil::sendSuccess($this->translate('msg_workflow_list_reset_success'), true); + } else { + ilUtil::sendFailure($this->translate('msg_workflow_list_reset_failed'), true); + } + } catch (xoctException $e) { + ilUtil::sendFailure($e->getMessage(), true); + } + $this->ctrl->redirect($this, self::CMD_STANDARD); } /** @@ -76,11 +230,13 @@ protected function initToolbar() */ protected function getForm(WorkflowAR $workflow = null): Standard { - $id = $this->factory->input()->field()->text($this->language->txt('id'))->withRequired(true); - $title = $this->factory->input()->field()->text($this->language->txt('title'))->withRequired(true); - $parameters = $this->factory->input()->field()->text($this->plugin->txt('parameters'))->withByline( - $this->plugin->txt('parameters_info') - ); + $id = $this->factory->input()->field()->text($this->language->txt('id'))->withDisabled(true); + $title = $this->factory->input()->field()->text($this->language->txt('title')); + $description = $this->factory->input()->field()->textarea($this->language->txt('description')); + $tags = $this->factory->input()->field()->text($this->translate('tags', self::LANG_MODULE))->withDisabled(true); + $roles = $this->factory->input()->field()->text($this->translate('roles', self::LANG_MODULE))->withDisabled(true); + $configuration_panel = $this->factory->input()->field()->textarea($this->translate('config_panel', self::LANG_MODULE)) + ->withDisabled(true); if (!is_null($workflow)) { $this->ctrl->setParameter($this, 'workflow_id', $workflow->getId()); @@ -94,8 +250,11 @@ protected function getForm(WorkflowAR $workflow = null): Standard [ 'id' => is_null($workflow) ? $id : $id->withValue($workflow->getWorkflowId()), 'title' => is_null($workflow) ? $title : $title->withValue($workflow->getTitle()), - 'parameters' => is_null($workflow) ? $parameters : $parameters->withValue( - $workflow->getParameters() + 'description' => is_null($workflow) ? $description : $description->withValue($workflow->getDescription()), + 'tags' => is_null($workflow) ? $tags : $tags->withValue($workflow->getTags()), + 'roles' => is_null($workflow) ? $roles : $roles->withValue($workflow->getRoles()), + 'configuration_panel' => is_null($workflow) ? $configuration_panel : $configuration_panel->withValue( + json_encode($workflow->getConfigPanel()) ) ], $this->plugin->txt('workflow') @@ -119,7 +278,8 @@ protected function create() { $form = $this->getForm()->withRequest($this->http->request()); if ($data = $form->getData()) { - $this->workflow_repository->store($data[0]['id'], $data[0]['title'], $data[0]['parameters']); + $wf = reset($data); + $this->workflow_repository->createOrUpdate($wf['id'], $wf['title'], $wf['description'], $wf['tags'], $wf['roles']); ilUtil::sendSuccess($this->plugin->txt('msg_workflow_created'), true); $this->ctrl->redirect($this, self::CMD_STANDARD); } else { @@ -146,7 +306,8 @@ protected function update() $id = filter_input(INPUT_GET, 'workflow_id', FILTER_SANITIZE_STRING); $form = $this->getForm(WorkflowAR::find($id))->withRequest($this->http->request()); if ($data = $form->getData()) { - $this->workflow_repository->store($data[0]['id'], $data[0]['title'], $data[0]['parameters'], $id); + $wf = reset($data); + $this->workflow_repository->createOrUpdate($wf['id'], $wf['title'], $wf['description']); ilUtil::sendSuccess($this->plugin->txt('msg_workflow_updated'), true); $this->ctrl->redirect($this, self::CMD_STANDARD); } else { @@ -176,4 +337,15 @@ protected function delete() $this->ctrl->redirect($this, self::CMD_STANDARD); } } + + /** + * @param $key + * + * @return string + * @throws DICException + */ + public function txt($key): string + { + return $this->translate($key, self::LANG_MODULE); + } } diff --git a/classes/Conf/Workflows/class.xoctWorkflowTableGUI.php b/classes/Conf/Workflows/class.xoctWorkflowTableGUI.php index 5e58b6071..029384644 100644 --- a/classes/Conf/Workflows/class.xoctWorkflowTableGUI.php +++ b/classes/Conf/Workflows/class.xoctWorkflowTableGUI.php @@ -36,10 +36,15 @@ class xoctWorkflowTableGUI extends TableGUI * @var Renderer */ protected $renderer; + /** + * @var ilOpenCastPlugin + */ + private $plugin; public function __construct($parent, string $parent_cmd, WorkflowRepository $workflow_repository) { - global $DIC; + global $DIC, $opencastContainer; + $this->plugin = $opencastContainer->get(ilOpenCastPlugin::class); $ui = $DIC->ui(); $this->workflow_repository = $workflow_repository; $this->factory = $ui->factory(); @@ -48,6 +53,7 @@ public function __construct($parent, string $parent_cmd, WorkflowRepository $wor $this->setExternalSegmentation(true); parent::__construct($parent, $parent_cmd); $this->setDescription($this->translate('msg_workflows_info')); + $parent->setTabParameter(xoctMainGUI::SUBTAB_WORKFLOWS_LIST); } /** @@ -55,10 +61,27 @@ public function __construct($parent, string $parent_cmd, WorkflowRepository $wor */ protected function initColumns(): void { - $this->addColumn($this->lng->txt('id')); - $this->addColumn($this->lng->txt('title')); - $this->addColumn($this->translate('parameters')); - $this->addColumn($this->lng->txt('actions'), '', '', true); + $cols = $this->getSelectableColumns2(); + foreach ($this->getSelectedColumns() as $col) { + $txt = $cols[$col]['txt']; + $id = $cols[$col]['id']; + $sort = false; + $width = ''; + switch ($id) { + case 'description': + $width = '250px'; + break; + case 'id': + $width = '200px'; + break; + case 'config_panel': + $width = '24px'; + break; + } + $action = ($id == 'action'); + + $this->addColumn($txt, $sort, $width, $action); + } } public function getHTML() @@ -86,8 +109,25 @@ protected function getColumnValue(string $column, /*array*/ $row, int $format = return $row->getWorkflowId(); case 'title': return $row->getTitle(); - case 'parameters': - return $row->getParameters(); + case 'description': + return $row->getDescription(); + case 'tags': + return str_replace(',', '
', $row->getTags()); + case 'roles': + return str_replace(',', '
', $row->getRoles()); + case 'config_panel': + $tpl = new ilTemplate("tpl.icon.html", true, true, $this->plugin->getDirectory()); + $has_config_panel = !empty($row->getConfigPanel()) ? true : false; + $icon = $has_config_panel ? 'checkbox_checked.png' : 'checkbox_unchecked.png'; + $tpl->setCurrentBlock('icon'); + $tpl->setVariable('ICON_SRC', ilUtil::getHtmlPath(ilUtil::getImagePath($icon))); + $tpl->setVariable('ICON_ALT', $icon); + $icon_title = $has_config_panel ? + $this->translate('config_panel_icon_with', self::LANG_MODULE) : + $this->translate('config_panel_icon_without', self::LANG_MODULE); + $tpl->setVariable('ICON_TITLE', $icon_title); + $tpl->parseCurrentBlock(); + return $tpl->get(); case 'actions': $this->ctrl->setParameter($this->parent_obj, 'workflow_id', $row->getId()); $delete_modal = $this->factory->modal()->interruptive( @@ -130,7 +170,10 @@ protected function getSelectableColumns2(): array return [ ['txt' => $this->lng->txt('id'), 'id' => 'id'], ['txt' => $this->lng->txt('title'), 'id' => 'title'], - ['txt' => $this->translate('parameters'), 'id' => 'parameters'], + ['txt' => $this->lng->txt('description'), 'id' => 'description'], + ['txt' => $this->translate('tags', self::LANG_MODULE), 'id' => 'tags'], + ['txt' => $this->translate('roles', self::LANG_MODULE), 'id' => 'roles'], + ['txt' => $this->translate('config_panel', self::LANG_MODULE), 'id' => 'config_panel'], ['txt' => $this->lng->txt('actions'), 'id' => 'actions'] ]; } @@ -140,7 +183,11 @@ protected function getSelectableColumns2(): array */ public function isColumnSelected($col): bool { - return true; + if (!array_key_exists($col, $this->getSelectableColumns())) { + return true; + } + + return in_array($col, $this->getSelectedColumns()); } /** @@ -164,7 +211,7 @@ protected function initFilterFields(): void */ protected function initId(): void { - // TODO: Implement initId() method. + $this->setId('xoct_workflows'); } /** diff --git a/classes/Event/class.xoctEventGUI.php b/classes/Event/class.xoctEventGUI.php index e21b73a98..fd38597bb 100755 --- a/classes/Event/class.xoctEventGUI.php +++ b/classes/Event/class.xoctEventGUI.php @@ -62,7 +62,8 @@ class xoctEventGUI extends xoctGUI public const CMD_SWITCH_TO_LIST = 'switchToList'; public const CMD_SWITCH_TO_TILES = 'switchToTiles'; public const CMD_CHANGE_TILE_LIMIT = 'changeTileLimit'; - public const CMD_REPUBLISH = 'republish'; + // public const CMD_REPUBLISH = 'republish'; + public const CMD_START_WORKFLOW = 'startWorkflow'; public const CMD_OPENCAST_STUDIO = 'opencaststudio'; public const CMD_DOWNLOAD = 'download'; public const CMD_CREATE_SCHEDULED = 'createScheduled'; @@ -260,6 +261,11 @@ protected function performCommand($cmd) $this->plugin->getDirectory() . '/templates/default/reporting_modal.css' ); + // Start Workflow stylesheet + $this->main_tpl->addCss( + $this->plugin->getDirectory() . '/templates/default/startworkflow_modal.css' + ); + switch ($cmd) { case self::CMD_STANDARD: $this->prepareContent(); @@ -1061,14 +1067,14 @@ protected function updateScheduled() * @throws DICException * @throws xoctException */ - protected function republish() + protected function startWorkflow() { $post_body = $this->http->request()->getParsedBody(); if (isset($post_body['workflow_id']) && is_string($post_body['workflow_id']) - && isset($post_body['republish_event_id']) && is_string($post_body['republish_event_id']) + && isset($post_body['startworkflow_event_id']) && is_string($post_body['startworkflow_event_id']) ) { $workflow_id = strip_tags($post_body['workflow_id']); - $event_id = strip_tags($post_body['republish_event_id']); + $event_id = strip_tags($post_body['startworkflow_event_id']); $workflow = $this->workflowRepository->getById($workflow_id); if (!ilObjOpenCastAccess::checkAction( ilObjOpenCastAccess::ACTION_EDIT_EVENT, @@ -1078,9 +1084,32 @@ protected function republish() ilUtil::sendFailure($this->txt('msg_no_access'), true); $this->cancel(); } + + $received_configs = []; + if (isset($post_body[$workflow_id]) && !empty($post_body[$workflow_id])) { + $received_configs = $post_body[$workflow_id]; + } + $default_configs = $this->workflowRepository->getConfigPanelAsArrayById($workflow_id); $configurations = []; - foreach (array_filter(explode(',', $workflow->getParameters())) as $param) { - $configurations[$param] = 'true'; + + foreach ($default_configs as $key => $config_data) { + $value = $config_data['value']; + $type = $config_data['type']; + if (in_array($key, array_keys($received_configs))) { + $received_value = $received_configs[$key]; + // Take care of datetime conversion. + if (strpos($type, 'datetime') !== false) { + $datetime = new DateTimeImmutable($received_value); + // $datetime->setTimezone(new DateTimeZone("UTC")); + $received_value = $datetime->format('Y-m-d\TH:i:s\Z'); + } + $value = $received_value; + } + // Take care of boolean conversion. + if (is_bool($value)) { + $value = $value ? 'true' : 'false'; + } + $configurations[$key] = (string) $value; } $workflow_instance = $this->api->routes()->workflowsApi->run( @@ -1322,7 +1351,7 @@ public function getModals(): EventModals $DIC, $this->workflowRepository ); - $modals->initRepublish(); + $modals->initWorkflows(); $modals->initReportDate(); $modals->initReportQuality(); $this->modals = $modals; diff --git a/classes/Event/class.xoctEventRenderer.php b/classes/Event/class.xoctEventRenderer.php index 7c6952eef..3c57e06ab 100644 --- a/classes/Event/class.xoctEventRenderer.php +++ b/classes/Event/class.xoctEventRenderer.php @@ -665,13 +665,13 @@ public function getActions(): array // Republish if (ilObjOpenCastAccess::checkAction(ilObjOpenCastAccess::ACTION_EDIT_EVENT, $this->event, $xoctUser) - && !$this->event->isScheduled() && !is_null(self::$modals) && !is_null(self::$modals->getRepublishModal()) + && !$this->event->isScheduled() && !is_null(self::$modals) && !is_null(self::$modals->getStartworkflowModal()) ) { $actions[] = $this->factory->button()->shy( - $this->plugin->txt('event_republish'), - self::$modals->getRepublishModal()->getShowSignal() + $this->plugin->txt('event_startworkflow'), + self::$modals->getStartworkflowModal()->getShowSignal() )->withOnLoadCode(function ($id) { - return "$({$id}).on('click', function(event){ $('input#republish_event_id').val('{$this->event->getIdentifier()}'); });"; + return "$({$id}).on('click', function(event){ $('input#startworkflow_event_id').val('{$this->event->getIdentifier()}'); $('.startworkflow-form select#workflow_id').val(''); $('.startworkflow-form select#workflow_id').trigger('change');});"; }); } diff --git a/classes/class.xoctMainGUI.php b/classes/class.xoctMainGUI.php index 524965d9b..5b06786f6 100644 --- a/classes/class.xoctMainGUI.php +++ b/classes/class.xoctMainGUI.php @@ -28,6 +28,9 @@ class xoctMainGUI extends xoctGUI public const SUBTAB_GROUPS_ROLES = 'groups_roles'; public const SUBTAB_SECURITY = 'security'; public const SUBTAB_ADVANCED = 'advanced'; + + public const SUBTAB_WORKFLOWS_SETTINGS = 'wf_settings'; + public const SUBTAB_WORKFLOWS_LIST = 'wf_list'; /** * @var \ilTabsGUI */ @@ -117,6 +120,7 @@ public function executeCommand(): void break; case strtolower(xoctWorkflowGUI::class): $this->tabs->activateTab(self::TAB_WORKFLOWS); + $this->setWorkflowsSubTabs(); $xoctWorkflowGUI = new xoctWorkflowGUI($opencast_dic->workflow_repository()); $this->ctrl->forwardCommand($xoctWorkflowGUI); break; @@ -193,6 +197,22 @@ protected function setSubTabs() $this->ctrl->clearParametersByClass(xoctConfGUI::class); } + protected function setWorkflowsSubTabs() + { + $this->ctrl->setParameterByClass(xoctWorkflowGUI::class, 'wf_subtab_active', self::SUBTAB_WORKFLOWS_SETTINGS); + $this->tabs->addSubTab( + self::SUBTAB_WORKFLOWS_SETTINGS, + $this->plugin->txt('subtab_' . self::SUBTAB_WORKFLOWS_SETTINGS), + $this->ctrl->getLinkTargetByClass(xoctWorkflowGUI::class) + ); + $this->ctrl->setParameterByClass(xoctWorkflowGUI::class, 'wf_subtab_active', self::SUBTAB_WORKFLOWS_LIST); + $this->tabs->addSubTab( + self::SUBTAB_WORKFLOWS_LIST, + $this->plugin->txt('subtab_' . self::SUBTAB_WORKFLOWS_LIST), + $this->ctrl->getLinkTargetByClass(xoctWorkflowGUI::class) + ); + } + protected function index() { } diff --git a/lang/ilias_de.lang b/lang/ilias_de.lang index b9c971afd..089dec2a4 100644 --- a/lang/ilias_de.lang +++ b/lang/ilias_de.lang @@ -231,7 +231,7 @@ event_presenters#:#Referent/in event_preview#:#Vorschau event_processing_state#:#Status event_recording_station#:#Aufnahmestation -event_republish#:#Workflow starten +event_startworkflow#:#Workflow starten event_schedule_new#:#Aufzeichnungstermin(e) planen event_start_time#:#Start event_start#:#Start @@ -517,7 +517,7 @@ tiles_per_page#:#Kacheln pro Seite: event_live_waiting_text#:#Das Live Event hat noch nicht begonnen.
Geplante Startzeit: %s event_live_interrupted_text#:#Das Live Event wurde unterbrochen oder frühzeitig beendet. event_live_over_text#:#Das Live Event wurde beendet. -msg_workflows_info#:#Die hier konfigurierten Workflows können verwendet werden, um Events erneut zu publizieren. +msg_workflows_info#:#Hier können Sie die Workflows von Opencast laden und sie anpassen. msg_workflow_created#:#Workflow erfolgreich erstellt. msg_workflow_updated#:#Workflow erfolgreich aktualisiert. config_button_add_workflow#:#Neuen Workflow hinzufügen @@ -577,3 +577,31 @@ object_settings#:#Einstellungen channel_type#:#Serie file#:#Datei link#:#URL +subtab_wf_settings#:#Einstellungen +subtab_wf_list#:#Liste der Workflow-Definitionen +workflow_settings_header#:#Allgemeine Einstellungen +workflow_settings_header_description#:#Hier können Sie die Workflow-Einstellungen und deren Verwendung konfigurieren.
HINWEIS: Beim Speichern der neuen Änderungen wird auch die Liste der Workflow-Definitionen entsprechend aktualisiert. +config_workflows_tags#:#Workflows-Tags +config_workflows_tags_info#:#Eine durch Kommas getrennte Liste von Tags, die aus allen verfügbaren Tags in Opencast extrahiert und den Lehrern als Liste für den Start bei Ereignissen bereitgestellt werden sollen.
HINWEIS: Wenn Sie das Feld leer lassen, wird die Funktion deaktiviert. +config_workflows_exclude_roles#:#Auszuschließende Rollen +config_workflows_exclude_roles_info#:#Eine durch Kommas getrennte Liste von Rollen, die in Workflows enthalten sind und aus der Workflow-Liste ausgeschlossen werden sollen. +config_workflows_update_btn#:#Workflowliste aktualisieren +config_workflows_reset_btn#:#Workflowliste zurücksetzen +msg_workflow_settings_saved#:#Allgemeine Einstellungen wurden gespeichert, die Workflow-Liste wurde ebenfalls aktualisiert. +msg_workflow_settings_saved_update_failed#:#Die Workflow-Liste konnte nicht aktualisiert werden. Bitte versuchen Sie es erneut! +msg_workflow_list_update_success#:#Die Workflow-Liste wurde erfolgreich aktualisiert. +msg_workflow_list_update_failed#:#Die Workflow-Liste konnte nicht aktualisiert werden. Bitte versuchen Sie es erneut! +msg_workflow_list_reset_success#:#Die Workflowliste wurde erfolgreich zurückgesetzt. +msg_workflow_list_reset_failed#:#Die Workflowliste konnte nicht zurückgesetzt werden. Bitte versuchen Sie es erneut! +msg_workflows_updated#:#Die Workflowliste wurde erfolgreich aktualisiert. +msg_confirm_reset_workflow_list#:#Die Workflowliste wird zurückgesetzt und alle Änderungen werden entfernt. Möchten Sie trotzdem mit dem Zurücksetzen fortfahren? +workflow_tags#:#Tags +workflow_roles#:#Rollen +workflow_config_panel#:#Konfigurationspanel +workflow_config_panel_icon_with#:#Mit Konfigurationspanel +workflow_config_panel_icon_without#:#Ohne Konfigurationspanel +workflow_empty_select_option#:#--einen Workflow auswählen-- +workflow_description_section_header#:#Beschreibung +workflow_configpanel_section_header#:#Konfigurationspanel +msg_startworkflow_no_workflow_seleced#:#Bitte wählen Sie zunächst einen Workflow aus +msg_startworkflow_required_config_panel_item#:#Erforderliche Felder müssen Werte enthalten diff --git a/lang/ilias_en.lang b/lang/ilias_en.lang index 7d3a5f510..ec35e22cb 100644 --- a/lang/ilias_en.lang +++ b/lang/ilias_en.lang @@ -266,7 +266,7 @@ event_state_failed_owner#:#Publication failed. Please contact the support. event_state_running_owner#:#Converting, not yet visible to other students event_report_date_modification#:#Report Date Modifications event_report_quality_problem#:#Report Quality Problems -event_republish#:#Start Workflow +event_startworkflow#:#Start Workflow event_series#:#Series event_own_series#:#Own Series (%s) groups_available_participants_header#:#Available Participants @@ -519,7 +519,7 @@ tiles_per_page#:#Tiles per page: event_live_waiting_text#:#Live Event hasn't started yet.
Scheduled start time: %s event_live_interrupted_text#:#The Live Event has been paused or prematurely terminated. event_live_over_text#:#Live Event finished. -msg_workflows_info#:#The configured workflows can be used to republish events. +msg_workflows_info#:#Here you can load the workflows from opencast and customize them. msg_workflow_created#:#Workflow created successfully. msg_workflow_updated#:#Workflow updated successfully. config_button_add_workflow#:#Create new workflow @@ -578,3 +578,31 @@ object_settings#:#Settings channel_type#:#Series file#:#File link#:#URL +subtab_wf_settings#:#Settings +subtab_wf_list#:#Workflow definitions list +workflow_settings_header#:#General Settings +workflow_settings_header_description#:#Here you can configure the workflow settings and the way to use them.
NOTE: Upon saving the new changes, the list of workflow definitions will also be updated accordingly. +config_workflows_tags#:#Workflows Tags +config_workflows_tags_info#:#A comma-separated list of tags to extract from the all available tags in Opencast and provide them to the teachers as a list to start against events.
NOTE: Leaving it empty will disable the feature. +config_workflows_exclude_roles#:#Roles to exclude +config_workflows_exclude_roles_info#:#A comma-separated list of roles included in workflows to be excluded from the workflow list. +config_workflows_update_btn#:#Update Workflows List +config_workflows_reset_btn#:#Reset Workflows List +msg_workflow_settings_saved#:#General settings have been saved, the workflow list has also been updated. +msg_workflow_settings_saved_update_failed#:#Unable to update the workflow list, please try again! +msg_workflow_list_update_success#:#Workflow list has been successfully updated. +msg_workflow_list_update_failed#:#Unable to update the workflow list, please try again! +msg_workflow_list_reset_success#:#Workflows list has been successfully reset. +msg_workflow_list_reset_failed#:#Unable to reset workflows list, please try again! +msg_workflows_updated#:#The workflows list has been successfully updated. +msg_confirm_reset_workflow_list#:#The workflows list will be reset and all changes will be removed, would you like to proceed with the reset anyway? +workflow_tags#:#Tags +workflow_roles#:#Roles +workflow_config_panel#:#Configuration Panel +workflow_config_panel_icon_with#:#With Configuration Panel +workflow_config_panel_icon_without#:#Without Configuration Panel +workflow_empty_select_option#:#--Select a workflow-- +workflow_description_section_header#:#Description +workflow_configpanel_section_header#:#Configuration Panel +msg_startworkflow_no_workflow_seleced#:#Please select a workflow first +msg_startworkflow_required_config_panel_item#:#Required fields must have values diff --git a/plugin.php b/plugin.php index 10cb95f36..8f6819792 100644 --- a/plugin.php +++ b/plugin.php @@ -1,7 +1,7 @@ manipulate('update xoct_data set intro_text = "" where intro_text is null'); ?> +<#41> + diff --git a/src/Container/Init.php b/src/Container/Init.php index 60d910f9c..53d9b194f 100644 --- a/src/Container/Init.php +++ b/src/Container/Init.php @@ -70,7 +70,10 @@ static function () { PluginConfig::getConfig(PluginConfig::F_API_BASE) ?? 'https://stable.opencast.org/api', PluginConfig::getConfig(PluginConfig::F_CURL_USERNAME) ?? 'admin', PluginConfig::getConfig(PluginConfig::F_CURL_PASSWORD) ?? 'opencast', - PluginConfig::getConfig(PluginConfig::F_API_VERSION) ?? '1.9.0' + PluginConfig::getConfig(PluginConfig::F_API_VERSION) ?? '1.9.0', + 0, + 0, + PluginConfig::getConfig(PluginConfig::F_PRESENTATION_NODE) ?? null ); }); diff --git a/src/Model/Config/PluginConfig.php b/src/Model/Config/PluginConfig.php index 6baf06fe0..3d973b817 100644 --- a/src/Model/Config/PluginConfig.php +++ b/src/Model/Config/PluginConfig.php @@ -11,6 +11,7 @@ use srag\Plugins\Opencast\Model\TermsOfUse\ToUManager; use srag\Plugins\Opencast\Model\User\xoctUser; use srag\Plugins\Opencast\Model\WorkflowParameter\Config\WorkflowParameter; +use srag\Plugins\Opencast\Model\Workflow\WorkflowAR; use xoctCurl; use xoctCurlSettings; use xoctLog; @@ -112,6 +113,8 @@ class PluginConfig extends ActiveRecord public const PAELLA_DEFAULT_PATH = 'Customizing/global/plugins/Services/Repository/RepositoryObject/OpenCast/js/paella_player/config.json'; public const PAELLA_DEFAULT_PATH_LIVE = 'Customizing/global/plugins/Services/Repository/RepositoryObject/OpenCast/js/paella_player/config_live.json'; + public const F_WORKFLOWS_TAGS = 'config_workflows_tags'; + public const F_WORKFLOWS_EXCLUDE_ROLES = 'config_workflows_exclude_roles'; /** * @var array */ @@ -210,6 +213,25 @@ public static function importFromXML(string $xml_file_path): void } } + /** + * @var $xoctWorkflowParameter WorkflowParameter + */ + $xoct_workflow = $domxml->getElementsByTagName('xoct_workflow'); + + // We need to reset the workflow table. + WorkflowAR::flushDB(); + + foreach ($xoct_workflow as $node) { + $xoctWorkflow = new WorkflowAR(); + $xoctWorkflow->setWorkflowId($node->getElementsByTagName('workflow_id')->item(0)->nodeValue); + $xoctWorkflow->setTitle($node->getElementsByTagName('title')->item(0)->nodeValue ?? ''); + $xoctWorkflow->setDescription($node->getElementsByTagName('description')->item(0)->nodeValue ?? ''); + $xoctWorkflow->setTags($node->getElementsByTagName('tags')->item(0)->nodeValue ?? ''); + $xoctWorkflow->setRoles($node->getElementsByTagName('roles')->item(0)->nodeValue ?? ''); + $xoctWorkflow->setConfigPanel($node->getElementsByTagName('config_panel')->item(0)->nodeValue ?? ''); + $xoctWorkflow->create(); + } + /** * @var $xoctPublicationUsage PublicationUsage */ @@ -290,6 +312,33 @@ public static function getXMLExport(): string ); } + // xoctWorkflows + $xml_xoctWorkflows = $config->appendChild(new DOMElement('xoct_workflows')); + /** + * @var $xoctWorkflowAR WorkflowAR + */ + foreach (WorkflowAR::get() as $xoctWorkflows) { + $xml_xoctWf = $xml_xoctWorkflows->appendChild(new DOMElement('xoct_workflow')); + $xml_xoctWf->appendChild(new DOMElement('workflow_id'))->appendChild( + new DOMCdataSection($xoctWorkflows->getWorkflowId()) + ); + $xml_xoctWf->appendChild(new DOMElement('title'))->appendChild( + new DOMCdataSection($xoctWorkflows->getTitle() ?? '') + ); + $xml_xoctWf->appendChild(new DOMElement('description'))->appendChild( + new DOMCdataSection($xoctWorkflows->getDescription() ?? '') + ); + $xml_xoctWf->appendChild(new DOMElement('tags'))->appendChild( + new DOMCdataSection($xoctWorkflows->getTags() ?? '') + ); + $xml_xoctWf->appendChild(new DOMElement('roles'))->appendChild( + new DOMCdataSection($xoctWorkflows->getRoles() ?? '') + ); + $xml_xoctWf->appendChild(new DOMElement('config_panel'))->appendChild( + new DOMCdataSection($xoctWorkflows->getConfigPanel() ?? '') + ); + } + // xoctPublicationUsages $xml_xoctPublicationUsages = $config->appendChild(new DOMElement('xoct_publication_usages')); /** diff --git a/src/Model/Workflow/WorkflowAR.php b/src/Model/Workflow/WorkflowAR.php index efc576c7d..f1d167c26 100644 --- a/src/Model/Workflow/WorkflowAR.php +++ b/src/Model/Workflow/WorkflowAR.php @@ -54,7 +54,30 @@ public function getConnectorContainerName(): string * @con_fieldtype text * @con_length 512 */ - protected $parameters; + protected $description; + /** + * @var string + * + * @con_has_field true + * @con_fieldtype text + * @con_length 512 + */ + protected $tags; + /** + * @var string + * + * @con_has_field true + * @con_fieldtype text + * @con_length 512 + */ + protected $roles; + /** + * @var string + * + * @con_has_field true + * @con_fieldtype clob + */ + protected $config_panel; public function getId(): int { @@ -86,13 +109,43 @@ public function setTitle(string $title): void $this->title = $title; } - public function getParameters(): string + public function getDescription(): string + { + return $this->description ?: ''; + } + + public function setDescription(string $description): void + { + $this->description = $description; + } + + public function getTags(): string + { + return $this->tags ?: ''; + } + + public function setTags(string $tags): void + { + $this->tags = $tags; + } + + public function getRoles(): string + { + return $this->roles ?: ''; + } + + public function setRoles(string $roles): void + { + $this->roles = $roles; + } + + public function getConfigPanel(): string { - return $this->parameters ?: ''; + return $this->config_panel ?: ''; } - public function setParameters(string $parameters): void + public function setConfigPanel(string $config_panel): void { - $this->parameters = $parameters; + $this->config_panel = $config_panel; } } diff --git a/src/Model/Workflow/WorkflowDBRepository.php b/src/Model/Workflow/WorkflowDBRepository.php index cffe0329c..747c69f97 100644 --- a/src/Model/Workflow/WorkflowDBRepository.php +++ b/src/Model/Workflow/WorkflowDBRepository.php @@ -1,14 +1,37 @@ api = $opencastContainer[API::class]; + } + public function anyWorkflowExists(): bool { return (WorkflowAR::count() > 0); } + public function anyWorkflowAvailable(): bool + { + return (count($this->getFilteredWorkflowsArray()) > 0); + } + public function getAllWorkflows(): array { return WorkflowAR::get(); @@ -19,13 +42,30 @@ public function getAllWorkflowsAsArray($key = null, $values = null): array return WorkflowAR::getArray($key, $values); } - public function store(string $workflow_id, string $title, string $parameters, int $id = 0): void + public function store(string $workflow_id, string $title, string $description, + string $tags, string $roles, string $config_panel, int $id = 0): void { /** @var WorkflowAR $workflow */ $workflow = new WorkflowAR($id == 0 ? null : $id); $workflow->setWorkflowId($workflow_id); $workflow->setTitle($title); - $workflow->setParameters($parameters); + $workflow->setDescription($description); + if ($id == 0) { + $workflow->setTags($tags); + $workflow->setRoles($roles); + $workflow->setConfigPanel($config_panel); + } else { + if (!empty($tags)) { + $workflow->setTags($tags); + } + if (!empty($roles)) { + $workflow->setRoles($roles); + } + if (!empty($config_panel)) { + $workflow->setConfigPanel($config_panel); + } + } + $workflow->store(); } @@ -49,4 +89,552 @@ public function getById(int $id) { return WorkflowAR::where(['id' => $id])->first(); } + + public function getConfigPanelAsArrayById(string $id): array + { + $config_panel_array = []; + $workflow = $this->getById($id); + $configuration_panel_html = $workflow->getConfigPanel(); + $configuration_panel_html = trim(str_replace("\n", "", $configuration_panel_html)); + if (!empty($configuration_panel_html)) { + $dom = new \DOMDocument(); + $dom->strictErrorChecking = false; + $dom->loadHTML($configuration_panel_html, LIBXML_NOCDATA|LIBXML_NOWARNING|LIBXML_NOERROR); + $inputs = $dom->getElementsByTagName('input'); + $selects = $dom->getElementsByTagName('select'); + + foreach ($inputs as $input) { + $key = ''; + $value = ''; + $type = ''; + if ($input->hasAttribute('type')) { + $type = $input->getAttribute('type'); + } + if ($input->hasAttribute('name')) { + $key = $input->getAttribute('name'); + } else if ($input->hasAttribute('id')) { + $key = $input->getAttribute('id'); + } + + if ($input->hasAttribute('value')) { + $value = $input->getAttribute('value'); + } + + if (!empty($key)) { + $value = ($type == 'checkbox') ? + ($value == 'true' ? true : false) : + trim($value); + $config_panel_array[$key] = [ + 'value' => $value, + 'type' => $type + ]; + } + } + + foreach ($selects as $select) { + $key = ''; + $value = ''; + if ($input->hasAttribute('name')) { + $key = $input->getAttribute('name'); + } else if ($input->hasAttribute('id')) { + $key = $input->getAttribute('id'); + } + + if ($input->hasAttribute('value')) { + $value = $input->getAttribute('value'); + } + + if (!empty($key)) { + $config_panel_array[$key] = [ + 'value' => trim($value), + 'type' => 'select' + ]; + } + } + } + return $config_panel_array; + } + + public function getWorkflowsFromOpencastApi(array $filter = [], bool $with_configuration_panel = false, + bool $with_tags = false): array + { + $workflows = $this->api->routes()->workflowsApi->getAllDefinitions([ + 'withconfigurationpanel' => $with_configuration_panel, + 'filter' => $filter, + ]); + if ($with_tags) { + return array_filter($workflows, function ($workflow) { + return !empty($workflow->tags); + }); + } + return $workflows; + } + + public function updateList(?string $tags_str = null, ?string $roles_str = null): bool + { + $oc_workflows_all = $this->getWorkflowsFromOpencastApi([], true, true); + $filtered_oc_workflows = $this->getFilteredWorkflowsArray($oc_workflows_all, $tags_str, $roles_str); + $filtered_oc_workflows_ids = array_keys($filtered_oc_workflows); + $current_workflows = $this->getAllWorkflowsAsArray('workflow_id'); + $current_workflows_ids = array_keys($current_workflows); + + // First remove from workflowsAR list. + foreach ($current_workflows as $cr_wf_id => $cr_wf) { + if (!in_array($cr_wf_id, $filtered_oc_workflows_ids)) { + $this->delete($cr_wf['id']); + } + } + + // Then, update the list if it is not there, without touching the current ones + foreach ($filtered_oc_workflows as $oc_wd_id => $oc_wf) { + // Exists, we check the diffs! + if (in_array($oc_wd_id, $current_workflows_ids)) { + $current_workflow = $this->getByWorkflowId($oc_wd_id); + // Check the configuration panel changes only. + $current_config_panel = json_encode( + str_replace("\r\n", "\n", trim($current_workflow->getConfigPanel())) + ); + $new_config_panel = json_encode( + str_replace("\r\n", "\n", trim($oc_wf->configuration_panel)) + ); + if (!strcmp($current_config_panel, $new_config_panel)) { + continue; + } + $configuration_panel = !empty($oc_wf->configuration_panel) ? $oc_wf->configuration_panel : ''; + $current_workflow->setConfigPanel($configuration_panel); + $current_workflow->store(); + } else { + // Otherwise, we save it new! + $title = isset($oc_wf->title) ? trim($oc_wf->title) : ''; + $description = isset($oc_wf->description) ? trim($oc_wf->description) : ''; + $tags = isset($oc_wf->tags) ? implode(',', $oc_wf->tags) : ''; + $roles = isset($oc_wf->roles) ? implode(',', $oc_wf->roles) : ''; + $configuration_panel = !empty($oc_wf->configuration_panel) ? $oc_wf->configuration_panel : ''; + $this->createOrUpdate($oc_wd_id, $title, $description, $tags, $roles, $configuration_panel); + } + } + + $success = WorkflowAR::count() === count($filtered_oc_workflows_ids); + + // Rolling back. + if (!$success) { + WorkflowAR::flushDB(); + foreach ($current_workflows as $workflow) { + $new_workflow = new WorkflowAR(null); + $new_workflow->setWorkflowId($workflow->getWorkflowId()); + $new_workflow->setTitle($workflow->getTitle()); + $new_workflow->setDescription($workflow->getDescription()); + $new_workflow->setTags($workflow->getTags()); + $new_workflow->setRoles($workflow->getRoles()); + $new_workflow->setConfigPanel($workflow->getConfigPanel()); + $new_workflow->store(); + } + } + + return $success; + } + + public function resetList(): bool + { + $oc_workflows_all = $this->getWorkflowsFromOpencastApi([], true, true); + $filtered_oc_workflows = $this->getFilteredWorkflowsArray($oc_workflows_all); + $filtered_oc_workflows_ids = array_keys($filtered_oc_workflows); + $current_workflows = $this->getAllWorkflowsAsArray('workflow_id'); + $current_workflows_ids = array_keys($current_workflows); + + // Flushing everything first. + WorkflowAR::flushDB(); + + // Then, add the new ones one by one! + foreach ($filtered_oc_workflows as $oc_wd_id => $oc_wf) { + // Otherwise, we save it new! + $title = isset($oc_wf->title) ? trim($oc_wf->title) : ''; + $description = isset($oc_wf->description) ? trim($oc_wf->description) : ''; + $tags = isset($oc_wf->tags) ? implode(',', $oc_wf->tags) : ''; + $roles = isset($oc_wf->roles) ? implode(',', $oc_wf->roles) : ''; + $configuration_panel = !empty($oc_wf->configuration_panel) ? $oc_wf->configuration_panel : ''; + $this->createOrUpdate($oc_wd_id, $title, $description, $tags, $roles, $configuration_panel); + } + + $success = WorkflowAR::count() === count($filtered_oc_workflows_ids); + + // Rolling back. + if (!$success) { + WorkflowAR::flushDB(); + foreach ($current_workflows as $workflow) { + $new_workflow = new WorkflowAR(null); + $new_workflow->setWorkflowId($workflow->getWorkflowId()); + $new_workflow->setTitle($workflow->getTitle()); + $new_workflow->setDescription($workflow->getDescription()); + $new_workflow->setTags($workflow->getTags()); + $new_workflow->setRoles($workflow->getRoles()); + $new_workflow->setConfigPanel($workflow->getConfigPanel()); + $new_workflow->store(); + } + } + + return $success; + } + + public function createOrUpdate(string $workflow_id, string $title, string $description, string $tags = '', string $roles = '', + string $config_panel = ''): WorkflowAR + { + $id = 0; + if ($this->exists($workflow_id)) { + $workflow = $this->getByWorkflowId($workflow_id); + $id = $workflow->getId(); + } + + $this->store($workflow_id, $title, $description, $tags, $roles, $config_panel, $id); + + $new_workflow = $this->getByWorkflowId($workflow_id); + return $new_workflow; + } + + private function commaToArray($comma_separated_str): array + { + $converted_list = []; + if (!empty($comma_separated_str)) { + $converted_list = explode(',', $comma_separated_str); + $converted_list = array_map('trim', $converted_list); + } + return $converted_list; + } + + private function hasItem($base, $check): bool + { + foreach ($base as $item) { + if (in_array($item, $check)) { + return true; + } + } + return false; + } + + public function getFilteredWorkflowsArray( + array $workflows = [], + ?string $tags_str = null, + ?string $roles_str = null): array + { + $filtered_list = []; + + $tags_to_include = PluginConfig::getConfig(PluginConfig::F_WORKFLOWS_TAGS) ?? ''; + if (!is_null($tags_str)) { + $tags_to_include = $tags_str; + } + $tags_to_include_arr = $this->commaToArray($tags_to_include); + $tags_to_include_arr = array_filter($tags_to_include_arr, function ($tag) { + return !empty(trim($tag)); + }); + // Disable the feature if tags list is empty. + if (empty($tags_to_include_arr)) { + return []; + } + + $roles_to_exclude = PluginConfig::getConfig(PluginConfig::F_WORKFLOWS_EXCLUDE_ROLES) ?? ''; + if (!is_null($roles_str)) { + $roles_to_exclude = $roles_str; + } + $roles_to_exclude_arr = $this->commaToArray($roles_to_exclude); + $roles_to_exclude_arr = array_filter($roles_to_exclude_arr, function ($role) { + return !empty(trim($role)); + }); + + // If the list is empty, then we get all currect ones from WorkflowAR + if (empty($workflows)) { + $workflows = $this->getAllWorkflows(); + } + + foreach ($workflows as $workflow) { + $tags_array = []; + $roles_array = []; + $workflow_id = ''; + if ($workflow instanceof WorkflowAR) { + $tags_array = $this->commaToArray($workflow->getTags()); + $roles_array = $this->commaToArray($workflow->getRoles()); + $workflow_id = $workflow->getWorkflowId(); + } else { + $workflow_id = $workflow->identifier; + if (isset($workflow->tags)) { + $tags_array = $workflow->tags; + } + if (isset($workflow->roles)) { + $roles_array = $workflow->roles; + } + } + + if (empty($tags_array)) { + continue; + } + + if ($this->hasItem($tags_array, $tags_to_include_arr) === false) { + continue; + } + + if ($this->hasItem($roles_array, $roles_to_exclude_arr) === true) { + continue; + } + + $filtered_list[$workflow_id] = $workflow; + } + + return $filtered_list; + } + + public function buildWorkflowSelectOptions(): string + { + $options = [ + '' + ]; + foreach ($this->getFilteredWorkflowsArray() as $workflow) { + $title = $workflow->getTitle(); + $workflow_record_id = $workflow->getId(); + $translated_text = $this->translate($workflow_record_id, self::SELECTION_TEXT_LANG_MODULE); + if (strpos($translated_text, 'MISSING') === false) { + $title = $translated_text; + } + $options[] = ""; + } + return implode("\n", $options); + } + + public function parseConfigPanels(): array + { + $config_panels = []; + foreach ($this->getFilteredWorkflowsArray() as $workflow) { + $configuration_panel_html = $workflow->getConfigPanel(); + $id = $workflow->getId(); + if (!empty(trim($configuration_panel_html))) { + $config_panels[$id] = $this->mapConfigPanelElements($id, $configuration_panel_html); + } + } + return $config_panels; + } + + private function mapConfigPanelElements(string $workflow_id, string $configuration_panel_html): string + { + $dom = new \DOMDocument(); + $dom->strictErrorChecking = false; + $configuration_panel_html = trim(str_replace("\n", "", $configuration_panel_html)); + $mapped_configuration_panel_html = $configuration_panel_html; + + if (strlen($configuration_panel_html) > 0) { + $dom->loadHTML($configuration_panel_html, LIBXML_NOCDATA|LIBXML_NOWARNING|LIBXML_NOERROR); + $inputs = $dom->getElementsByTagName('input'); + $selects = $dom->getElementsByTagName('select'); + $labels = $dom->getElementsByTagName('label'); + $uls = $dom->getElementsByTagName('ul'); + $legends = $dom->getElementsByTagName('legend'); + $main_div = $dom->getElementById('workflow-configuration'); + + if ($main_div) { + $main_div->setAttribute('id', "{$workflow_id}_workflow-configuration"); + } + + $ids = []; + $names = []; + $defaults = []; + $required = []; + + // Legends replacements. We need to legend to be displayed in there! + foreach ($legends as $legend) { + $text = $legend->textContent; + $h3 = $dom->createElement('h3'); + $h3->textContent = $text; + $legend->setAttribute('class', 'hidden'); + $legend->parentNode->insertBefore($h3, $legend); + } + + // Stylings and classes of ul li elements. + foreach ($uls as $ul) { + if ($ul->hasAttribute('style')) { + $ul->removeAttribute('style'); + } + + $classes = ['noStyle', 'flex-col']; + if ($ul->hasAttribute('class')) { + $current_classes = $ul->getAttribute('class'); + $current_classes = explode(' ', $current_classes); + foreach ($current_classes as $current_class) { + if (!in_array($current_class, $classes)) { + $classes[] = $current_class; + } + } + } + $ul->setAttribute('class', implode(' ', $classes)); + + // Items. + $lis = $ul->getElementsByTagName('li'); + foreach ($lis as $li) { + if ($li->hasAttribute('style')) { + // Removing all the default styles. + $li->removeAttribute('style'); + } + $classes = ['row-flex']; + if ($li->hasAttribute('class')) { + $current_classes = $li->getAttribute('class'); + $current_classes = explode(' ', $current_classes); + foreach ($current_classes as $current_class) { + if (!in_array($current_class, $classes)) { + $classes[] = $current_class; + } + } + } + $li->setAttribute('class', implode(' ', $classes)); + } + } + + // Replace the 'id' and 'name' attributes for input elements + foreach ($inputs as $input) { + if ($input->hasAttribute('id')) { + $old_id = $input->getAttribute('id'); + $new_id = "{$workflow_id}_{$old_id}"; + $ids[$old_id] = $new_id; + $input->setAttribute('id', $new_id); + } + // Make sure there the name is replaced. + if ($input->hasAttribute('name')) { + $old_name = $input->getAttribute('name'); + $new_name = "{$workflow_id}[{$old_name}]"; + $names[$old_name] = $new_name; + $input->setAttribute('name', $new_name); + } + $classes = ['wf-inputs']; + if ($input->hasAttribute('class')) { + $classes[] = $input->getAttribute('class'); + } + $input->setAttribute('class', implode(' ', $classes)); + + // We need to inline styles. + if ($input->hasAttribute('style')) { + $input->removeAttribute('style'); + } + + if ($input->hasAttribute('value')) { + $default_value = $input->getAttribute('value'); + $default_id = "{$new_id}_default"; + $hidden_input_default = $dom->createElement('input'); + $hidden_input_default->setAttribute('type', 'hidden'); + $hidden_input_default->setAttribute('id', $default_id); + $hidden_input_default->setAttribute('value', trim($default_value)); + $defaults[] = $hidden_input_default; + } + + if ($input->hasAttribute('required')) { + $required[] = $new_id; + } + } + + // Replace the 'id' and 'name' attributes for select elements + foreach ($selects as $select) { + if ($select->hasAttribute('id')) { + $old_id = $select->getAttribute('id'); + $new_id = "{$workflow_id}_{$old_id}"; + $ids[$old_id] = $new_id; + $select->setAttribute('id', $new_id); + } + if ($select->hasAttribute('name')) { + $old_name = $select->getAttribute('name'); + $new_name = "{$workflow_id}[{$old_name}]"; + $names[$old_name] = $new_name; + $select->setAttribute('name', $new_name); + } + $classes = ['wf-inputs']; + if ($select->hasAttribute('class')) { + $classes[] = $select->getAttribute('class'); + } + $select->setAttribute('class', implode(' ', $classes)); + + // We need to inline styles. + if ($select->hasAttribute('style')) { + $select->removeAttribute('style'); + } + + if ($select->hasAttribute('value')) { + $default_value = $select->getAttribute('value'); + $default_id = "{$new_id}_default"; + $hidden_input_default = $dom->createElement('input'); + $hidden_input_default->setAttribute('type', 'hidden'); + $hidden_input_default->setAttribute('id', $default_id); + $hidden_input_default->setAttribute('value', trim($default_value)); + $defaults[] = $hidden_input_default; + } + + if ($select->hasAttribute('required')) { + $required[] = $new_id; + } + } + + + // Replace the 'for' attribute and translate the text for label elements + foreach ($labels as $label) { + $label_text = $label->nodeValue; + if ($label->hasAttribute('for')) { + $for = $label->getAttribute('for'); + if (isset($ids[$for])) { + $label->setAttribute('for', $ids[$for]); + } + $translated_label = $this->translate($for, self::CONFIG_PANEL_LABEL_LANG_MODULE); + if (strpos($translated_label, 'MISSING') === false) { + $label_text = $translated_label; + } + } + $label->nodeValue = $label_text; + + $classes = ['control-label']; + if ($label->parentNode->tagName !== 'li') { + $classes[] = 'col-sm-3'; + $classes[] = 'configLabel'; + } else { + $classes[] = 'configListLabel'; + } + if ($label->hasAttribute('class')) { + $classes[] = $label->getAttribute('class'); + } + $label->setAttribute('class', implode(' ', $classes)); + + // We need to inline styles. + if ($label->hasAttribute('style')) { + $label->removeAttribute('style'); + } + + if (isset($for) && in_array($ids[$for], $required)) { + $required_span = $dom->createElement('span'); + $required_span->setAttribute('class', 'asterisk'); + $required_span->textContent = ' * '; + $label->appendChild($required_span); + } + } + + foreach ($defaults as $hidden_input_default) { + $dom->appendChild($hidden_input_default); + } + + + $modified = $dom->saveHTML(); + foreach ($ids as $old_id => $new_id) { + $modified = str_replace( + ["#{$old_id}", 'getElementById("{$old_id}")', "getElementById('{$old_id}')"], + ["#{$new_id}", 'getElementById("{$new_id}")', "getElementById('{$new_id}')"], + $modified + ); + } + + foreach ($names as $old_name => $new_name) { + $modified = str_replace( + ["name={$old_name}", 'name="{$old_name}"', "name='{$old_name}'", + 'getElementsByName("{$old_name}")', "getElementsByName('{$old_name}')", 'getElementsByName({$old_name})'], + ["name*={$old_name}", 'name*="{$old_name}"', "name*='{$old_name}'", + 'getElementsByName("{$new_name}")', "getElementsByName('{$new_name}')", 'getElementsByName({$new_name})'], + $modified + ); + } + + if (!empty($modified)) { + $mapped_configuration_panel_html = $modified; + } + } + + return $mapped_configuration_panel_html; + } } diff --git a/src/Model/Workflow/WorkflowRepository.php b/src/Model/Workflow/WorkflowRepository.php index f069b8b93..885086d28 100644 --- a/src/Model/Workflow/WorkflowRepository.php +++ b/src/Model/Workflow/WorkflowRepository.php @@ -24,7 +24,8 @@ public function getAllWorkflows(): array; */ public function getAllWorkflowsAsArray($key = null, $values = null): array; - public function store(string $workflow_id, string $title, string $parameters, int $id = 0); + public function store(string $workflow_id, string $title, string $description, + string $tags, string $roles, string $config_panel, int $id = 0); public function exists(string $workflow_id): bool; @@ -42,4 +43,50 @@ public function getByWorkflowId(string $workflow_id); * @return WorkflowAR|null */ public function getById(int $id); + + /** + * @return array + * @throws xoctException + */ + public function getWorkflowsFromOpencastApi(array $filter = [], bool $with_configuration_panel = false, + bool $with_tags = false): array; + + /** + * @return WorkflowAR + */ + public function createOrUpdate(string $workflow_id, string $title, string $description, + string $tags = '', string $roles = '', string $config_panel = ''): WorkflowAR; + + /** + * @return array + * @throws xoctException + */ + public function parseConfigPanels(): array; + + /** + * @return string + * @throws xoctException + */ + public function buildWorkflowSelectOptions(): string; + + /** + * @return array + */ + public function getFilteredWorkflowsArray(array $workflows = [], ?string $tags_str = null, + ?string $roles_str = null): array; + + /** + * @return bool + */ + public function resetList(): bool; + + /** + * @return bool + */ + public function updateList(?string $tags_str = null, ?string $roles_str = null): bool; + + /** + * @return array + */ + public function getConfigPanelAsArrayById(string $id): array; } diff --git a/src/UI/Modal/EventModals.php b/src/UI/Modal/EventModals.php index c8bd8dcee..6b4a4463c 100644 --- a/src/UI/Modal/EventModals.php +++ b/src/UI/Modal/EventModals.php @@ -31,7 +31,8 @@ class EventModals /** * @var Modal */ - protected $republish_modal; + protected $startworkflow_modal; + private $parent_gui; /** * @var Container @@ -58,34 +59,80 @@ public function __construct( $this->plugin = $plugin; } - public function initRepublish(): void + public function initWorkflows(): void { - if ($this->workflow_repository->anyWorkflowExists()) { - $form = new ilPropertyFormGUI(); - $form->setFormAction($this->dic->ctrl()->getFormAction($this->parent_gui, "republish")); - $form->setId(uniqid('form')); - - $select = new ilSelectInputGUI($this->plugin->txt('workflow'), 'workflow_id'); - $select->setOptions($this->workflow_repository->getAllWorkflowsAsArray('id', 'title')); - $form->addItem($select); - - $hidden = new ilHiddenInputGUI('republish_event_id'); - $form->addItem($hidden); + if ($this->workflow_repository->anyWorkflowAvailable()) { + $tpl = new ilTemplate("tpl.startworkflow_modal.html", true, true, $this->plugin->getDirectory()); + + $form_id = 'startworkflow_modal_form'; + $form_submit_btn_id = 'startworkflow-form-submit-btn'; + $tpl->setVariable('FORM_SUBMIT_BTN_ID', $form_submit_btn_id); + $tpl->setVariable('FORM_ID', $form_id); + $tpl->setVariable( + 'FORM_ACTION', + $this->dic->ctrl()->getFormAction($this->parent_gui, $this->parent_gui::CMD_START_WORKFLOW) + ); + + + $workflow_options = $this->workflow_repository->buildWorkflowSelectOptions(); + $tpl->setVariable('WORKFLOW_OPTIONS', $workflow_options); + + // Descriptions. + $description_section_tpl = new ilTemplate("tpl.startworkflow_description_section.html", + true, true, $this->plugin->getDirectory()); + $description_section_tpl->setVariable('HEADER', + $this->plugin->txt('workflow_description_section_header')); + $description_blocks = []; + foreach ($this->workflow_repository->getFilteredWorkflowsArray() as $workflow) { + $description_block_tpl = new ilTemplate("tpl.startworkflow_description_block.html", + true, true, $this->plugin->getDirectory()); + $description = $workflow->getDescription(); + $id = $workflow->getId(); + if (!empty(trim($description))) { + $description_block_tpl->setVariable('BLOCK_ID', $id); + $description_block_tpl->setVariable('DESCRIPTION_TEXT', $description); + $description_blocks[] = $description_block_tpl->get(); + } + } + if (!empty($description_blocks)) { + $description_section_tpl->setVariable('BLOCK_CONTENT', implode('', $description_blocks)); + $tpl->setVariable('WORKFLOW_DESCRIPTIONS', $description_section_tpl->get()); + } + + // Configuration Panel + $configpanel_section_tpl = new ilTemplate("tpl.startworkflow_configpanel_section.html", + true, true, $this->plugin->getDirectory()); + $configpanel_section_tpl->setVariable('HEADER', + $this->plugin->txt('workflow_configpanel_section_header')); + $configpanel_blocks = []; + foreach ($this->workflow_repository->parseConfigPanels() as $id => $configpanel) { + $configpanel_block_tpl = new ilTemplate("tpl.startworkflow_configpanel_block.html", + true, true, $this->plugin->getDirectory()); + $configpanel_block_tpl->setVariable('BLOCK_ID', $id); + $configpanel_block_tpl->setVariable('CONFIGPANEL_BLOCK', $configpanel); + $configpanel_blocks[] = $configpanel_block_tpl->get(); + } + if (!empty($configpanel_blocks)) { + $configpanel_section_tpl->setVariable('BLOCK_CONTENT', implode('', $configpanel_blocks)); + $tpl->setVariable('WORKFLOW_CONFIG_PANELS', $configpanel_section_tpl->get()); + } + + // Error messages. + $tpl->setVariable('NO_WORKFLOW_SELECTED_ERROR_TEXT', $this->plugin->txt('msg_startworkflow_no_workflow_seleced')); + $tpl->setVariable('CONFIG_PANEL_REQUIRED_ERROR_TEXT', $this->plugin->txt('msg_startworkflow_required_config_panel_item')); - $form_id = 'form_' . $form->getId(); $submit_btn = $this->dic->ui()->factory()->button()->primary($this->dic->language()->txt("save"), '#') - ->withOnLoadCode(function ($id) use ($form_id): string { + ->withOnLoadCode(function ($id) use ($form_submit_btn_id): string { return "$('#{$id}').click(function() { " . - "$('#{$form_id}').submit(); " . - "$(this).prop('disabled', true); " . + "$('#{$form_submit_btn_id}').click(); " . "return false; });"; }); - $modal_republish = $this->dic->ui()->factory()->modal()->roundtrip( - $this->plugin->txt('event_republish'), - $this->dic->ui()->factory()->legacy($form->getHTML()) + $modal_startworkflow = $this->dic->ui()->factory()->modal()->roundtrip( + $this->plugin->txt('event_startworkflow'), + $this->dic->ui()->factory()->legacy($tpl->get()) )->withActionButtons([$submit_btn]); - $this->setRepublishModal($modal_republish); + $this->setStartworkflowModal($modal_startworkflow); } } @@ -156,8 +203,8 @@ public function getAllComponents(): array if (!is_null($this->report_quality_modal)) { $return[] = $this->report_quality_modal; } - if (!is_null($this->republish_modal)) { - $return[] = $this->republish_modal; + if (!is_null($this->startworkflow_modal)) { + $return[] = $this->startworkflow_modal; } return $return; } @@ -191,13 +238,13 @@ public function setReportDateModal(Modal $report_date_modal): void /** * @return Modal|null */ - public function getRepublishModal() + public function getStartworkflowModal() { - return $this->republish_modal; + return $this->startworkflow_modal; } - public function setRepublishModal(Modal $republish_modal): void + public function setStartworkflowModal(Modal $startworkflow_modal): void { - $this->republish_modal = $republish_modal; + $this->startworkflow_modal = $startworkflow_modal; } } diff --git a/templates/default/startworkflow_modal.css b/templates/default/startworkflow_modal.css new file mode 100644 index 000000000..2d9d764c4 --- /dev/null +++ b/templates/default/startworkflow_modal.css @@ -0,0 +1,38 @@ +.startworkflow-form .wf-section { + border-top: 1px solid #e5e5e5; + padding: 10px 20px 0 20px; +} +.startworkflow-form .wf-section .wf-section-header { + font-weight: 600; +} +.startworkflow-form .wf-section:last-of-type { + margin-top: 20px; +} +.startworkflow-form .configField { + width: auto !important; + height: 25px; + font-size: 12px; + color: #161616; + border: 1px solid #757575; + border-radius: 0; +} + +.startworkflow-form .configLabel { + width: 175px; +} +.startworkflow-form ul li { + align-items: center; +} +.startworkflow-form ul li .configField { + margin: 0; +} +.startworkflow-form ul li label.configListLabel { + margin-left: 10px; + margin-right: 10px; + margin-top: 4px; + text-align: justify; + min-width: 175px; +} +.startworkflow-form .submit-btn { + display: none; +} diff --git a/templates/default/tpl.icon.html b/templates/default/tpl.icon.html new file mode 100644 index 000000000..c19a2652d --- /dev/null +++ b/templates/default/tpl.icon.html @@ -0,0 +1,3 @@ + +{ICON_ALT} + diff --git a/templates/default/tpl.startworkflow_configpanel_block.html b/templates/default/tpl.startworkflow_configpanel_block.html new file mode 100644 index 000000000..3ea3770c8 --- /dev/null +++ b/templates/default/tpl.startworkflow_configpanel_block.html @@ -0,0 +1,3 @@ + diff --git a/templates/default/tpl.startworkflow_configpanel_section.html b/templates/default/tpl.startworkflow_configpanel_section.html new file mode 100644 index 000000000..31a2d95e8 --- /dev/null +++ b/templates/default/tpl.startworkflow_configpanel_section.html @@ -0,0 +1,4 @@ + diff --git a/templates/default/tpl.startworkflow_description_block.html b/templates/default/tpl.startworkflow_description_block.html new file mode 100644 index 000000000..08e3165c5 --- /dev/null +++ b/templates/default/tpl.startworkflow_description_block.html @@ -0,0 +1,3 @@ + diff --git a/templates/default/tpl.startworkflow_description_section.html b/templates/default/tpl.startworkflow_description_section.html new file mode 100644 index 000000000..5f3ef6e63 --- /dev/null +++ b/templates/default/tpl.startworkflow_description_section.html @@ -0,0 +1,4 @@ + diff --git a/templates/default/tpl.startworkflow_modal.html b/templates/default/tpl.startworkflow_modal.html new file mode 100644 index 000000000..060670629 --- /dev/null +++ b/templates/default/tpl.startworkflow_modal.html @@ -0,0 +1,83 @@ + +
+ +
+
+ + +
+ {WORKFLOW_DESCRIPTIONS} + {WORKFLOW_CONFIG_PANELS} + +
+
+ From e642e8fa93291eaeb1125ffbf0176c8ed2731773 Mon Sep 17 00:00:00 2001 From: ferishili Date: Wed, 25 Oct 2023 14:22:13 +0200 Subject: [PATCH 02/13] fix: not functioning table columns --- .../Conf/Workflows/class.xoctWorkflowTableGUI.php | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/classes/Conf/Workflows/class.xoctWorkflowTableGUI.php b/classes/Conf/Workflows/class.xoctWorkflowTableGUI.php index 029384644..cf0f9983d 100644 --- a/classes/Conf/Workflows/class.xoctWorkflowTableGUI.php +++ b/classes/Conf/Workflows/class.xoctWorkflowTableGUI.php @@ -61,10 +61,9 @@ public function __construct($parent, string $parent_cmd, WorkflowRepository $wor */ protected function initColumns(): void { - $cols = $this->getSelectableColumns2(); - foreach ($this->getSelectedColumns() as $col) { - $txt = $cols[$col]['txt']; - $id = $cols[$col]['id']; + foreach ($this->getSelectableColumns2() as $col) { + $txt = $col['txt']; + $id = $col['id']; $sort = false; $width = ''; switch ($id) { @@ -183,11 +182,7 @@ protected function getSelectableColumns2(): array */ public function isColumnSelected($col): bool { - if (!array_key_exists($col, $this->getSelectableColumns())) { - return true; - } - - return in_array($col, $this->getSelectedColumns()); + return true; } /** From c020003c432009ab0ef3af5fb02b9e5cfa69292d Mon Sep 17 00:00:00 2001 From: ferishili Date: Wed, 8 Nov 2023 16:16:45 +0100 Subject: [PATCH 03/13] fix for hidden inputs and latest changes from main --- .../Conf/Workflows/class.xoctWorkflowGUI.php | 3 +- classes/Event/class.xoctEventGUI.php | 5 +- js/opencast/dist/index.js | 2 +- js/opencast/src/Workflow/StartWorkflow.js | 92 +++++++++++++++++++ js/opencast/src/index.js | 4 + sql/dbupdate.php | 1 + src/Model/Workflow/WorkflowDBRepository.php | 15 +++ .../default/tpl.startworkflow_modal.html | 64 +------------ 8 files changed, 120 insertions(+), 66 deletions(-) create mode 100644 js/opencast/src/Workflow/StartWorkflow.js diff --git a/classes/Conf/Workflows/class.xoctWorkflowGUI.php b/classes/Conf/Workflows/class.xoctWorkflowGUI.php index 9f5d3bb01..4abe0d6b2 100644 --- a/classes/Conf/Workflows/class.xoctWorkflowGUI.php +++ b/classes/Conf/Workflows/class.xoctWorkflowGUI.php @@ -71,6 +71,8 @@ public function __construct(WorkflowRepository $workflow_repository) $this->workflow_repository = $workflow_repository; $this->factory = $ui->factory(); $this->main_tpl = $ui->mainTemplate(); + $this->wf_subtab_active = + $this->http->request()->getQueryParams()['wf_subtab_active'] ?? xoctMainGUI::SUBTAB_WORKFLOWS_SETTINGS; $this->setTab(); } @@ -95,7 +97,6 @@ protected function index() public function setTab() { $this->ctrl->saveParameter($this, 'wf_subtab_active'); - $this->wf_subtab_active = $_GET['wf_subtab_active'] ?: xoctMainGUI::SUBTAB_WORKFLOWS_SETTINGS; $this->tabs->setSubTabActive($this->wf_subtab_active); } diff --git a/classes/Event/class.xoctEventGUI.php b/classes/Event/class.xoctEventGUI.php index 025770683..6ba5bbb49 100755 --- a/classes/Event/class.xoctEventGUI.php +++ b/classes/Event/class.xoctEventGUI.php @@ -244,6 +244,10 @@ public function executeCommand(): void protected function performCommand($cmd) { $this->tabs->activateTab(ilObjOpenCastGUI::TAB_EVENTS); + + // Adding the top level index.js. + $this->main_tpl->addJavaScript($this->plugin->getDirectory().'/js/opencast/dist/index.js'); + $this->main_tpl->addCss( './Customizing/global/plugins/Services/Repository/RepositoryObject/OpenCast/templates/default/events.css' ); @@ -1101,7 +1105,6 @@ protected function startWorkflow() // Take care of datetime conversion. if (strpos($type, 'datetime') !== false) { $datetime = new DateTimeImmutable($received_value); - // $datetime->setTimezone(new DateTimeZone("UTC")); $received_value = $datetime->format('Y-m-d\TH:i:s\Z'); } $value = $received_value; diff --git a/js/opencast/dist/index.js b/js/opencast/dist/index.js index c2e6629d7..ea45f3de9 100644 --- a/js/opencast/dist/index.js +++ b/js/opencast/dist/index.js @@ -1 +1 @@ -!function(t,e){"use strict";t=t&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t,e=e&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e;t.Opencast=t.Opencast||{},t.Opencast.Form=t.Opencast.Form||{},t.Opencast.Form.passwordToggle=new class{jquery;password_input_ids;constructor(t){this.jquery=t,this.password_input_ids=[]}init(t){try{let e=JSON.parse(t);Array.isArray(e)&&(this.password_input_ids=e)}catch(e){""!==t&&(this.password_input_ids=[t])}if(Array.isArray(this.password_input_ids)&&0!==this.password_input_ids.length){var e=this;$(document).ready((function(){$(".xoct_pw_toggle_item").click((function(t){let e="IMG"==t.target.nodeName?t.target.parentNode:t.element,s=$(e).siblings(".xoct_pw_toggle_item"),i=$(e.parentNode).siblings("input");if(i&&i.length>0){let t=i[0];$(e).hasClass("toggle-show")?$(t).attr("type","text"):$(t).attr("type","password")}$(e).hide(),$(s).show()}))})),this.password_input_ids.forEach((function(t,s){e.wrapper(t)}))}else console.warn("Unable to find any input to mask!")}wrapper(t){let e=document.getElementById(t);if(!e)return;e.setAttribute("type","password");let s=document.createElement("img");s.setAttribute("alt","show password"),s.setAttribute("class","xoct_pw_icon xoct_pw_eye"),s.setAttribute("src","./Customizing/global/plugins/Services/Repository/RepositoryObject/OpenCast/templates/images/eye.svg");let i=document.createElement("div");i.setAttribute("class","xoct_pw_toggle_item toggle-show"),i.appendChild(s);let a=document.createElement("img");a.setAttribute("alt","hide password"),a.setAttribute("class","xoct_pw_icon xoct_pw_eye-slash"),a.setAttribute("src","./Customizing/global/plugins/Services/Repository/RepositoryObject/OpenCast/templates/images/eye-slash.svg");let o=document.createElement("div");o.setAttribute("class","xoct_pw_toggle_item toggle-hide"),o.appendChild(a);let r=document.createElement("div");r.setAttribute("class","xoct_pw_toggle_container"),r.appendChild(i),r.appendChild(o);let p=e.parentElement;p.classList.add("xoct_pw_main_container");let n=document.createElement("div");n.setAttribute("class","xoct_pw_wrapper"),n.appendChild(e),n.appendChild(r),p.prepend(n)}}(e)}(il,$); +!function(e,t){"use strict";function s(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var i=s(e),n=s(t);i.default.Opencast=i.default.Opencast||{},i.default.Opencast.Form=i.default.Opencast.Form||{},i.default.Opencast.Form.passwordToggle=new class{jquery;password_input_ids;constructor(e){this.jquery=e,this.password_input_ids=[]}init(e){try{let t=JSON.parse(e);Array.isArray(t)&&(this.password_input_ids=t)}catch(t){""!==e&&(this.password_input_ids=[e])}if(Array.isArray(this.password_input_ids)&&0!==this.password_input_ids.length){var t=this;$(document).ready((function(){$(".xoct_pw_toggle_item").click((function(e){let t="IMG"==e.target.nodeName?e.target.parentNode:e.element,s=$(t).siblings(".xoct_pw_toggle_item"),i=$(t.parentNode).siblings("input");if(i&&i.length>0){let e=i[0];$(t).hasClass("toggle-show")?$(e).attr("type","text"):$(e).attr("type","password")}$(t).hide(),$(s).show()}))})),this.password_input_ids.forEach((function(e,s){t.wrapper(e)}))}else console.warn("Unable to find any input to mask!")}wrapper(e){let t=document.getElementById(e);if(!t)return;t.setAttribute("type","password");let s=document.createElement("img");s.setAttribute("alt","show password"),s.setAttribute("class","xoct_pw_icon xoct_pw_eye"),s.setAttribute("src","./Customizing/global/plugins/Services/Repository/RepositoryObject/OpenCast/templates/images/eye.svg");let i=document.createElement("div");i.setAttribute("class","xoct_pw_toggle_item toggle-show"),i.appendChild(s);let n=document.createElement("img");n.setAttribute("alt","hide password"),n.setAttribute("class","xoct_pw_icon xoct_pw_eye-slash"),n.setAttribute("src","./Customizing/global/plugins/Services/Repository/RepositoryObject/OpenCast/templates/images/eye-slash.svg");let a=document.createElement("div");a.setAttribute("class","xoct_pw_toggle_item toggle-hide"),a.appendChild(n);let o=document.createElement("div");o.setAttribute("class","xoct_pw_toggle_container"),o.appendChild(i),o.appendChild(a);let l=t.parentElement;l.classList.add("xoct_pw_main_container");let d=document.createElement("div");d.setAttribute("class","xoct_pw_wrapper"),d.appendChild(t),d.appendChild(o),l.prepend(d)}}(n.default),i.default.Opencast.Workflow=i.default.Opencast.Workflow||{},i.default.Opencast.Workflow.startWorkflow=new class{jquery;constructor(e){this.jquery=e}init(e,t){e&&t&&$((function(){$("#"+e).on("click",(function(e){e.preventDefault();let s=$("#workflow_id").val();if(!s||""==s)return $("#wf-alert").removeClass("hidden"),$("#required").addClass("hidden"),void $("#no-workflow-seleced").removeClass("hidden");let i=!0;if($("#config_panel_"+s+" .wf-inputs").each((e,t)=>{let s=$(t).attr("required"),n=$(t).attr("type"),a=$(t).val();void 0!==s&&!1!==s&&""==a.trim()&&(i=!1),void 0!==n&&("number"!=n||Number.isInteger(a)||(i=!1))}),i){$("#"+t).trigger("submit");let e=$(this).parents(".modal-content").find("button.btn.btn-default.btn-primary");e.length&&e.prop("disabled",!0)}else $("#wf-alert").removeClass("hidden"),$("#required").removeClass("hidden"),$("#no-workflow-seleced").addClass("hidden");return!1})),$("#workflow_id").on("change",(function(e){$("#wf-alert").addClass("hidden"),$("#required").addClass("hidden"),$("#no-workflow-seleced").addClass("hidden");let t=this.value,s="#desc_"+t,i="#config_panel_"+t;$(".workflows-description-section").addClass("hidden"),$(".worflow-description-block").addClass("hidden"),$(".workflows-configpanel-section").addClass("hidden"),$(".workflows-configpanel-block").addClass("hidden"),$(s).length&&($(".workflows-description-section").removeClass("hidden"),$(s).removeClass("hidden")),$(".wf-inputs").each((e,t)=>{let s=$(t).attr("id");if(s&&$("#"+s+"_default").length){let e=$("#"+s+"_default").val();$(t).val(e)}}),$(i).length&&($(".workflows-configpanel-section").removeClass("hidden"),$(i).removeClass("hidden"))}))}))}}(n.default)}(il,$); diff --git a/js/opencast/src/Workflow/StartWorkflow.js b/js/opencast/src/Workflow/StartWorkflow.js new file mode 100644 index 000000000..66090ef1b --- /dev/null +++ b/js/opencast/src/Workflow/StartWorkflow.js @@ -0,0 +1,92 @@ +/** + * StartWorkflow + * + * @author Farbod Zamani Boroujeni + */ +export default class StartWorkflow { + /** + * @type {jQuery} + */ + jquery; + + constructor( + jquery, + ){ + this.jquery = jquery; + } + + init(submit_btn_id, form_id) { + if (!submit_btn_id || !form_id) { + return; + } + $(function() { + $('#' + submit_btn_id).on('click', function(e) { + e.preventDefault(); + let workflow_id = $('#workflow_id').val(); + if (!workflow_id || workflow_id == '') { + $('#wf-alert').removeClass('hidden'); + $('#required').addClass('hidden'); + $('#no-workflow-seleced').removeClass('hidden'); + return; + } + let validated = true; + let target_config_panel_id = '#config_panel_' + workflow_id; + $(target_config_panel_id + ' .wf-inputs').each((index, element) => { + let required = $(element).attr('required'); + let type = $(element).attr('type'); + let value = $(element).val(); + if (typeof required !== 'undefined' && required !== false && value.trim() == '') { + validated = false; + } + if (typeof type !== 'undefined') { + if (type == 'number' && !Number.isInteger(value)) { + validated = false; + } + } + }); + if (validated) { + $('#' + form_id).trigger('submit'); + let btn = $(this).parents('.modal-content').find('button.btn.btn-default.btn-primary'); + if (btn.length) { + btn.prop('disabled', true); + } + } else { + $('#wf-alert').removeClass('hidden'); + $('#required').removeClass('hidden'); + $('#no-workflow-seleced').addClass('hidden'); + } + return false; + }); + $('#workflow_id').on('change', function(e){ + $('#wf-alert').addClass('hidden'); + $('#required').addClass('hidden'); + $('#no-workflow-seleced').addClass('hidden'); + let workflow_id = this.value; + let target_description_id = '#desc_' + workflow_id; + let target_config_panel_id = '#config_panel_' + workflow_id; + $('.workflows-description-section').addClass('hidden'); + $('.worflow-description-block').addClass('hidden'); + $('.workflows-configpanel-section').addClass('hidden'); + $('.workflows-configpanel-block').addClass('hidden'); + + if ($(target_description_id).length) { + $('.workflows-description-section').removeClass('hidden'); + $(target_description_id).removeClass('hidden'); + } + + $('.wf-inputs').each((index, element) => { + let input_id = $(element).attr('id'); + if (input_id && $('#' + input_id + '_default').length) { + let default_value = $('#' + input_id + '_default').val(); + $(element).val(default_value); + } + }); + + if ($(target_config_panel_id).length) { + $('.workflows-configpanel-section').removeClass('hidden'); + $(target_config_panel_id).removeClass('hidden'); + } + }); + }); + } +} diff --git a/js/opencast/src/index.js b/js/opencast/src/index.js index c908aab6e..64073467d 100644 --- a/js/opencast/src/index.js +++ b/js/opencast/src/index.js @@ -1,7 +1,11 @@ import il from 'ilias'; import $ from 'jquery'; import PasswordToggle from './Form/PasswordToggle'; +import StartWorkflow from './Workflow/StartWorkflow'; il.Opencast = il.Opencast || {}; il.Opencast.Form = il.Opencast.Form || {}; il.Opencast.Form.passwordToggle = new PasswordToggle($); + +il.Opencast.Workflow = il.Opencast.Workflow || {}; +il.Opencast.Workflow.startWorkflow = new StartWorkflow($); diff --git a/sql/dbupdate.php b/sql/dbupdate.php index af893f075..6237c580a 100644 --- a/sql/dbupdate.php +++ b/sql/dbupdate.php @@ -482,6 +482,7 @@ ?> <#42> diff --git a/src/Model/Workflow/WorkflowDBRepository.php b/src/Model/Workflow/WorkflowDBRepository.php index 747c69f97..b99e40624 100644 --- a/src/Model/Workflow/WorkflowDBRepository.php +++ b/src/Model/Workflow/WorkflowDBRepository.php @@ -486,6 +486,10 @@ private function mapConfigPanelElements(string $workflow_id, string $configurati // Replace the 'id' and 'name' attributes for input elements foreach ($inputs as $input) { + $old_id = ''; + $new_id = ''; + $old_name = ''; + $new_name = ''; if ($input->hasAttribute('id')) { $old_id = $input->getAttribute('id'); $new_id = "{$workflow_id}_{$old_id}"; @@ -523,6 +527,17 @@ private function mapConfigPanelElements(string $workflow_id, string $configurati if ($input->hasAttribute('required')) { $required[] = $new_id; } + + if ($input->hasAttribute('type')) { + $type = $input->getAttribute('type'); + // Exception for hidden inputs, to make them following the form naming convension. + if ($type == 'hidden') { + if (empty($new_name) && !empty($old_id)) { + $new_name = "{$workflow_id}[{$old_id}]"; + $input->setAttribute('name', $new_name); + } + } + } } // Replace the 'id' and 'name' attributes for select elements diff --git a/templates/default/tpl.startworkflow_modal.html b/templates/default/tpl.startworkflow_modal.html index 060670629..e65162822 100644 --- a/templates/default/tpl.startworkflow_modal.html +++ b/templates/default/tpl.startworkflow_modal.html @@ -17,67 +17,5 @@ From f232efe59544cfa931761a658f02e51329af3516 Mon Sep 17 00:00:00 2001 From: ferishili Date: Fri, 10 Nov 2023 13:54:17 +0100 Subject: [PATCH 04/13] bump minor version --- plugin.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin.php b/plugin.php index 92b7d55ec..f7bb6161d 100644 --- a/plugin.php +++ b/plugin.php @@ -1,7 +1,7 @@ Date: Fri, 10 Nov 2023 14:45:04 +0100 Subject: [PATCH 05/13] fix: adjust the validation in php-side --- classes/Event/class.xoctEventGUI.php | 7 ++++++- js/opencast/dist/index.js | 2 +- js/opencast/src/Workflow/StartWorkflow.js | 5 ----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/classes/Event/class.xoctEventGUI.php b/classes/Event/class.xoctEventGUI.php index 6ba5bbb49..de8066900 100755 --- a/classes/Event/class.xoctEventGUI.php +++ b/classes/Event/class.xoctEventGUI.php @@ -1107,7 +1107,12 @@ protected function startWorkflow() $datetime = new DateTimeImmutable($received_value); $received_value = $datetime->format('Y-m-d\TH:i:s\Z'); } - $value = $received_value; + if ($type === 'text') { + $value = strip_tags($received_value); + } + if ($type == 'number') { + $value = intval($received_value); + } } // Take care of boolean conversion. if (is_bool($value)) { diff --git a/js/opencast/dist/index.js b/js/opencast/dist/index.js index ea45f3de9..5b111f6a1 100644 --- a/js/opencast/dist/index.js +++ b/js/opencast/dist/index.js @@ -1 +1 @@ -!function(e,t){"use strict";function s(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var i=s(e),n=s(t);i.default.Opencast=i.default.Opencast||{},i.default.Opencast.Form=i.default.Opencast.Form||{},i.default.Opencast.Form.passwordToggle=new class{jquery;password_input_ids;constructor(e){this.jquery=e,this.password_input_ids=[]}init(e){try{let t=JSON.parse(e);Array.isArray(t)&&(this.password_input_ids=t)}catch(t){""!==e&&(this.password_input_ids=[e])}if(Array.isArray(this.password_input_ids)&&0!==this.password_input_ids.length){var t=this;$(document).ready((function(){$(".xoct_pw_toggle_item").click((function(e){let t="IMG"==e.target.nodeName?e.target.parentNode:e.element,s=$(t).siblings(".xoct_pw_toggle_item"),i=$(t.parentNode).siblings("input");if(i&&i.length>0){let e=i[0];$(t).hasClass("toggle-show")?$(e).attr("type","text"):$(e).attr("type","password")}$(t).hide(),$(s).show()}))})),this.password_input_ids.forEach((function(e,s){t.wrapper(e)}))}else console.warn("Unable to find any input to mask!")}wrapper(e){let t=document.getElementById(e);if(!t)return;t.setAttribute("type","password");let s=document.createElement("img");s.setAttribute("alt","show password"),s.setAttribute("class","xoct_pw_icon xoct_pw_eye"),s.setAttribute("src","./Customizing/global/plugins/Services/Repository/RepositoryObject/OpenCast/templates/images/eye.svg");let i=document.createElement("div");i.setAttribute("class","xoct_pw_toggle_item toggle-show"),i.appendChild(s);let n=document.createElement("img");n.setAttribute("alt","hide password"),n.setAttribute("class","xoct_pw_icon xoct_pw_eye-slash"),n.setAttribute("src","./Customizing/global/plugins/Services/Repository/RepositoryObject/OpenCast/templates/images/eye-slash.svg");let a=document.createElement("div");a.setAttribute("class","xoct_pw_toggle_item toggle-hide"),a.appendChild(n);let o=document.createElement("div");o.setAttribute("class","xoct_pw_toggle_container"),o.appendChild(i),o.appendChild(a);let l=t.parentElement;l.classList.add("xoct_pw_main_container");let d=document.createElement("div");d.setAttribute("class","xoct_pw_wrapper"),d.appendChild(t),d.appendChild(o),l.prepend(d)}}(n.default),i.default.Opencast.Workflow=i.default.Opencast.Workflow||{},i.default.Opencast.Workflow.startWorkflow=new class{jquery;constructor(e){this.jquery=e}init(e,t){e&&t&&$((function(){$("#"+e).on("click",(function(e){e.preventDefault();let s=$("#workflow_id").val();if(!s||""==s)return $("#wf-alert").removeClass("hidden"),$("#required").addClass("hidden"),void $("#no-workflow-seleced").removeClass("hidden");let i=!0;if($("#config_panel_"+s+" .wf-inputs").each((e,t)=>{let s=$(t).attr("required"),n=$(t).attr("type"),a=$(t).val();void 0!==s&&!1!==s&&""==a.trim()&&(i=!1),void 0!==n&&("number"!=n||Number.isInteger(a)||(i=!1))}),i){$("#"+t).trigger("submit");let e=$(this).parents(".modal-content").find("button.btn.btn-default.btn-primary");e.length&&e.prop("disabled",!0)}else $("#wf-alert").removeClass("hidden"),$("#required").removeClass("hidden"),$("#no-workflow-seleced").addClass("hidden");return!1})),$("#workflow_id").on("change",(function(e){$("#wf-alert").addClass("hidden"),$("#required").addClass("hidden"),$("#no-workflow-seleced").addClass("hidden");let t=this.value,s="#desc_"+t,i="#config_panel_"+t;$(".workflows-description-section").addClass("hidden"),$(".worflow-description-block").addClass("hidden"),$(".workflows-configpanel-section").addClass("hidden"),$(".workflows-configpanel-block").addClass("hidden"),$(s).length&&($(".workflows-description-section").removeClass("hidden"),$(s).removeClass("hidden")),$(".wf-inputs").each((e,t)=>{let s=$(t).attr("id");if(s&&$("#"+s+"_default").length){let e=$("#"+s+"_default").val();$(t).val(e)}}),$(i).length&&($(".workflows-configpanel-section").removeClass("hidden"),$(i).removeClass("hidden"))}))}))}}(n.default)}(il,$); +!function(t,e){"use strict";function n(t){return t&&"object"==typeof t&&"default"in t?t:{default:t}}var r=n(t),o=n(e);"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self&&self;function i(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var a=i(function(t,e,n){return t(n={path:e,exports:{},require:function(t,e){return function(){throw new Error("Dynamic requires are not currently supported by @rollup/plugin-commonjs")}(null==e&&n.path)}},n.exports),n.exports}((function(t,e){var n;n=()=>(()=>{var t={383:(t,e,n)=>{n.r(e),n.d(e,{default:()=>f});var r=n(622),o=n(689),i=n.n(o);function a(t){return(a="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function s(){s=function(){return t};var t={},e=Object.prototype,n=e.hasOwnProperty,r=Object.defineProperty||function(t,e,n){t[e]=n.value},o="function"==typeof Symbol?Symbol:{},i=o.iterator||"@@iterator",c=o.asyncIterator||"@@asyncIterator",u=o.toStringTag||"@@toStringTag";function l(t,e,n){return Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}),t[e]}try{l({},"")}catch(t){l=function(t,e,n){return t[e]=n}}function f(t,e,n,o){var i=e&&e.prototype instanceof d?e:d,a=Object.create(i.prototype),s=new L(o||[]);return r(a,"_invoke",{value:E(t,n,s)}),a}function h(t,e,n){try{return{type:"normal",arg:t.call(e,n)}}catch(t){return{type:"throw",arg:t}}}t.wrap=f;var p={};function d(){}function y(){}function v(){}var g={};l(g,i,(function(){return this}));var m=Object.getPrototypeOf,b=m&&m(m(S([])));b&&b!==e&&n.call(b,i)&&(g=b);var w=v.prototype=d.prototype=Object.create(g);function A(t){["next","throw","return"].forEach((function(e){l(t,e,(function(t){return this._invoke(e,t)}))}))}function x(t,e){function o(r,i,s,c){var u=h(t[r],t,i);if("throw"!==u.type){var l=u.arg,f=l.value;return f&&"object"==a(f)&&n.call(f,"__await")?e.resolve(f.__await).then((function(t){o("next",t,s,c)}),(function(t){o("throw",t,s,c)})):e.resolve(f).then((function(t){l.value=t,s(l)}),(function(t){return o("throw",t,s,c)}))}c(u.arg)}var i;r(this,"_invoke",{value:function(t,n){function r(){return new e((function(e,r){o(t,n,e,r)}))}return i=i?i.then(r,r):r()}})}function E(t,e,n){var r="suspendedStart";return function(o,i){if("executing"===r)throw new Error("Generator is already running");if("completed"===r){if("throw"===o)throw i;return{value:void 0,done:!0}}for(n.method=o,n.arg=i;;){var a=n.delegate;if(a){var s=C(a,n);if(s){if(s===p)continue;return s}}if("next"===n.method)n.sent=n._sent=n.arg;else if("throw"===n.method){if("suspendedStart"===r)throw r="completed",n.arg;n.dispatchException(n.arg)}else"return"===n.method&&n.abrupt("return",n.arg);r="executing";var c=h(t,e,n);if("normal"===c.type){if(r=n.done?"completed":"suspendedYield",c.arg===p)continue;return{value:c.arg,done:n.done}}"throw"===c.type&&(r="completed",n.method="throw",n.arg=c.arg)}}}function C(t,e){var n=e.method,r=t.iterator[n];if(void 0===r)return e.delegate=null,"throw"===n&&t.iterator.return&&(e.method="return",e.arg=void 0,C(t,e),"throw"===e.method)||"return"!==n&&(e.method="throw",e.arg=new TypeError("The iterator does not provide a '"+n+"' method")),p;var o=h(r,t.iterator,e.arg);if("throw"===o.type)return e.method="throw",e.arg=o.arg,e.delegate=null,p;var i=o.arg;return i?i.done?(e[t.resultName]=i.value,e.next=t.nextLoc,"return"!==e.method&&(e.method="next",e.arg=void 0),e.delegate=null,p):i:(e.method="throw",e.arg=new TypeError("iterator result is not an object"),e.delegate=null,p)}function k(t){var e={tryLoc:t[0]};1 in t&&(e.catchLoc=t[1]),2 in t&&(e.finallyLoc=t[2],e.afterLoc=t[3]),this.tryEntries.push(e)}function _(t){var e=t.completion||{};e.type="normal",delete e.arg,t.completion=e}function L(t){this.tryEntries=[{tryLoc:"root"}],t.forEach(k,this),this.reset(!0)}function S(t){if(t){var e=t[i];if(e)return e.call(t);if("function"==typeof t.next)return t;if(!isNaN(t.length)){var r=-1,o=function e(){for(;++r=0;--o){var i=this.tryEntries[o],a=i.completion;if("root"===i.tryLoc)return r("end");if(i.tryLoc<=this.prev){var s=n.call(i,"catchLoc"),c=n.call(i,"finallyLoc");if(s&&c){if(this.prev=0;--r){var o=this.tryEntries[r];if(o.tryLoc<=this.prev&&n.call(o,"finallyLoc")&&this.prev=0;--e){var n=this.tryEntries[e];if(n.finallyLoc===t)return this.complete(n.completion,n.afterLoc),_(n),p}},catch:function(t){for(var e=this.tryEntries.length-1;e>=0;--e){var n=this.tryEntries[e];if(n.tryLoc===t){var r=n.completion;if("throw"===r.type){var o=r.arg;_(n)}return o}}throw new Error("illegal catch attempt")},delegateYield:function(t,e,n){return this.delegate={iterator:S(t),resultName:e,nextLoc:n},"next"===this.method&&(this.arg=void 0),p}},t}function c(t,e,n,r,o,i,a){try{var s=t[i](a),c=s.value}catch(t){return void n(t)}s.done?e(c):Promise.resolve(c).then(r,o)}function u(t,e){return(u=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(t,e){return t.__proto__=e,t})(t,e)}function l(t){return(l=Object.setPrototypeOf?Object.getPrototypeOf.bind():function(t){return t.__proto__||Object.getPrototypeOf(t)})(t)}var f=function(t){!function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function");t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,writable:!0,configurable:!0}}),Object.defineProperty(t,"prototype",{writable:!1}),e&&u(t,e)}(d,t);var e,n,r,o,f,h,p=(f=d,h=function(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],(function(){}))),!0}catch(t){return!1}}(),function(){var t,e=l(f);if(h){var n=l(this).constructor;t=Reflect.construct(e,arguments,n)}else t=e.apply(this,arguments);return function(t,e){if(e&&("object"===a(e)||"function"==typeof e))return e;if(void 0!==e)throw new TypeError("Derived constructors may only return object or undefined");return function(t){if(void 0===t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return t}(t)}(this,t)});function d(){return function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}(this,d),p.apply(this,arguments)}return e=d,(n=[{key:"load",value:(r=s().mark((function t(){return s().wrap((function(t){for(;;)switch(t.prev=t.next){case 0:this.icon=this.player.getCustomPluginIcon(this.name,"buttonIcon")||i();case 1:case"end":return t.stop()}}),t,this)})),o=function(){var t=this,e=arguments;return new Promise((function(n,o){var i=r.apply(t,e);function a(t){c(i,n,o,a,s,"next",t)}function s(t){c(i,n,o,a,s,"throw",t)}a(void 0)}))},function(){return o.apply(this,arguments)})}])&&function(t,e){for(var n=0;n0&&e-1 in t)}function L(t,e){return t.nodeName&&t.nodeName.toLowerCase()===e.toLowerCase()}k.fn=k.prototype={jquery:E,constructor:k,length:0,toArray:function(){return s.call(this)},get:function(t){return null==t?s.call(this):t<0?this[t+this.length]:this[t]},pushStack:function(t){var e=k.merge(this.constructor(),t);return e.prevObject=this,e},each:function(t){return k.each(this,t)},map:function(t){return this.pushStack(k.map(this,(function(e,n){return t.call(e,n,e)})))},slice:function(){return this.pushStack(s.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},even:function(){return this.pushStack(k.grep(this,(function(t,e){return(e+1)%2})))},odd:function(){return this.pushStack(k.grep(this,(function(t,e){return e%2})))},eq:function(t){var e=this.length,n=+t+(t<0?e:0);return this.pushStack(n>=0&&n+~]|"+O+")"+O+"*"),G=new RegExp(O+"|>"),H=new RegExp(N),$=new RegExp("^"+I+"$"),V={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+j),PSEUDO:new RegExp("^"+N),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+O+"*(even|odd|(([+-]|)(\\d*)n|)"+O+"*(?:([+-]|)"+O+"*(\\d+)|))"+O+"*\\)|)","i"),bool:new RegExp("^(?:"+_+")$","i"),needsContext:new RegExp("^"+O+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+O+"*((?:-\\d)?\\d*)"+O+"*\\)|)(?=[^-]|$)","i")},Z=/^(?:input|select|textarea|button)$/i,z=/^h\d$/i,K=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,Y=/[+~]/,W=new RegExp("\\\\[\\da-fA-F]{1,6}"+O+"?|\\\\([^\\r\\n\\f])","g"),q=function(t,e){var n="0x"+t.slice(1)-65536;return e||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},X=function(){ct()},Q=ht((function(t){return!0===t.disabled&&L(t,"fieldset")}),{dir:"parentNode",next:"legend"});try{y.apply(i=s.call(D.childNodes),D.childNodes),i[D.childNodes.length].nodeType}catch(t){y={apply:function(t,e){B.apply(t,s.call(e))},call:function(t){B.apply(t,s.call(arguments,1))}}}function J(t,e,n,r){var o,i,a,s,u,l,p,d=e&&e.ownerDocument,m=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==m&&9!==m&&11!==m)return n;if(!r&&(ct(e),e=e||c,f)){if(11!==m&&(u=K.exec(t)))if(o=u[1]){if(9===m){if(!(a=e.getElementById(o)))return n;if(a.id===o)return y.call(n,a),n}else if(d&&(a=d.getElementById(o))&&J.contains(e,a)&&a.id===o)return y.call(n,a),n}else{if(u[2])return y.apply(n,e.getElementsByTagName(t)),n;if((o=u[3])&&e.getElementsByClassName)return y.apply(n,e.getElementsByClassName(o)),n}if(!(E[t+" "]||h&&h.test(t))){if(p=t,d=e,1===m&&(G.test(t)||U.test(t))){for((d=Y.test(t)&&st(e.parentNode)||e)==e&&v.scope||((s=e.getAttribute("id"))?s=k.escapeSelector(s):e.setAttribute("id",s=g)),i=(l=lt(t)).length;i--;)l[i]=(s?"#"+s:":scope")+" "+ft(l[i]);p=l.join(",")}try{return y.apply(n,d.querySelectorAll(p)),n}catch(e){E(t,!0)}finally{s===g&&e.removeAttribute("id")}}}return mt(t.replace(R,"$1"),e,n,r)}function tt(){var t=[];return function n(r,o){return t.push(r+" ")>e.cacheLength&&delete n[t.shift()],n[r+" "]=o}}function et(t){return t[g]=!0,t}function nt(t){var e=c.createElement("fieldset");try{return!!t(e)}catch(t){return!1}finally{e.parentNode&&e.parentNode.removeChild(e),e=null}}function rt(t){return function(e){return L(e,"input")&&e.type===t}}function ot(t){return function(e){return(L(e,"input")||L(e,"button"))&&e.type===t}}function it(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&Q(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function at(t){return et((function(e){return e=+e,et((function(n,r){for(var o,i=t([],n.length,e),a=i.length;a--;)n[o=i[a]]&&(n[o]=!(r[o]=n[o]))}))}))}function st(t){return t&&void 0!==t.getElementsByTagName&&t}function ct(t){var n,r=t?t.ownerDocument||t:D;return r!=c&&9===r.nodeType&&r.documentElement?(u=(c=r).documentElement,f=!k.isXMLDoc(c),d=u.matches||u.webkitMatchesSelector||u.msMatchesSelector,D!=c&&(n=c.defaultView)&&n.top!==n&&n.addEventListener("unload",X),v.getById=nt((function(t){return u.appendChild(t).id=k.expando,!c.getElementsByName||!c.getElementsByName(k.expando).length})),v.disconnectedMatch=nt((function(t){return d.call(t,"*")})),v.scope=nt((function(){return c.querySelectorAll(":scope")})),v.cssHas=nt((function(){try{return c.querySelector(":has(*,:jqfake)"),!1}catch(t){return!0}})),v.getById?(e.filter.ID=function(t){var e=t.replace(W,q);return function(t){return t.getAttribute("id")===e}},e.find.ID=function(t,e){if(void 0!==e.getElementById&&f){var n=e.getElementById(t);return n?[n]:[]}}):(e.filter.ID=function(t){var e=t.replace(W,q);return function(t){var n=void 0!==t.getAttributeNode&&t.getAttributeNode("id");return n&&n.value===e}},e.find.ID=function(t,e){if(void 0!==e.getElementById&&f){var n,r,o,i=e.getElementById(t);if(i){if((n=i.getAttributeNode("id"))&&n.value===t)return[i];for(o=e.getElementsByName(t),r=0;i=o[r++];)if((n=i.getAttributeNode("id"))&&n.value===t)return[i]}return[]}}),e.find.TAG=function(t,e){return void 0!==e.getElementsByTagName?e.getElementsByTagName(t):e.querySelectorAll(t)},e.find.CLASS=function(t,e){if(void 0!==e.getElementsByClassName&&f)return e.getElementsByClassName(t)},h=[],nt((function(t){var e;u.appendChild(t).innerHTML="",t.querySelectorAll("[selected]").length||h.push("\\["+O+"*(?:value|"+_+")"),t.querySelectorAll("[id~="+g+"-]").length||h.push("~="),t.querySelectorAll("a#"+g+"+*").length||h.push(".#.+[+~]"),t.querySelectorAll(":checked").length||h.push(":checked"),(e=c.createElement("input")).setAttribute("type","hidden"),t.appendChild(e).setAttribute("name","D"),u.appendChild(t).disabled=!0,2!==t.querySelectorAll(":disabled").length&&h.push(":enabled",":disabled"),(e=c.createElement("input")).setAttribute("name",""),t.appendChild(e),t.querySelectorAll("[name='']").length||h.push("\\["+O+"*name"+O+"*="+O+"*(?:''|\"\")")})),v.cssHas||h.push(":has"),h=h.length&&new RegExp(h.join("|")),C=function(t,e){if(t===e)return a=!0,0;var n=!t.compareDocumentPosition-!e.compareDocumentPosition;return n||(1&(n=(t.ownerDocument||t)==(e.ownerDocument||e)?t.compareDocumentPosition(e):1)||!v.sortDetached&&e.compareDocumentPosition(t)===n?t===c||t.ownerDocument==D&&J.contains(D,t)?-1:e===c||e.ownerDocument==D&&J.contains(D,e)?1:o?l.call(o,t)-l.call(o,e):0:4&n?-1:1)},c):c}for(t in J.matches=function(t,e){return J(t,null,null,e)},J.matchesSelector=function(t,e){if(ct(t),f&&!E[e+" "]&&(!h||!h.test(e)))try{var n=d.call(t,e);if(n||v.disconnectedMatch||t.document&&11!==t.document.nodeType)return n}catch(t){E(e,!0)}return J(e,c,null,[t]).length>0},J.contains=function(t,e){return(t.ownerDocument||t)!=c&&ct(t),k.contains(t,e)},J.attr=function(t,n){(t.ownerDocument||t)!=c&&ct(t);var r=e.attrHandle[n.toLowerCase()],o=r&&p.call(e.attrHandle,n.toLowerCase())?r(t,n,!f):void 0;return void 0!==o?o:t.getAttribute(n)},J.error=function(t){throw new Error("Syntax error, unrecognized expression: "+t)},k.uniqueSort=function(t){var e,n=[],r=0,i=0;if(a=!v.sortStable,o=!v.sortStable&&s.call(t,0),T.call(t,C),a){for(;e=t[i++];)e===t[i]&&(r=n.push(i));for(;r--;)P.call(t,n[r],1)}return o=null,t},k.fn.uniqueSort=function(){return this.pushStack(k.uniqueSort(s.apply(this)))},(e=k.expr={cacheLength:50,createPseudo:et,match:V,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(t){return t[1]=t[1].replace(W,q),t[3]=(t[3]||t[4]||t[5]||"").replace(W,q),"~="===t[2]&&(t[3]=" "+t[3]+" "),t.slice(0,4)},CHILD:function(t){return t[1]=t[1].toLowerCase(),"nth"===t[1].slice(0,3)?(t[3]||J.error(t[0]),t[4]=+(t[4]?t[5]+(t[6]||1):2*("even"===t[3]||"odd"===t[3])),t[5]=+(t[7]+t[8]||"odd"===t[3])):t[3]&&J.error(t[0]),t},PSEUDO:function(t){var e,n=!t[6]&&t[2];return V.CHILD.test(t[0])?null:(t[3]?t[2]=t[4]||t[5]||"":n&&H.test(n)&&(e=lt(n,!0))&&(e=n.indexOf(")",n.length-e)-n.length)&&(t[0]=t[0].slice(0,e),t[2]=n.slice(0,e)),t.slice(0,3))}},filter:{TAG:function(t){var e=t.replace(W,q).toLowerCase();return"*"===t?function(){return!0}:function(t){return L(t,e)}},CLASS:function(t){var e=w[t+" "];return e||(e=new RegExp("(^|"+O+")"+t+"("+O+"|$)"))&&w(t,(function(t){return e.test("string"==typeof t.className&&t.className||void 0!==t.getAttribute&&t.getAttribute("class")||"")}))},ATTR:function(t,e,n){return function(r){var o=J.attr(r,t);return null==o?"!="===e:!e||(o+="","="===e?o===n:"!="===e?o!==n:"^="===e?n&&0===o.indexOf(n):"*="===e?n&&o.indexOf(n)>-1:"$="===e?n&&o.slice(-n.length)===n:"~="===e?(" "+o.replace(F," ")+" ").indexOf(n)>-1:"|="===e&&(o===n||o.slice(0,n.length+1)===n+"-"))}},CHILD:function(t,e,n,r,o){var i="nth"!==t.slice(0,3),a="last"!==t.slice(-4),s="of-type"===e;return 1===r&&0===o?function(t){return!!t.parentNode}:function(e,n,c){var u,l,f,h,p,d=i!==a?"nextSibling":"previousSibling",y=e.parentNode,v=s&&e.nodeName.toLowerCase(),b=!c&&!s,w=!1;if(y){if(i){for(;d;){for(f=e;f=f[d];)if(s?L(f,v):1===f.nodeType)return!1;p=d="only"===t&&!p&&"nextSibling"}return!0}if(p=[a?y.firstChild:y.lastChild],a&&b){for(w=(h=(u=(l=y[g]||(y[g]={}))[t]||[])[0]===m&&u[1])&&u[2],f=h&&y.childNodes[h];f=++h&&f&&f[d]||(w=h=0)||p.pop();)if(1===f.nodeType&&++w&&f===e){l[t]=[m,h,w];break}}else if(b&&(w=h=(u=(l=e[g]||(e[g]={}))[t]||[])[0]===m&&u[1]),!1===w)for(;(f=++h&&f&&f[d]||(w=h=0)||p.pop())&&(!(s?L(f,v):1===f.nodeType)||!++w||(b&&((l=f[g]||(f[g]={}))[t]=[m,w]),f!==e)););return(w-=o)===r||w%r==0&&w/r>=0}}},PSEUDO:function(t,n){var r,o=e.pseudos[t]||e.setFilters[t.toLowerCase()]||J.error("unsupported pseudo: "+t);return o[g]?o(n):o.length>1?(r=[t,t,"",n],e.setFilters.hasOwnProperty(t.toLowerCase())?et((function(t,e){for(var r,i=o(t,n),a=i.length;a--;)t[r=l.call(t,i[a])]=!(e[r]=i[a])})):function(t){return o(t,0,r)}):o}},pseudos:{not:et((function(t){var e=[],n=[],r=gt(t.replace(R,"$1"));return r[g]?et((function(t,e,n,o){for(var i,a=r(t,null,o,[]),s=t.length;s--;)(i=a[s])&&(t[s]=!(e[s]=i))})):function(t,o,i){return e[0]=t,r(e,null,i,n),e[0]=null,!n.pop()}})),has:et((function(t){return function(e){return J(t,e).length>0}})),contains:et((function(t){return t=t.replace(W,q),function(e){return(e.textContent||k.text(e)).indexOf(t)>-1}})),lang:et((function(t){return $.test(t||"")||J.error("unsupported lang: "+t),t=t.replace(W,q).toLowerCase(),function(e){var n;do{if(n=f?e.lang:e.getAttribute("xml:lang")||e.getAttribute("lang"))return(n=n.toLowerCase())===t||0===n.indexOf(t+"-")}while((e=e.parentNode)&&1===e.nodeType);return!1}})),target:function(t){var e=r.location&&r.location.hash;return e&&e.slice(1)===t.id},root:function(t){return t===u},focus:function(t){return t===function(){try{return c.activeElement}catch(t){}}()&&c.hasFocus()&&!!(t.type||t.href||~t.tabIndex)},enabled:it(!1),disabled:it(!0),checked:function(t){return L(t,"input")&&!!t.checked||L(t,"option")&&!!t.selected},selected:function(t){return t.parentNode&&t.parentNode.selectedIndex,!0===t.selected},empty:function(t){for(t=t.firstChild;t;t=t.nextSibling)if(t.nodeType<6)return!1;return!0},parent:function(t){return!e.pseudos.empty(t)},header:function(t){return z.test(t.nodeName)},input:function(t){return Z.test(t.nodeName)},button:function(t){return L(t,"input")&&"button"===t.type||L(t,"button")},text:function(t){var e;return L(t,"input")&&"text"===t.type&&(null==(e=t.getAttribute("type"))||"text"===e.toLowerCase())},first:at((function(){return[0]})),last:at((function(t,e){return[e-1]})),eq:at((function(t,e,n){return[n<0?n+e:n]})),even:at((function(t,e){for(var n=0;ne?e:n;--r>=0;)t.push(r);return t})),gt:at((function(t,e,n){for(var r=n<0?n+e:n;++r1?function(e,n,r){for(var o=t.length;o--;)if(!t[o](e,n,r))return!1;return!0}:t[0]}function dt(t,e,n,r,o){for(var i,a=[],s=0,c=t.length,u=null!=e;s-1&&(i[u]=!(a[u]=h))}}else p=dt(p===a?p.splice(g,p.length):p),o?o(null,a,p,c):y.apply(a,p)}))}function vt(t){for(var r,o,i,a=t.length,s=e.relative[t[0].type],c=s||e.relative[" "],u=s?1:0,f=ht((function(t){return t===r}),c,!0),h=ht((function(t){return l.call(r,t)>-1}),c,!0),p=[function(t,e,o){var i=!s&&(o||e!=n)||((r=e).nodeType?f(t,e,o):h(t,e,o));return r=null,i}];u1&&pt(p),u>1&&ft(t.slice(0,u-1).concat({value:" "===t[u-2].type?"*":""})).replace(R,"$1"),o,u0,i=t.length>0,a=function(a,s,u,l,h){var p,d,v,g=0,b="0",w=a&&[],A=[],x=n,E=a||i&&e.find.TAG("*",h),C=m+=null==x?1:Math.random()||.1,_=E.length;for(h&&(n=s==c||s||h);b!==_&&null!=(p=E[b]);b++){if(i&&p){for(d=0,s||p.ownerDocument==c||(ct(p),u=!f);v=t[d++];)if(v(p,s||c,u)){y.call(l,p);break}h&&(m=C)}o&&((p=!v&&p)&&g--,a&&w.push(p))}if(g+=b,o&&b!==g){for(d=0;v=r[d++];)v(w,A,s,u);if(a){if(g>0)for(;b--;)w[b]||A[b]||(A[b]=S.call(l));A=dt(A)}y.apply(l,A),h&&!a&&A.length>0&&g+r.length>1&&k.uniqueSort(l)}return h&&(m=C,n=x),w};return o?et(a):a}(a,i))).selector=t}return s}function mt(t,n,r,o){var i,a,s,c,u,l="function"==typeof t&&t,h=!o&<(t=l.selector||t);if(r=r||[],1===h.length){if((a=h[0]=h[0].slice(0)).length>2&&"ID"===(s=a[0]).type&&9===n.nodeType&&f&&e.relative[a[1].type]){if(!(n=(e.find.ID(s.matches[0].replace(W,q),n)||[])[0]))return r;l&&(n=n.parentNode),t=t.slice(a.shift().value.length)}for(i=V.needsContext.test(t)?0:a.length;i--&&(s=a[i],!e.relative[c=s.type]);)if((u=e.find[c])&&(o=u(s.matches[0].replace(W,q),Y.test(a[0].type)&&st(n.parentNode)||n))){if(a.splice(i,1),!(t=o.length&&ft(a)))return y.apply(r,o),r;break}}return(l||gt(t,h))(o,n,!f,r,!n||Y.test(t)&&st(n.parentNode)||n),r}ut.prototype=e.filters=e.pseudos,e.setFilters=new ut,v.sortStable=g.split("").sort(C).join("")===g,ct(),v.sortDetached=nt((function(t){return 1&t.compareDocumentPosition(c.createElement("fieldset"))})),k.find=J,k.expr[":"]=k.expr.pseudos,k.unique=k.uniqueSort,J.compile=gt,J.select=mt,J.setDocument=ct,J.escape=k.escapeSelector,J.getText=k.text,J.isXML=k.isXMLDoc,J.selectors=k.expr,J.support=k.support,J.uniqueSort=k.uniqueSort}();var N=function(t,e,n){for(var r=[],o=void 0!==n;(t=t[e])&&9!==t.nodeType;)if(1===t.nodeType){if(o&&k(t).is(n))break;r.push(t)}return r},F=function(t,e){for(var n=[];t;t=t.nextSibling)1===t.nodeType&&t!==e&&n.push(t);return n},M=k.expr.match.needsContext,U=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function G(t,e,n){return g(e)?k.grep(t,(function(t,r){return!!e.call(t,r,t)!==n})):e.nodeType?k.grep(t,(function(t){return t===e!==n})):"string"!=typeof e?k.grep(t,(function(t){return l.call(e,t)>-1!==n})):k.filter(e,t,n)}k.filter=function(t,e,n){var r=e[0];return n&&(t=":not("+t+")"),1===e.length&&1===r.nodeType?k.find.matchesSelector(r,t)?[r]:[]:k.find.matches(t,k.grep(e,(function(t){return 1===t.nodeType})))},k.fn.extend({find:function(t){var e,n,r=this.length,o=this;if("string"!=typeof t)return this.pushStack(k(t).filter((function(){for(e=0;e1?k.uniqueSort(n):n},filter:function(t){return this.pushStack(G(this,t||[],!1))},not:function(t){return this.pushStack(G(this,t||[],!0))},is:function(t){return!!G(this,"string"==typeof t&&M.test(t)?k(t):t||[],!1).length}});var H,$=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;(k.fn.init=function(t,e,n){var r,o;if(!t)return this;if(n=n||H,"string"==typeof t){if(!(r="<"===t[0]&&">"===t[t.length-1]&&t.length>=3?[null,t,null]:$.exec(t))||!r[1]&&e)return!e||e.jquery?(e||n).find(t):this.constructor(e).find(t);if(r[1]){if(e=e instanceof k?e[0]:e,k.merge(this,k.parseHTML(r[1],e&&e.nodeType?e.ownerDocument||e:b,!0)),U.test(r[1])&&k.isPlainObject(e))for(r in e)g(this[r])?this[r](e[r]):this.attr(r,e[r]);return this}return(o=b.getElementById(r[2]))&&(this[0]=o,this.length=1),this}return t.nodeType?(this[0]=t,this.length=1,this):g(t)?void 0!==n.ready?n.ready(t):t(k):k.makeArray(t,this)}).prototype=k.fn,H=k(b);var V=/^(?:parents|prev(?:Until|All))/,Z={children:!0,contents:!0,next:!0,prev:!0};function z(t,e){for(;(t=t[e])&&1!==t.nodeType;);return t}k.fn.extend({has:function(t){var e=k(t,this),n=e.length;return this.filter((function(){for(var t=0;t-1:1===n.nodeType&&k.find.matchesSelector(n,t))){i.push(n);break}return this.pushStack(i.length>1?k.uniqueSort(i):i)},index:function(t){return t?"string"==typeof t?l.call(k(t),this[0]):l.call(this,t.jquery?t[0]:t):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(t,e){return this.pushStack(k.uniqueSort(k.merge(this.get(),k(t,e))))},addBack:function(t){return this.add(null==t?this.prevObject:this.prevObject.filter(t))}}),k.each({parent:function(t){var e=t.parentNode;return e&&11!==e.nodeType?e:null},parents:function(t){return N(t,"parentNode")},parentsUntil:function(t,e,n){return N(t,"parentNode",n)},next:function(t){return z(t,"nextSibling")},prev:function(t){return z(t,"previousSibling")},nextAll:function(t){return N(t,"nextSibling")},prevAll:function(t){return N(t,"previousSibling")},nextUntil:function(t,e,n){return N(t,"nextSibling",n)},prevUntil:function(t,e,n){return N(t,"previousSibling",n)},siblings:function(t){return F((t.parentNode||{}).firstChild,t)},children:function(t){return F(t.firstChild)},contents:function(t){return null!=t.contentDocument&&a(t.contentDocument)?t.contentDocument:(L(t,"template")&&(t=t.content||t),k.merge([],t.childNodes))}},(function(t,e){k.fn[t]=function(n,r){var o=k.map(this,e,n);return"Until"!==t.slice(-5)&&(r=n),r&&"string"==typeof r&&(o=k.filter(r,o)),this.length>1&&(Z[t]||k.uniqueSort(o),V.test(t)&&o.reverse()),this.pushStack(o)}}));var K=/[^\x20\t\r\n\f]+/g;function Y(t){return t}function W(t){throw t}function q(t,e,n,r){var o;try{t&&g(o=t.promise)?o.call(t).done(e).fail(n):t&&g(o=t.then)?o.call(t,e,n):e.apply(void 0,[t].slice(r))}catch(t){n.apply(void 0,[t])}}k.Callbacks=function(t){t="string"==typeof t?function(t){var e={};return k.each(t.match(K)||[],(function(t,n){e[n]=!0})),e}(t):k.extend({},t);var e,n,r,o,i=[],a=[],s=-1,c=function(){for(o=o||t.once,r=e=!0;a.length;s=-1)for(n=a.shift();++s-1;)i.splice(n,1),n<=s&&s--})),this},has:function(t){return t?k.inArray(t,i)>-1:i.length>0},empty:function(){return i&&(i=[]),this},disable:function(){return o=a=[],i=n="",this},disabled:function(){return!i},lock:function(){return o=a=[],n||e||(i=n=""),this},locked:function(){return!!o},fireWith:function(t,n){return o||(n=[t,(n=n||[]).slice?n.slice():n],a.push(n),e||c()),this},fire:function(){return u.fireWith(this,arguments),this},fired:function(){return!!r}};return u},k.extend({Deferred:function(t){var e=[["notify","progress",k.Callbacks("memory"),k.Callbacks("memory"),2],["resolve","done",k.Callbacks("once memory"),k.Callbacks("once memory"),0,"resolved"],["reject","fail",k.Callbacks("once memory"),k.Callbacks("once memory"),1,"rejected"]],n="pending",o={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},catch:function(t){return o.then(null,t)},pipe:function(){var t=arguments;return k.Deferred((function(n){k.each(e,(function(e,r){var o=g(t[r[4]])&&t[r[4]];i[r[1]]((function(){var t=o&&o.apply(this,arguments);t&&g(t.promise)?t.promise().progress(n.notify).done(n.resolve).fail(n.reject):n[r[0]+"With"](this,o?[t]:arguments)}))})),t=null})).promise()},then:function(t,n,o){var i=0;function a(t,e,n,o){return function(){var s=this,c=arguments,u=function(){var r,u;if(!(t=i&&(n!==W&&(s=void 0,c=[r]),e.rejectWith(s,c))}};t?l():(k.Deferred.getErrorHook?l.error=k.Deferred.getErrorHook():k.Deferred.getStackHook&&(l.error=k.Deferred.getStackHook()),r.setTimeout(l))}}return k.Deferred((function(r){e[0][3].add(a(0,r,g(o)?o:Y,r.notifyWith)),e[1][3].add(a(0,r,g(t)?t:Y)),e[2][3].add(a(0,r,g(n)?n:W))})).promise()},promise:function(t){return null!=t?k.extend(t,o):o}},i={};return k.each(e,(function(t,r){var a=r[2],s=r[5];o[r[1]]=a.add,s&&a.add((function(){n=s}),e[3-t][2].disable,e[3-t][3].disable,e[0][2].lock,e[0][3].lock),a.add(r[3].fire),i[r[0]]=function(){return i[r[0]+"With"](this===i?void 0:this,arguments),this},i[r[0]+"With"]=a.fireWith})),o.promise(i),t&&t.call(i,i),i},when:function(t){var e=arguments.length,n=e,r=Array(n),o=s.call(arguments),i=k.Deferred(),a=function(t){return function(n){r[t]=this,o[t]=arguments.length>1?s.call(arguments):n,--e||i.resolveWith(r,o)}};if(e<=1&&(q(t,i.done(a(n)).resolve,i.reject,!e),"pending"===i.state()||g(o[n]&&o[n].then)))return i.then();for(;n--;)q(o[n],a(n),i.reject);return i.promise()}});var X=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;k.Deferred.exceptionHook=function(t,e){r.console&&r.console.warn&&t&&X.test(t.name)&&r.console.warn("jQuery.Deferred exception: "+t.message,t.stack,e)},k.readyException=function(t){r.setTimeout((function(){throw t}))};var Q=k.Deferred();function J(){b.removeEventListener("DOMContentLoaded",J),r.removeEventListener("load",J),k.ready()}k.fn.ready=function(t){return Q.then(t).catch((function(t){k.readyException(t)})),this},k.extend({isReady:!1,readyWait:1,ready:function(t){(!0===t?--k.readyWait:k.isReady)||(k.isReady=!0,!0!==t&&--k.readyWait>0||Q.resolveWith(b,[k]))}}),k.ready.then=Q.then,"complete"===b.readyState||"loading"!==b.readyState&&!b.documentElement.doScroll?r.setTimeout(k.ready):(b.addEventListener("DOMContentLoaded",J),r.addEventListener("load",J));var tt=function(t,e,n,r,o,i,a){var s=0,c=t.length,u=null==n;if("object"===x(n))for(s in o=!0,n)tt(t,e,s,n[s],!0,i,a);else if(void 0!==r&&(o=!0,g(r)||(a=!0),u&&(a?(e.call(t,r),e=null):(u=e,e=function(t,e,n){return u.call(k(t),n)})),e))for(;s1,null,!0)},removeData:function(t){return this.each((function(){ct.remove(this,t)}))}}),k.extend({queue:function(t,e,n){var r;if(t)return e=(e||"fx")+"queue",r=st.get(t,e),n&&(!r||Array.isArray(n)?r=st.access(t,e,k.makeArray(n)):r.push(n)),r||[]},dequeue:function(t,e){e=e||"fx";var n=k.queue(t,e),r=n.length,o=n.shift(),i=k._queueHooks(t,e);"inprogress"===o&&(o=n.shift(),r--),o&&("fx"===e&&n.unshift("inprogress"),delete i.stop,o.call(t,(function(){k.dequeue(t,e)}),i)),!r&&i&&i.empty.fire()},_queueHooks:function(t,e){var n=e+"queueHooks";return st.get(t,n)||st.access(t,n,{empty:k.Callbacks("once memory").add((function(){st.remove(t,[e+"queue",n])}))})}}),k.fn.extend({queue:function(t,e){var n=2;return"string"!=typeof t&&(e=t,t="fx",n--),arguments.length\x20\t\r\n\f]*)/i,Lt=/^$|^module$|\/(?:java|ecma)script/i;Et=b.createDocumentFragment().appendChild(b.createElement("div")),(Ct=b.createElement("input")).setAttribute("type","radio"),Ct.setAttribute("checked","checked"),Ct.setAttribute("name","t"),Et.appendChild(Ct),v.checkClone=Et.cloneNode(!0).cloneNode(!0).lastChild.checked,Et.innerHTML="",v.noCloneChecked=!!Et.cloneNode(!0).lastChild.defaultValue,Et.innerHTML="",v.option=!!Et.lastChild;var St={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function Tt(t,e){var n;return n=void 0!==t.getElementsByTagName?t.getElementsByTagName(e||"*"):void 0!==t.querySelectorAll?t.querySelectorAll(e||"*"):[],void 0===e||e&&L(t,e)?k.merge([t],n):n}function Pt(t,e){for(var n=0,r=t.length;n",""]);var Ot=/<|&#?\w+;/;function Rt(t,e,n,r,o){for(var i,a,s,c,u,l,f=e.createDocumentFragment(),h=[],p=0,d=t.length;p-1)o&&o.push(i);else if(u=vt(i),a=Tt(f.appendChild(i),"script"),u&&Pt(a),n)for(l=0;i=a[l++];)Lt.test(i.type||"")&&n.push(i);return f}var It=/^([^.]*)(?:\.(.+)|)/;function jt(){return!0}function Dt(){return!1}function Bt(t,e,n,r,o,i){var a,s;if("object"==typeof e){for(s in"string"!=typeof n&&(r=r||n,n=void 0),e)Bt(t,s,n,r,e[s],i);return t}if(null==r&&null==o?(o=n,r=n=void 0):null==o&&("string"==typeof n?(o=r,r=void 0):(o=r,r=n,n=void 0)),!1===o)o=Dt;else if(!o)return t;return 1===i&&(a=o,(o=function(t){return k().off(t),a.apply(this,arguments)}).guid=a.guid||(a.guid=k.guid++)),t.each((function(){k.event.add(this,e,o,r,n)}))}function Nt(t,e,n){n?(st.set(t,e,!1),k.event.add(t,e,{namespace:!1,handler:function(t){var n,r=st.get(this,e);if(1&t.isTrigger&&this[e]){if(r)(k.event.special[e]||{}).delegateType&&t.stopPropagation();else if(r=s.call(arguments),st.set(this,e,r),this[e](),n=st.get(this,e),st.set(this,e,!1),r!==n)return t.stopImmediatePropagation(),t.preventDefault(),n}else r&&(st.set(this,e,k.event.trigger(r[0],r.slice(1),this)),t.stopPropagation(),t.isImmediatePropagationStopped=jt)}})):void 0===st.get(t,e)&&k.event.add(t,e,jt)}k.event={global:{},add:function(t,e,n,r,o){var i,a,s,c,u,l,f,h,p,d,y,v=st.get(t);if(it(t))for(n.handler&&(n=(i=n).handler,o=i.selector),o&&k.find.matchesSelector(yt,o),n.guid||(n.guid=k.guid++),(c=v.events)||(c=v.events=Object.create(null)),(a=v.handle)||(a=v.handle=function(e){return void 0!==k&&k.event.triggered!==e.type?k.event.dispatch.apply(t,arguments):void 0}),u=(e=(e||"").match(K)||[""]).length;u--;)p=y=(s=It.exec(e[u])||[])[1],d=(s[2]||"").split(".").sort(),p&&(f=k.event.special[p]||{},p=(o?f.delegateType:f.bindType)||p,f=k.event.special[p]||{},l=k.extend({type:p,origType:y,data:r,handler:n,guid:n.guid,selector:o,needsContext:o&&k.expr.match.needsContext.test(o),namespace:d.join(".")},i),(h=c[p])||((h=c[p]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(t,r,d,a)||t.addEventListener&&t.addEventListener(p,a)),f.add&&(f.add.call(t,l),l.handler.guid||(l.handler.guid=n.guid)),o?h.splice(h.delegateCount++,0,l):h.push(l),k.event.global[p]=!0)},remove:function(t,e,n,r,o){var i,a,s,c,u,l,f,h,p,d,y,v=st.hasData(t)&&st.get(t);if(v&&(c=v.events)){for(u=(e=(e||"").match(K)||[""]).length;u--;)if(p=y=(s=It.exec(e[u])||[])[1],d=(s[2]||"").split(".").sort(),p){for(f=k.event.special[p]||{},h=c[p=(r?f.delegateType:f.bindType)||p]||[],s=s[2]&&new RegExp("(^|\\.)"+d.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=i=h.length;i--;)l=h[i],!o&&y!==l.origType||n&&n.guid!==l.guid||s&&!s.test(l.namespace)||r&&r!==l.selector&&("**"!==r||!l.selector)||(h.splice(i,1),l.selector&&h.delegateCount--,f.remove&&f.remove.call(t,l));a&&!h.length&&(f.teardown&&!1!==f.teardown.call(t,d,v.handle)||k.removeEvent(t,p,v.handle),delete c[p])}else for(p in c)k.event.remove(t,p+e[u],n,r,!0);k.isEmptyObject(c)&&st.remove(t,"handle events")}},dispatch:function(t){var e,n,r,o,i,a,s=new Array(arguments.length),c=k.event.fix(t),u=(st.get(this,"events")||Object.create(null))[c.type]||[],l=k.event.special[c.type]||{};for(s[0]=c,e=1;e=1))for(;u!==this;u=u.parentNode||this)if(1===u.nodeType&&("click"!==t.type||!0!==u.disabled)){for(i=[],a={},n=0;n-1:k.find(o,this,null,[u]).length),a[o]&&i.push(r);i.length&&s.push({elem:u,handlers:i})}return u=this,c\s*$/g;function Gt(t,e){return L(t,"table")&&L(11!==e.nodeType?e:e.firstChild,"tr")&&k(t).children("tbody")[0]||t}function Ht(t){return t.type=(null!==t.getAttribute("type"))+"/"+t.type,t}function $t(t){return"true/"===(t.type||"").slice(0,5)?t.type=t.type.slice(5):t.removeAttribute("type"),t}function Vt(t,e){var n,r,o,i,a,s;if(1===e.nodeType){if(st.hasData(t)&&(s=st.get(t).events))for(o in st.remove(e,"handle events"),s)for(n=0,r=s[o].length;n1&&"string"==typeof d&&!v.checkClone&&Mt.test(d))return t.each((function(o){var i=t.eq(o);y&&(e[0]=d.call(this,o,i.html())),zt(i,e,n,r)}));if(h&&(i=(o=Rt(e,t[0].ownerDocument,!1,t,r)).firstChild,1===o.childNodes.length&&(o=i),i||r)){for(s=(a=k.map(Tt(o,"script"),Ht)).length;f0&&Pt(a,!c&&Tt(t,"script")),s},cleanData:function(t){for(var e,n,r,o=k.event.special,i=0;void 0!==(n=t[i]);i++)if(it(n)){if(e=n[st.expando]){if(e.events)for(r in e.events)o[r]?k.event.remove(n,r):k.removeEvent(n,r,e.handle);n[st.expando]=void 0}n[ct.expando]&&(n[ct.expando]=void 0)}}}),k.fn.extend({detach:function(t){return Kt(this,t,!0)},remove:function(t){return Kt(this,t)},text:function(t){return tt(this,(function(t){return void 0===t?k.text(this):this.empty().each((function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=t)}))}),null,t,arguments.length)},append:function(){return zt(this,arguments,(function(t){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||Gt(this,t).appendChild(t)}))},prepend:function(){return zt(this,arguments,(function(t){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var e=Gt(this,t);e.insertBefore(t,e.firstChild)}}))},before:function(){return zt(this,arguments,(function(t){this.parentNode&&this.parentNode.insertBefore(t,this)}))},after:function(){return zt(this,arguments,(function(t){this.parentNode&&this.parentNode.insertBefore(t,this.nextSibling)}))},empty:function(){for(var t,e=0;null!=(t=this[e]);e++)1===t.nodeType&&(k.cleanData(Tt(t,!1)),t.textContent="");return this},clone:function(t,e){return t=null!=t&&t,e=null==e?t:e,this.map((function(){return k.clone(this,t,e)}))},html:function(t){return tt(this,(function(t){var e=this[0]||{},n=0,r=this.length;if(void 0===t&&1===e.nodeType)return e.innerHTML;if("string"==typeof t&&!Ft.test(t)&&!St[(_t.exec(t)||["",""])[1].toLowerCase()]){t=k.htmlPrefilter(t);try{for(;n=0&&(c+=Math.max(0,Math.ceil(t["offset"+e[0].toUpperCase()+e.slice(1)]-i-c-s-.5))||0),c+u}function le(t,e,n){var r=qt(t),o=(!v.boxSizingReliable()||n)&&"border-box"===k.css(t,"boxSizing",!1,r),i=o,a=Jt(t,e,r),s="offset"+e[0].toUpperCase()+e.slice(1);if(Yt.test(a)){if(!n)return a;a="auto"}return(!v.boxSizingReliable()&&o||!v.reliableTrDimensions()&&L(t,"tr")||"auto"===a||!parseFloat(a)&&"inline"===k.css(t,"display",!1,r))&&t.getClientRects().length&&(o="border-box"===k.css(t,"boxSizing",!1,r),(i=s in t)&&(a=t[s])),(a=parseFloat(a)||0)+ue(t,e,n||(o?"border":"content"),i,r,a)+"px"}function fe(t,e,n,r,o){return new fe.prototype.init(t,e,n,r,o)}k.extend({cssHooks:{opacity:{get:function(t,e){if(e){var n=Jt(t,"opacity");return""===n?"1":n}}}},cssNumber:{animationIterationCount:!0,aspectRatio:!0,borderImageSlice:!0,columnCount:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,gridArea:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnStart:!0,gridRow:!0,gridRowEnd:!0,gridRowStart:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,scale:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeMiterlimit:!0,strokeOpacity:!0},cssProps:{},style:function(t,e,n,r){if(t&&3!==t.nodeType&&8!==t.nodeType&&t.style){var o,i,a,s=ot(e),c=Wt.test(e),u=t.style;if(c||(e=oe(s)),a=k.cssHooks[e]||k.cssHooks[s],void 0===n)return a&&"get"in a&&void 0!==(o=a.get(t,!1,r))?o:u[e];"string"==(i=typeof n)&&(o=pt.exec(n))&&o[1]&&(n=bt(t,e,o),i="number"),null!=n&&n==n&&("number"!==i||c||(n+=o&&o[3]||(k.cssNumber[s]?"":"px")),v.clearCloneStyle||""!==n||0!==e.indexOf("background")||(u[e]="inherit"),a&&"set"in a&&void 0===(n=a.set(t,n,r))||(c?u.setProperty(e,n):u[e]=n))}},css:function(t,e,n,r){var o,i,a,s=ot(e);return Wt.test(e)||(e=oe(s)),(a=k.cssHooks[e]||k.cssHooks[s])&&"get"in a&&(o=a.get(t,!0,n)),void 0===o&&(o=Jt(t,e,r)),"normal"===o&&e in se&&(o=se[e]),""===n||n?(i=parseFloat(o),!0===n||isFinite(i)?i||0:o):o}}),k.each(["height","width"],(function(t,e){k.cssHooks[e]={get:function(t,n,r){if(n)return!ie.test(k.css(t,"display"))||t.getClientRects().length&&t.getBoundingClientRect().width?le(t,e,r):Xt(t,ae,(function(){return le(t,e,r)}))},set:function(t,n,r){var o,i=qt(t),a=!v.scrollboxSize()&&"absolute"===i.position,s=(a||r)&&"border-box"===k.css(t,"boxSizing",!1,i),c=r?ue(t,e,r,s,i):0;return s&&a&&(c-=Math.ceil(t["offset"+e[0].toUpperCase()+e.slice(1)]-parseFloat(i[e])-ue(t,e,"border",!1,i)-.5)),c&&(o=pt.exec(n))&&"px"!==(o[3]||"px")&&(t.style[e]=n,n=k.css(t,e)),ce(0,n,c)}}})),k.cssHooks.marginLeft=te(v.reliableMarginLeft,(function(t,e){if(e)return(parseFloat(Jt(t,"marginLeft"))||t.getBoundingClientRect().left-Xt(t,{marginLeft:0},(function(){return t.getBoundingClientRect().left})))+"px"})),k.each({margin:"",padding:"",border:"Width"},(function(t,e){k.cssHooks[t+e]={expand:function(n){for(var r=0,o={},i="string"==typeof n?n.split(" "):[n];r<4;r++)o[t+dt[r]+e]=i[r]||i[r-2]||i[0];return o}},"margin"!==t&&(k.cssHooks[t+e].set=ce)})),k.fn.extend({css:function(t,e){return tt(this,(function(t,e,n){var r,o,i={},a=0;if(Array.isArray(e)){for(r=qt(t),o=e.length;a1)}}),k.Tween=fe,fe.prototype={constructor:fe,init:function(t,e,n,r,o,i){this.elem=t,this.prop=n,this.easing=o||k.easing._default,this.options=e,this.start=this.now=this.cur(),this.end=r,this.unit=i||(k.cssNumber[n]?"":"px")},cur:function(){var t=fe.propHooks[this.prop];return t&&t.get?t.get(this):fe.propHooks._default.get(this)},run:function(t){var e,n=fe.propHooks[this.prop];return this.options.duration?this.pos=e=k.easing[this.easing](t,this.options.duration*t,0,1,this.options.duration):this.pos=e=t,this.now=(this.end-this.start)*e+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):fe.propHooks._default.set(this),this}},fe.prototype.init.prototype=fe.prototype,fe.propHooks={_default:{get:function(t){var e;return 1!==t.elem.nodeType||null!=t.elem[t.prop]&&null==t.elem.style[t.prop]?t.elem[t.prop]:(e=k.css(t.elem,t.prop,""))&&"auto"!==e?e:0},set:function(t){k.fx.step[t.prop]?k.fx.step[t.prop](t):1!==t.elem.nodeType||!k.cssHooks[t.prop]&&null==t.elem.style[oe(t.prop)]?t.elem[t.prop]=t.now:k.style(t.elem,t.prop,t.now+t.unit)}}},fe.propHooks.scrollTop=fe.propHooks.scrollLeft={set:function(t){t.elem.nodeType&&t.elem.parentNode&&(t.elem[t.prop]=t.now)}},k.easing={linear:function(t){return t},swing:function(t){return.5-Math.cos(t*Math.PI)/2},_default:"swing"},k.fx=fe.prototype.init,k.fx.step={};var he,pe,de=/^(?:toggle|show|hide)$/,ye=/queueHooks$/;function ve(){pe&&(!1===b.hidden&&r.requestAnimationFrame?r.requestAnimationFrame(ve):r.setTimeout(ve,k.fx.interval),k.fx.tick())}function ge(){return r.setTimeout((function(){he=void 0})),he=Date.now()}function me(t,e){var n,r=0,o={height:t};for(e=e?1:0;r<4;r+=2-e)o["margin"+(n=dt[r])]=o["padding"+n]=t;return e&&(o.opacity=o.width=t),o}function be(t,e,n){for(var r,o=(we.tweeners[e]||[]).concat(we.tweeners["*"]),i=0,a=o.length;i1)},removeAttr:function(t){return this.each((function(){k.removeAttr(this,t)}))}}),k.extend({attr:function(t,e,n){var r,o,i=t.nodeType;if(3!==i&&8!==i&&2!==i)return void 0===t.getAttribute?k.prop(t,e,n):(1===i&&k.isXMLDoc(t)||(o=k.attrHooks[e.toLowerCase()]||(k.expr.match.bool.test(e)?Ae:void 0)),void 0!==n?null===n?void k.removeAttr(t,e):o&&"set"in o&&void 0!==(r=o.set(t,n,e))?r:(t.setAttribute(e,n+""),n):o&&"get"in o&&null!==(r=o.get(t,e))?r:null==(r=k.find.attr(t,e))?void 0:r)},attrHooks:{type:{set:function(t,e){if(!v.radioValue&&"radio"===e&&L(t,"input")){var n=t.value;return t.setAttribute("type",e),n&&(t.value=n),e}}}},removeAttr:function(t,e){var n,r=0,o=e&&e.match(K);if(o&&1===t.nodeType)for(;n=o[r++];)t.removeAttribute(n)}}),Ae={set:function(t,e,n){return!1===e?k.removeAttr(t,n):t.setAttribute(n,n),n}},k.each(k.expr.match.bool.source.match(/\w+/g),(function(t,e){var n=xe[e]||k.find.attr;xe[e]=function(t,e,r){var o,i,a=e.toLowerCase();return r||(i=xe[a],xe[a]=o,o=null!=n(t,e,r)?a:null,xe[a]=i),o}}));var Ee=/^(?:input|select|textarea|button)$/i,Ce=/^(?:a|area)$/i;function ke(t){return(t.match(K)||[]).join(" ")}function _e(t){return t.getAttribute&&t.getAttribute("class")||""}function Le(t){return Array.isArray(t)?t:"string"==typeof t&&t.match(K)||[]}k.fn.extend({prop:function(t,e){return tt(this,k.prop,t,e,arguments.length>1)},removeProp:function(t){return this.each((function(){delete this[k.propFix[t]||t]}))}}),k.extend({prop:function(t,e,n){var r,o,i=t.nodeType;if(3!==i&&8!==i&&2!==i)return 1===i&&k.isXMLDoc(t)||(e=k.propFix[e]||e,o=k.propHooks[e]),void 0!==n?o&&"set"in o&&void 0!==(r=o.set(t,n,e))?r:t[e]=n:o&&"get"in o&&null!==(r=o.get(t,e))?r:t[e]},propHooks:{tabIndex:{get:function(t){var e=k.find.attr(t,"tabindex");return e?parseInt(e,10):Ee.test(t.nodeName)||Ce.test(t.nodeName)&&t.href?0:-1}}},propFix:{for:"htmlFor",class:"className"}}),v.optSelected||(k.propHooks.selected={get:function(t){var e=t.parentNode;return e&&e.parentNode&&e.parentNode.selectedIndex,null},set:function(t){var e=t.parentNode;e&&(e.selectedIndex,e.parentNode&&e.parentNode.selectedIndex)}}),k.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],(function(){k.propFix[this.toLowerCase()]=this})),k.fn.extend({addClass:function(t){var e,n,r,o,i,a;return g(t)?this.each((function(e){k(this).addClass(t.call(this,e,_e(this)))})):(e=Le(t)).length?this.each((function(){if(r=_e(this),n=1===this.nodeType&&" "+ke(r)+" "){for(i=0;i-1;)n=n.replace(" "+o+" "," ");a=ke(n),r!==a&&this.setAttribute("class",a)}})):this:this.attr("class","")},toggleClass:function(t,e){var n,r,o,i,a=typeof t,s="string"===a||Array.isArray(t);return g(t)?this.each((function(n){k(this).toggleClass(t.call(this,n,_e(this),e),e)})):"boolean"==typeof e&&s?e?this.addClass(t):this.removeClass(t):(n=Le(t),this.each((function(){if(s)for(i=k(this),o=0;o-1)return!0;return!1}});var Se=/\r/g;k.fn.extend({val:function(t){var e,n,r,o=this[0];return arguments.length?(r=g(t),this.each((function(n){var o;1===this.nodeType&&(null==(o=r?t.call(this,n,k(this).val()):t)?o="":"number"==typeof o?o+="":Array.isArray(o)&&(o=k.map(o,(function(t){return null==t?"":t+""}))),(e=k.valHooks[this.type]||k.valHooks[this.nodeName.toLowerCase()])&&"set"in e&&void 0!==e.set(this,o,"value")||(this.value=o))}))):o?(e=k.valHooks[o.type]||k.valHooks[o.nodeName.toLowerCase()])&&"get"in e&&void 0!==(n=e.get(o,"value"))?n:"string"==typeof(n=o.value)?n.replace(Se,""):null==n?"":n:void 0}}),k.extend({valHooks:{option:{get:function(t){var e=k.find.attr(t,"value");return null!=e?e:ke(k.text(t))}},select:{get:function(t){var e,n,r,o=t.options,i=t.selectedIndex,a="select-one"===t.type,s=a?null:[],c=a?i+1:o.length;for(r=i<0?c:a?i:0;r-1)&&(n=!0);return n||(t.selectedIndex=-1),i}}}}),k.each(["radio","checkbox"],(function(){k.valHooks[this]={set:function(t,e){if(Array.isArray(e))return t.checked=k.inArray(k(t).val(),e)>-1}},v.checkOn||(k.valHooks[this].get=function(t){return null===t.getAttribute("value")?"on":t.value})}));var Te=r.location,Pe={guid:Date.now()},Oe=/\?/;k.parseXML=function(t){var e,n;if(!t||"string"!=typeof t)return null;try{e=(new r.DOMParser).parseFromString(t,"text/xml")}catch(t){}return n=e&&e.getElementsByTagName("parsererror")[0],e&&!n||k.error("Invalid XML: "+(n?k.map(n.childNodes,(function(t){return t.textContent})).join("\n"):t)),e};var Re=/^(?:focusinfocus|focusoutblur)$/,Ie=function(t){t.stopPropagation()};k.extend(k.event,{trigger:function(t,e,n,o){var i,a,s,c,u,l,f,h,d=[n||b],y=p.call(t,"type")?t.type:t,v=p.call(t,"namespace")?t.namespace.split("."):[];if(a=h=s=n=n||b,3!==n.nodeType&&8!==n.nodeType&&!Re.test(y+k.event.triggered)&&(y.indexOf(".")>-1&&(v=y.split("."),y=v.shift(),v.sort()),u=y.indexOf(":")<0&&"on"+y,(t=t[k.expando]?t:new k.Event(y,"object"==typeof t&&t)).isTrigger=o?2:3,t.namespace=v.join("."),t.rnamespace=t.namespace?new RegExp("(^|\\.)"+v.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,t.result=void 0,t.target||(t.target=n),e=null==e?[t]:k.makeArray(e,[t]),f=k.event.special[y]||{},o||!f.trigger||!1!==f.trigger.apply(n,e))){if(!o&&!f.noBubble&&!m(n)){for(c=f.delegateType||y,Re.test(c+y)||(a=a.parentNode);a;a=a.parentNode)d.push(a),s=a;s===(n.ownerDocument||b)&&d.push(s.defaultView||s.parentWindow||r)}for(i=0;(a=d[i++])&&!t.isPropagationStopped();)h=a,t.type=i>1?c:f.bindType||y,(l=(st.get(a,"events")||Object.create(null))[t.type]&&st.get(a,"handle"))&&l.apply(a,e),(l=u&&a[u])&&l.apply&&it(a)&&(t.result=l.apply(a,e),!1===t.result&&t.preventDefault());return t.type=y,o||t.isDefaultPrevented()||f._default&&!1!==f._default.apply(d.pop(),e)||!it(n)||u&&g(n[y])&&!m(n)&&((s=n[u])&&(n[u]=null),k.event.triggered=y,t.isPropagationStopped()&&h.addEventListener(y,Ie),n[y](),t.isPropagationStopped()&&h.removeEventListener(y,Ie),k.event.triggered=void 0,s&&(n[u]=s)),t.result}},simulate:function(t,e,n){var r=k.extend(new k.Event,n,{type:t,isSimulated:!0});k.event.trigger(r,null,e)}}),k.fn.extend({trigger:function(t,e){return this.each((function(){k.event.trigger(t,e,this)}))},triggerHandler:function(t,e){var n=this[0];if(n)return k.event.trigger(t,e,n,!0)}});var je=/\[\]$/,De=/\r?\n/g,Be=/^(?:submit|button|image|reset|file)$/i,Ne=/^(?:input|select|textarea|keygen)/i;function Fe(t,e,n,r){var o;if(Array.isArray(e))k.each(e,(function(e,o){n||je.test(t)?r(t,o):Fe(t+"["+("object"==typeof o&&null!=o?e:"")+"]",o,n,r)}));else if(n||"object"!==x(e))r(t,e);else for(o in e)Fe(t+"["+o+"]",e[o],n,r)}k.param=function(t,e){var n,r=[],o=function(t,e){var n=g(e)?e():e;r[r.length]=encodeURIComponent(t)+"="+encodeURIComponent(null==n?"":n)};if(null==t)return"";if(Array.isArray(t)||t.jquery&&!k.isPlainObject(t))k.each(t,(function(){o(this.name,this.value)}));else for(n in t)Fe(n,t[n],e,o);return r.join("&")},k.fn.extend({serialize:function(){return k.param(this.serializeArray())},serializeArray:function(){return this.map((function(){var t=k.prop(this,"elements");return t?k.makeArray(t):this})).filter((function(){var t=this.type;return this.name&&!k(this).is(":disabled")&&Ne.test(this.nodeName)&&!Be.test(t)&&(this.checked||!kt.test(t))})).map((function(t,e){var n=k(this).val();return null==n?null:Array.isArray(n)?k.map(n,(function(t){return{name:e.name,value:t.replace(De,"\r\n")}})):{name:e.name,value:n.replace(De,"\r\n")}})).get()}});var Me=/%20/g,Ue=/#.*$/,Ge=/([?&])_=[^&]*/,He=/^(.*?):[ \t]*([^\r\n]*)$/gm,$e=/^(?:GET|HEAD)$/,Ve=/^\/\//,Ze={},ze={},Ke="*/".concat("*"),Ye=b.createElement("a");function We(t){return function(e,n){"string"!=typeof e&&(n=e,e="*");var r,o=0,i=e.toLowerCase().match(K)||[];if(g(n))for(;r=i[o++];)"+"===r[0]?(r=r.slice(1)||"*",(t[r]=t[r]||[]).unshift(n)):(t[r]=t[r]||[]).push(n)}}function qe(t,e,n,r){var o={},i=t===ze;function a(s){var c;return o[s]=!0,k.each(t[s]||[],(function(t,s){var u=s(e,n,r);return"string"!=typeof u||i||o[u]?i?!(c=u):void 0:(e.dataTypes.unshift(u),a(u),!1)})),c}return a(e.dataTypes[0])||!o["*"]&&a("*")}function Xe(t,e){var n,r,o=k.ajaxSettings.flatOptions||{};for(n in e)void 0!==e[n]&&((o[n]?t:r||(r={}))[n]=e[n]);return r&&k.extend(!0,t,r),t}Ye.href=Te.href,k.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:Te.href,type:"GET",isLocal:/^(?:about|app|app-storage|.+-extension|file|res|widget):$/.test(Te.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Ke,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":k.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(t,e){return e?Xe(Xe(t,k.ajaxSettings),e):Xe(k.ajaxSettings,t)},ajaxPrefilter:We(Ze),ajaxTransport:We(ze),ajax:function(t,e){"object"==typeof t&&(e=t,t=void 0),e=e||{};var n,o,i,a,s,c,u,l,f,h,p=k.ajaxSetup({},e),d=p.context||p,y=p.context&&(d.nodeType||d.jquery)?k(d):k.event,v=k.Deferred(),g=k.Callbacks("once memory"),m=p.statusCode||{},w={},A={},x="canceled",E={readyState:0,getResponseHeader:function(t){var e;if(u){if(!a)for(a={};e=He.exec(i);)a[e[1].toLowerCase()+" "]=(a[e[1].toLowerCase()+" "]||[]).concat(e[2]);e=a[t.toLowerCase()+" "]}return null==e?null:e.join(", ")},getAllResponseHeaders:function(){return u?i:null},setRequestHeader:function(t,e){return null==u&&(t=A[t.toLowerCase()]=A[t.toLowerCase()]||t,w[t]=e),this},overrideMimeType:function(t){return null==u&&(p.mimeType=t),this},statusCode:function(t){var e;if(t)if(u)E.always(t[E.status]);else for(e in t)m[e]=[m[e],t[e]];return this},abort:function(t){var e=t||x;return n&&n.abort(e),C(0,e),this}};if(v.promise(E),p.url=((t||p.url||Te.href)+"").replace(Ve,Te.protocol+"//"),p.type=e.method||e.type||p.method||p.type,p.dataTypes=(p.dataType||"*").toLowerCase().match(K)||[""],null==p.crossDomain){c=b.createElement("a");try{c.href=p.url,c.href=c.href,p.crossDomain=Ye.protocol+"//"+Ye.host!=c.protocol+"//"+c.host}catch(t){p.crossDomain=!0}}if(p.data&&p.processData&&"string"!=typeof p.data&&(p.data=k.param(p.data,p.traditional)),qe(Ze,p,e,E),u)return E;for(f in(l=k.event&&p.global)&&0==k.active++&&k.event.trigger("ajaxStart"),p.type=p.type.toUpperCase(),p.hasContent=!$e.test(p.type),o=p.url.replace(Ue,""),p.hasContent?p.data&&p.processData&&0===(p.contentType||"").indexOf("application/x-www-form-urlencoded")&&(p.data=p.data.replace(Me,"+")):(h=p.url.slice(o.length),p.data&&(p.processData||"string"==typeof p.data)&&(o+=(Oe.test(o)?"&":"?")+p.data,delete p.data),!1===p.cache&&(o=o.replace(Ge,"$1"),h=(Oe.test(o)?"&":"?")+"_="+Pe.guid+++h),p.url=o+h),p.ifModified&&(k.lastModified[o]&&E.setRequestHeader("If-Modified-Since",k.lastModified[o]),k.etag[o]&&E.setRequestHeader("If-None-Match",k.etag[o])),(p.data&&p.hasContent&&!1!==p.contentType||e.contentType)&&E.setRequestHeader("Content-Type",p.contentType),E.setRequestHeader("Accept",p.dataTypes[0]&&p.accepts[p.dataTypes[0]]?p.accepts[p.dataTypes[0]]+("*"!==p.dataTypes[0]?", "+Ke+"; q=0.01":""):p.accepts["*"]),p.headers)E.setRequestHeader(f,p.headers[f]);if(p.beforeSend&&(!1===p.beforeSend.call(d,E,p)||u))return E.abort();if(x="abort",g.add(p.complete),E.done(p.success),E.fail(p.error),n=qe(ze,p,e,E)){if(E.readyState=1,l&&y.trigger("ajaxSend",[E,p]),u)return E;p.async&&p.timeout>0&&(s=r.setTimeout((function(){E.abort("timeout")}),p.timeout));try{u=!1,n.send(w,C)}catch(t){if(u)throw t;C(-1,t)}}else C(-1,"No Transport");function C(t,e,a,c){var f,h,b,w,A,x=e;u||(u=!0,s&&r.clearTimeout(s),n=void 0,i=c||"",E.readyState=t>0?4:0,f=t>=200&&t<300||304===t,a&&(w=function(t,e,n){for(var r,o,i,a,s=t.contents,c=t.dataTypes;"*"===c[0];)c.shift(),void 0===r&&(r=t.mimeType||e.getResponseHeader("Content-Type"));if(r)for(o in s)if(s[o]&&s[o].test(r)){c.unshift(o);break}if(c[0]in n)i=c[0];else{for(o in n){if(!c[0]||t.converters[o+" "+c[0]]){i=o;break}a||(a=o)}i=i||a}if(i)return i!==c[0]&&c.unshift(i),n[i]}(p,E,a)),!f&&k.inArray("script",p.dataTypes)>-1&&k.inArray("json",p.dataTypes)<0&&(p.converters["text script"]=function(){}),w=function(t,e,n,r){var o,i,a,s,c,u={},l=t.dataTypes.slice();if(l[1])for(a in t.converters)u[a.toLowerCase()]=t.converters[a];for(i=l.shift();i;)if(t.responseFields[i]&&(n[t.responseFields[i]]=e),!c&&r&&t.dataFilter&&(e=t.dataFilter(e,t.dataType)),c=i,i=l.shift())if("*"===i)i=c;else if("*"!==c&&c!==i){if(!(a=u[c+" "+i]||u["* "+i]))for(o in u)if((s=o.split(" "))[1]===i&&(a=u[c+" "+s[0]]||u["* "+s[0]])){!0===a?a=u[o]:!0!==u[o]&&(i=s[0],l.unshift(s[1]));break}if(!0!==a)if(a&&t.throws)e=a(e);else try{e=a(e)}catch(t){return{state:"parsererror",error:a?t:"No conversion from "+c+" to "+i}}}return{state:"success",data:e}}(p,w,E,f),f?(p.ifModified&&((A=E.getResponseHeader("Last-Modified"))&&(k.lastModified[o]=A),(A=E.getResponseHeader("etag"))&&(k.etag[o]=A)),204===t||"HEAD"===p.type?x="nocontent":304===t?x="notmodified":(x=w.state,h=w.data,f=!(b=w.error))):(b=x,!t&&x||(x="error",t<0&&(t=0))),E.status=t,E.statusText=(e||x)+"",f?v.resolveWith(d,[h,x,E]):v.rejectWith(d,[E,x,b]),E.statusCode(m),m=void 0,l&&y.trigger(f?"ajaxSuccess":"ajaxError",[E,p,f?h:b]),g.fireWith(d,[E,x]),l&&(y.trigger("ajaxComplete",[E,p]),--k.active||k.event.trigger("ajaxStop")))}return E},getJSON:function(t,e,n){return k.get(t,e,n,"json")},getScript:function(t,e){return k.get(t,void 0,e,"script")}}),k.each(["get","post"],(function(t,e){k[e]=function(t,n,r,o){return g(n)&&(o=o||r,r=n,n=void 0),k.ajax(k.extend({url:t,type:e,dataType:o,data:n,success:r},k.isPlainObject(t)&&t))}})),k.ajaxPrefilter((function(t){var e;for(e in t.headers)"content-type"===e.toLowerCase()&&(t.contentType=t.headers[e]||"")})),k._evalUrl=function(t,e,n){return k.ajax({url:t,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,converters:{"text script":function(){}},dataFilter:function(t){k.globalEval(t,e,n)}})},k.fn.extend({wrapAll:function(t){var e;return this[0]&&(g(t)&&(t=t.call(this[0])),e=k(t,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&e.insertBefore(this[0]),e.map((function(){for(var t=this;t.firstElementChild;)t=t.firstElementChild;return t})).append(this)),this},wrapInner:function(t){return g(t)?this.each((function(e){k(this).wrapInner(t.call(this,e))})):this.each((function(){var e=k(this),n=e.contents();n.length?n.wrapAll(t):e.append(t)}))},wrap:function(t){var e=g(t);return this.each((function(n){k(this).wrapAll(e?t.call(this,n):t)}))},unwrap:function(t){return this.parent(t).not("body").each((function(){k(this).replaceWith(this.childNodes)})),this}}),k.expr.pseudos.hidden=function(t){return!k.expr.pseudos.visible(t)},k.expr.pseudos.visible=function(t){return!!(t.offsetWidth||t.offsetHeight||t.getClientRects().length)},k.ajaxSettings.xhr=function(){try{return new r.XMLHttpRequest}catch(t){}};var Qe={0:200,1223:204},Je=k.ajaxSettings.xhr();v.cors=!!Je&&"withCredentials"in Je,v.ajax=Je=!!Je,k.ajaxTransport((function(t){var e,n;if(v.cors||Je&&!t.crossDomain)return{send:function(o,i){var a,s=t.xhr();if(s.open(t.type,t.url,t.async,t.username,t.password),t.xhrFields)for(a in t.xhrFields)s[a]=t.xhrFields[a];for(a in t.mimeType&&s.overrideMimeType&&s.overrideMimeType(t.mimeType),t.crossDomain||o["X-Requested-With"]||(o["X-Requested-With"]="XMLHttpRequest"),o)s.setRequestHeader(a,o[a]);e=function(t){return function(){e&&(e=n=s.onload=s.onerror=s.onabort=s.ontimeout=s.onreadystatechange=null,"abort"===t?s.abort():"error"===t?"number"!=typeof s.status?i(0,"error"):i(s.status,s.statusText):i(Qe[s.status]||s.status,s.statusText,"text"!==(s.responseType||"text")||"string"!=typeof s.responseText?{binary:s.response}:{text:s.responseText},s.getAllResponseHeaders()))}},s.onload=e(),n=s.onerror=s.ontimeout=e("error"),void 0!==s.onabort?s.onabort=n:s.onreadystatechange=function(){4===s.readyState&&r.setTimeout((function(){e&&n()}))},e=e("abort");try{s.send(t.hasContent&&t.data||null)}catch(t){if(e)throw t}},abort:function(){e&&e()}}})),k.ajaxPrefilter((function(t){t.crossDomain&&(t.contents.script=!1)})),k.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(t){return k.globalEval(t),t}}}),k.ajaxPrefilter("script",(function(t){void 0===t.cache&&(t.cache=!1),t.crossDomain&&(t.type="GET")})),k.ajaxTransport("script",(function(t){var e,n;if(t.crossDomain||t.scriptAttrs)return{send:function(r,o){e=k("