diff --git a/.github/workflows/moodle-ci.yml b/.github/workflows/moodle-ci.yml
index 7515255..aaec0c7 100644
--- a/.github/workflows/moodle-ci.yml
+++ b/.github/workflows/moodle-ci.yml
@@ -8,4 +8,7 @@ on: [ push, pull_request ]
jobs:
call-moodle-ci-workflow:
- uses: Opencast-Moodle/moodle-workflows-opencast/.github/workflows/moodle-ci.yml@main
\ No newline at end of file
+ uses: Opencast-Moodle/moodle-workflows-opencast/.github/workflows/moodle-ci.yml@main
+ with:
+ requires-block-plugin: true
+ branch-block-plugin: main
diff --git a/amd/build/maintenance.min.js b/amd/build/maintenance.min.js
new file mode 100644
index 0000000..75f393a
--- /dev/null
+++ b/amd/build/maintenance.min.js
@@ -0,0 +1,10 @@
+define("tool_opencast/maintenance",["exports","core/ajax","core/notification","core/str","core/toast"],(function(_exports,Ajax,Notification,Str,Toast){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireWildcard(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}return newObj.default=obj,cache&&cache.set(obj,newObj),newObj}
+/**
+ * Javascript to handle maintenance mode in tool opencast.
+ *
+ * @module tool_opencast/maintenance
+ * @copyright 2024 Farbod Zamani Boroujeni (zamani@elan-ev.de)
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.notification=_exports.init=void 0,Ajax=_interopRequireWildcard(Ajax),Notification=_interopRequireWildcard(Notification),Str=_interopRequireWildcard(Str),Toast=_interopRequireWildcard(Toast);_exports.init=()=>{Str.get_strings([{key:"maintenancemode_modal_sync_confirmation_title",component:"tool_opencast"},{key:"maintenancemode_modal_sync_confirmation_text",component:"tool_opencast"},{key:"maintenancemode_modal_sync_confirmation_btn",component:"tool_opencast"},{key:"maintenancemode_modal_sync_error_title",component:"tool_opencast"},{key:"maintenancemode_modal_sync_error_noinstance_message",component:"tool_opencast"},{key:"maintenancemode_modal_sync_failed",component:"tool_opencast"},{key:"maintenancemode_modal_sync_succeeded",component:"tool_opencast"}]).then((function(jsstrings){document.querySelectorAll(".form-setting .opencast_config_dt_selector").forEach((dtblock=>{var _dtblock$dataset;if(null!=dtblock&&null!==(_dtblock$dataset=dtblock.dataset)&&void 0!==_dtblock$dataset&&_dtblock$dataset.isoptional){var _enablingelement$chec;const enablingelement=document.getElementById("".concat(dtblock.dataset.settingid,"_enabled")),initialvalue=null!==(_enablingelement$chec=null==enablingelement?void 0:enablingelement.checked)&&void 0!==_enablingelement$chec&&_enablingelement$chec,selects=dtblock.querySelectorAll(".opencast-config-dt-select");selects.forEach((select=>{select.disabled=!initialvalue})),enablingelement.addEventListener("change",(event=>{selects.forEach((select=>{select.disabled=!event.target.checked}))}))}}));document.querySelectorAll(".maintenance-sync-btn").forEach((btn=>{btn.addEventListener("click",(e=>{var _e$target,_e$target$dataset;e.preventDefault();const ocinstanceid=null===(_e$target=e.target)||void 0===_e$target||null===(_e$target$dataset=_e$target.dataset)||void 0===_e$target$dataset?void 0:_e$target$dataset.ocinstanceid;ocinstanceid?Notification.confirm(jsstrings[0],jsstrings[1],jsstrings[2],null,(()=>performSync(ocinstanceid,jsstrings))):Notification.alert(jsstrings[3],jsstrings[4])})),btn.removeAttribute("disabled"),btn.removeAttribute("title"),btn.classList.remove("disabled"),btn.classList.remove("btn-warning"),btn.classList.add("btn-primary")}))})).catch(Notification.exception)};const performSync=(ocinstanceid,jsstrings)=>{ocinstanceid&&Ajax.call([{methodname:"tool_opencast_maintenance_sync",args:{ocinstanceid:ocinstanceid}}])[0].then((data=>{null!=data&&data.status?(Toast.add(jsstrings[6],{type:"success"}),reloadWithDelay()):Toast.add(jsstrings[5],{type:"danger"})})).catch((error=>Notification.exception(error)))},reloadWithDelay=function(){let delay=arguments.length>0&&void 0!==arguments[0]?arguments[0]:3e3;setTimeout((()=>{window.location.reload()}),delay)};_exports.notification=(message,level,notify)=>{var _window;null!==(_window=window)&&void 0!==_window&&_window.ocMaintenanceNotified||!notify||(Notification.addNotification({message:message,type:level}),window.ocMaintenanceNotified=!0)}}));
+
+//# sourceMappingURL=maintenance.min.js.map
\ No newline at end of file
diff --git a/amd/build/maintenance.min.js.map b/amd/build/maintenance.min.js.map
new file mode 100644
index 0000000..4aa4f2a
--- /dev/null
+++ b/amd/build/maintenance.min.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"maintenance.min.js","sources":["../src/maintenance.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Javascript to handle maintenance mode in tool opencast.\n *\n * @module tool_opencast/maintenance\n * @copyright 2024 Farbod Zamani Boroujeni (zamani@elan-ev.de)\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport * as Ajax from 'core/ajax';\nimport * as Notification from 'core/notification';\nimport * as Str from 'core/str';\nimport * as Toast from 'core/toast';\n\n/**\n * Initializes the tool maintenance js module.\n */\nexport const init = () => {\n // Load strings\n var strings = [\n {key: 'maintenancemode_modal_sync_confirmation_title', component: 'tool_opencast'},\n {key: 'maintenancemode_modal_sync_confirmation_text', component: 'tool_opencast'},\n {key: 'maintenancemode_modal_sync_confirmation_btn', component: 'tool_opencast'},\n {key: 'maintenancemode_modal_sync_error_title', component: 'tool_opencast'},\n {key: 'maintenancemode_modal_sync_error_noinstance_message', component: 'tool_opencast'},\n {key: 'maintenancemode_modal_sync_failed', component: 'tool_opencast'},\n {key: 'maintenancemode_modal_sync_succeeded', component: 'tool_opencast'},\n ];\n Str.get_strings(strings).then(function(jsstrings) {\n // Required functionality for admin_setting_configdatetimeselector.\n const datetimeselectors = document.querySelectorAll('.form-setting .opencast_config_dt_selector');\n datetimeselectors.forEach((dtblock) => {\n if (dtblock?.dataset?.isoptional) {\n const enablingelement = document.getElementById(`${dtblock.dataset.settingid}_enabled`);\n const initialvalue = enablingelement?.checked ?? false;\n const selects = dtblock.querySelectorAll(`.opencast-config-dt-select`);\n selects.forEach((select) => {\n select.disabled = !initialvalue;\n });\n enablingelement.addEventListener('change', (event) => {\n selects.forEach((select) => {\n select.disabled = !event.target.checked;\n });\n });\n }\n });\n\n // Sync Button.\n const syncbtns = document.querySelectorAll('.maintenance-sync-btn');\n syncbtns.forEach((btn) => {\n btn.addEventListener('click', (e) => {\n e.preventDefault();\n const ocinstanceid = e.target?.dataset?.ocinstanceid;\n if (!ocinstanceid) {\n Notification.alert(jsstrings[3], jsstrings[4]);\n return;\n }\n\n Notification.confirm(\n jsstrings[0], jsstrings[1], jsstrings[2], null,\n () => performSync(ocinstanceid, jsstrings)\n );\n });\n // Make the button accessible to use after the listener is added.\n btn.removeAttribute('disabled');\n btn.removeAttribute('title');\n btn.classList.remove('disabled');\n btn.classList.remove('btn-warning');\n btn.classList.add('btn-primary');\n });\n\n return;\n }).catch(Notification.exception);\n};\n\n/**\n * Perform sync request via Ajax call.\n * @param {int} ocinstanceid\n * @param {array} jsstrings\n */\nconst performSync = (ocinstanceid, jsstrings) => {\n if (!ocinstanceid) {\n return;\n }\n Ajax.call([{\n methodname: 'tool_opencast_maintenance_sync',\n args: {ocinstanceid: ocinstanceid},\n }])[0]\n .then((data) => {\n if (!data?.status) {\n Toast.add(jsstrings[5], {type: 'danger'});\n return;\n }\n Toast.add(jsstrings[6], {type: 'success'});\n reloadWithDelay();\n return;\n })\n .catch((error) => Notification.exception(error));\n};\n\n/**\n * Reloads the current page with a delay.\n * @param {int} delay default 3000 ms\n */\nconst reloadWithDelay = (delay = 3000) => {\n setTimeout(() => {\n window.location.reload();\n }, delay);\n};\n\n/**\n * Opencast Tool maintenance notification handler.\n *\n * It is used to make sure that there is only one maintenance notification printed at a time.\n *\n * @param {string} message\n * @param {string} level\n * @param {bool} notify\n */\nexport const notification = (message, level, notify) => {\n if (!window?.ocMaintenanceNotified && notify) {\n Notification.addNotification({\n message: message,\n type: level\n });\n window.ocMaintenanceNotified = true;\n }\n};\n"],"names":["Str","get_strings","key","component","then","jsstrings","document","querySelectorAll","forEach","dtblock","dataset","_dtblock$dataset","isoptional","enablingelement","getElementById","settingid","initialvalue","checked","selects","select","disabled","addEventListener","event","target","btn","e","preventDefault","ocinstanceid","_e$target","_e$target$dataset","Notification","confirm","performSync","alert","removeAttribute","classList","remove","add","catch","exception","Ajax","call","methodname","args","data","status","Toast","type","reloadWithDelay","error","delay","setTimeout","window","location","reload","message","level","notify","_window","ocMaintenanceNotified","addNotification"],"mappings":";;;;;;;kRA+BoB,KAWhBA,IAAIC,YATU,CACV,CAACC,IAAK,gDAAiDC,UAAW,iBAClE,CAACD,IAAK,+CAAgDC,UAAW,iBACjE,CAACD,IAAK,8CAA+CC,UAAW,iBAChE,CAACD,IAAK,yCAA0CC,UAAW,iBAC3D,CAACD,IAAK,sDAAuDC,UAAW,iBACxE,CAACD,IAAK,oCAAqCC,UAAW,iBACtD,CAACD,IAAK,uCAAwCC,UAAW,mBAEpCC,MAAK,SAASC,WAETC,SAASC,iBAAiB,8CAClCC,SAASC,kCACnBA,MAAAA,kCAAAA,QAASC,qCAATC,iBAAkBC,WAAY,iCACxBC,gBAAkBP,SAASQ,yBAAkBL,QAAQC,QAAQK,uBAC7DC,2CAAeH,MAAAA,uBAAAA,gBAAiBI,gEAChCC,QAAUT,QAAQF,+CACxBW,QAAQV,SAASW,SACbA,OAAOC,UAAYJ,gBAEvBH,gBAAgBQ,iBAAiB,UAAWC,QACxCJ,QAAQV,SAASW,SACbA,OAAOC,UAAYE,MAAMC,OAAON,kBAO/BX,SAASC,iBAAiB,yBAClCC,SAASgB,MACdA,IAAIH,iBAAiB,SAAUI,oCAC3BA,EAAEC,uBACIC,+BAAeF,EAAEF,uDAAFK,UAAUlB,4CAAVmB,kBAAmBF,aACnCA,aAKLG,aAAaC,QACT1B,UAAU,GAAIA,UAAU,GAAIA,UAAU,GAAI,MAC1C,IAAM2B,YAAYL,aAActB,aANhCyB,aAAaG,MAAM5B,UAAU,GAAIA,UAAU,OAUnDmB,IAAIU,gBAAgB,YACpBV,IAAIU,gBAAgB,SACpBV,IAAIW,UAAUC,OAAO,YACrBZ,IAAIW,UAAUC,OAAO,eACrBZ,IAAIW,UAAUE,IAAI,qBAIvBC,MAAMR,aAAaS,kBAQpBP,YAAc,CAACL,aAActB,aAC1BsB,cAGLa,KAAKC,KAAK,CAAC,CACPC,WAAY,iCACZC,KAAM,CAAChB,aAAcA,iBACrB,GACHvB,MAAMwC,OACEA,MAAAA,MAAAA,KAAMC,QAIXC,MAAMT,IAAIhC,UAAU,GAAI,CAAC0C,KAAM,YAC/BC,mBAJIF,MAAMT,IAAIhC,UAAU,GAAI,CAAC0C,KAAM,cAOtCT,OAAOW,OAAUnB,aAAaS,UAAUU,UAOvCD,gBAAkB,eAACE,6DAAQ,IAC7BC,YAAW,KACPC,OAAOC,SAASC,WACjBJ,8BAYqB,CAACK,QAASC,MAAOC,sCACpCL,2BAAAM,QAAQC,wBAAyBF,SAClC3B,aAAa8B,gBAAgB,CACzBL,QAASA,QACTR,KAAMS,QAEVJ,OAAOO,uBAAwB"}
\ No newline at end of file
diff --git a/amd/src/maintenance.js b/amd/src/maintenance.js
new file mode 100644
index 0000000..ab74a14
--- /dev/null
+++ b/amd/src/maintenance.js
@@ -0,0 +1,142 @@
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see .
+
+/**
+ * Javascript to handle maintenance mode in tool opencast.
+ *
+ * @module tool_opencast/maintenance
+ * @copyright 2024 Farbod Zamani Boroujeni (zamani@elan-ev.de)
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+import * as Ajax from 'core/ajax';
+import * as Notification from 'core/notification';
+import * as Str from 'core/str';
+import * as Toast from 'core/toast';
+
+/**
+ * Initializes the tool maintenance js module.
+ */
+export const init = () => {
+ // Load strings
+ var strings = [
+ {key: 'maintenancemode_modal_sync_confirmation_title', component: 'tool_opencast'},
+ {key: 'maintenancemode_modal_sync_confirmation_text', component: 'tool_opencast'},
+ {key: 'maintenancemode_modal_sync_confirmation_btn', component: 'tool_opencast'},
+ {key: 'maintenancemode_modal_sync_error_title', component: 'tool_opencast'},
+ {key: 'maintenancemode_modal_sync_error_noinstance_message', component: 'tool_opencast'},
+ {key: 'maintenancemode_modal_sync_failed', component: 'tool_opencast'},
+ {key: 'maintenancemode_modal_sync_succeeded', component: 'tool_opencast'},
+ ];
+ Str.get_strings(strings).then(function(jsstrings) {
+ // Required functionality for admin_setting_configdatetimeselector.
+ const datetimeselectors = document.querySelectorAll('.form-setting .opencast_config_dt_selector');
+ datetimeselectors.forEach((dtblock) => {
+ if (dtblock?.dataset?.isoptional) {
+ const enablingelement = document.getElementById(`${dtblock.dataset.settingid}_enabled`);
+ const initialvalue = enablingelement?.checked ?? false;
+ const selects = dtblock.querySelectorAll(`.opencast-config-dt-select`);
+ selects.forEach((select) => {
+ select.disabled = !initialvalue;
+ });
+ enablingelement.addEventListener('change', (event) => {
+ selects.forEach((select) => {
+ select.disabled = !event.target.checked;
+ });
+ });
+ }
+ });
+
+ // Sync Button.
+ const syncbtns = document.querySelectorAll('.maintenance-sync-btn');
+ syncbtns.forEach((btn) => {
+ btn.addEventListener('click', (e) => {
+ e.preventDefault();
+ const ocinstanceid = e.target?.dataset?.ocinstanceid;
+ if (!ocinstanceid) {
+ Notification.alert(jsstrings[3], jsstrings[4]);
+ return;
+ }
+
+ Notification.confirm(
+ jsstrings[0], jsstrings[1], jsstrings[2], null,
+ () => performSync(ocinstanceid, jsstrings)
+ );
+ });
+ // Make the button accessible to use after the listener is added.
+ btn.removeAttribute('disabled');
+ btn.removeAttribute('title');
+ btn.classList.remove('disabled');
+ btn.classList.remove('btn-warning');
+ btn.classList.add('btn-primary');
+ });
+
+ return;
+ }).catch(Notification.exception);
+};
+
+/**
+ * Perform sync request via Ajax call.
+ * @param {int} ocinstanceid
+ * @param {array} jsstrings
+ */
+const performSync = (ocinstanceid, jsstrings) => {
+ if (!ocinstanceid) {
+ return;
+ }
+ Ajax.call([{
+ methodname: 'tool_opencast_maintenance_sync',
+ args: {ocinstanceid: ocinstanceid},
+ }])[0]
+ .then((data) => {
+ if (!data?.status) {
+ Toast.add(jsstrings[5], {type: 'danger'});
+ return;
+ }
+ Toast.add(jsstrings[6], {type: 'success'});
+ reloadWithDelay();
+ return;
+ })
+ .catch((error) => Notification.exception(error));
+};
+
+/**
+ * Reloads the current page with a delay.
+ * @param {int} delay default 3000 ms
+ */
+const reloadWithDelay = (delay = 3000) => {
+ setTimeout(() => {
+ window.location.reload();
+ }, delay);
+};
+
+/**
+ * Opencast Tool maintenance notification handler.
+ *
+ * It is used to make sure that there is only one maintenance notification printed at a time.
+ *
+ * @param {string} message
+ * @param {string} level
+ * @param {bool} notify
+ */
+export const notification = (message, level, notify) => {
+ if (!window?.ocMaintenanceNotified && notify) {
+ Notification.addNotification({
+ message: message,
+ type: level
+ });
+ window.ocMaintenanceNotified = true;
+ }
+};
diff --git a/classes/local/api.php b/classes/local/api.php
index acf1e70..762df10 100644
--- a/classes/local/api.php
+++ b/classes/local/api.php
@@ -58,6 +58,8 @@ class api extends \curl {
public $opencastapi;
/** @var \OpencastApi\Rest\OcRestClient the opencast REST Client instance */
public $opencastrestclient;
+ /** @var \tool_opencast\local\maintenance_class the maintenance class instance */
+ public $maintenance;
/** @var array array of supported api levels */
private static $supportedapilevel;
@@ -227,6 +229,7 @@ public function __construct($instanceid = null,
$this->timeout = settings_api::get_apitimeout($storedconfigocinstanceid);
$this->connecttimeout = settings_api::get_apiconnecttimeout($storedconfigocinstanceid);
$this->baseurl = settings_api::get_apiurl($storedconfigocinstanceid);
+ $this->maintenance = new maintenance_class($storedconfigocinstanceid);
if (empty($this->username)) {
throw new empty_configuration_exception('apiusernameempty', 'tool_opencast');
@@ -279,8 +282,56 @@ public function __construct($instanceid = null,
'timeout' => (intval($this->timeout) / 1000),
'connect_timeout' => (intval($this->connecttimeout) / 1000),
];
- $this->opencastapi = new \OpencastApi\Opencast($config, [], $enableingest);
- $this->opencastrestclient = new \OpencastApi\Rest\OcRestClient($config);
+ $this->opencastapi = $this->decorate_opencast_api_services($config, [], $enableingest);
+ $this->opencastrestclient = new \tool_opencast\proxy\decorated_opencastapi_rest_client($config, $this->maintenance);
+
+ // We notify the maintenance directly in constructor, to cover almost every external use of this class.
+ $this->notify_maintenance();
+ }
+
+ /**
+ * Decorates the Opencast API services with maintenance-aware proxy.
+ *
+ * This function creates a new Opencast API instance and wraps each of its services
+ * with a decorated proxy that is aware of the maintenance status.
+ *
+ * @param array $config The configuration array for the Opencast API.
+ * @param array $engageconfig Optional. The engage configuration array for the Opencast API. Default is an empty array.
+ * @param bool $enableingest Optional. Whether to enable ingest functionality. Default is false.
+ *
+ * @return \OpencastApi\Opencast A decorated instance of the Opencast API with maintenance-aware service proxy.
+ */
+ private function decorate_opencast_api_services(
+ array $config,
+ array $engageconfig = [],
+ bool $enableingest = false
+ ): \OpencastApi\Opencast {
+ $decoratedopencastapi = new \OpencastApi\Opencast($config, $engageconfig, $enableingest);
+ $classvars = get_object_vars($decoratedopencastapi);
+ foreach (array_keys($classvars) as $name) {
+ $decoratedopencastapi->{$name} =
+ new \tool_opencast\proxy\decorated_opencastapi_service($decoratedopencastapi->{$name}, $this->maintenance);
+ }
+ return $decoratedopencastapi;
+ }
+
+ /**
+ * Notifies about maintenance status and handles maintenance message display.
+ *
+ * This function checks if maintenance is set and, if so, handles the display
+ * of maintenance notification messages.
+ *
+ * @return void
+ */
+ private function notify_maintenance() {
+
+ // When the maintenance is not set, we do nothing!
+ if (empty($this->maintenance)) {
+ return;
+ }
+
+ // We now handle maintenance messages notification display.
+ $this->maintenance->handle_notification_message_display();
}
/**
@@ -363,6 +414,12 @@ private function get_authentication_header($runwithroles = []) {
* @throws \moodle_exception
*/
public function oc_get($resource, $runwithroles = []) {
+
+ // Check for maintenance first.
+ if (!empty($this->maintenance) && !$this->maintenance->can_access(__FUNCTION__)) {
+ return $this->maintenance->decide_access_bounce();
+ }
+
$url = $this->baseurl . $resource;
$this->resetHeader();
@@ -462,6 +519,11 @@ private function add_postname_chunkupload($file, $key) {
*/
public function oc_post($resource, $params = [], $runwithroles = []) {
+ // Check for maintenance first.
+ if (!empty($this->maintenance) && !$this->maintenance->can_access(__FUNCTION__)) {
+ return $this->maintenance->decide_access_bounce();
+ }
+
$url = $this->baseurl . $resource;
$this->resetHeader();
@@ -507,6 +569,11 @@ public function oc_post($resource, $params = [], $runwithroles = []) {
*/
public function oc_put($resource, $params = [], $runwithroles = []) {
+ // Check for maintenance first.
+ if (!empty($this->maintenance) && !$this->maintenance->can_access(__FUNCTION__)) {
+ return $this->maintenance->decide_access_bounce();
+ }
+
$url = $this->baseurl . $resource;
$this->resetHeader();
@@ -542,6 +609,11 @@ public function oc_put($resource, $params = [], $runwithroles = []) {
*/
public function oc_delete($resource, $params = [], $runwithroles = []) {
+ // Check for maintenance first.
+ if (!empty($this->maintenance) && !$this->maintenance->can_access(__FUNCTION__)) {
+ return $this->maintenance->decide_access_bounce();
+ }
+
$url = $this->baseurl . $resource;
$this->resetHeader();
@@ -634,4 +706,35 @@ public function connection_test_credentials() {
return true;
}
+
+ /**
+ * Synchronizes the maintenance status with Opencast.
+ *
+ * This function attempts to retrieve the maintenance status from Opencast
+ * and update the local maintenance mode accordingly. It is an experimental
+ * feature as the corresponding functionality may not yet exist in Opencast.
+ *
+ * @return bool Returns true if the maintenance mode was successfully updated,
+ * false if the update failed or the required properties/methods
+ * are not available.
+ */
+ public function sync_maintenance_with_opencast() {
+ // This an experimental feature, because the feature does not exist in Opencast yet.
+ if ($this->maintenance && $this->opencastapi && property_exists($this->opencastapi->baseApi, 'getMaintenance')) {
+ $response = $this->opencastapi->baseApi->getMaintenance();
+ if ($response['code'] != 200) {
+ return false;
+ }
+ $maintenanceobj = $response['body'];
+ $inmaintenance = isset($maintenanceobj->in_maintenance) ? (bool) $maintenanceobj->in_maintenance : false;
+ $readonly = isset($maintenanceobj->read_only) ? (bool) $maintenanceobj->read_only : false;
+ $maintenancemode = $inmaintenance ? maintenance_class::MODE_ENABLE : maintenance_class::MODE_DISABLE;
+ if ($inmaintenance && $readonly) {
+ $maintenancemode = maintenance_class::MODE_READONLY;
+ }
+ return $this->maintenance->update_mode_from_opencast($maintenancemode);
+ }
+
+ return false;
+ }
}
diff --git a/classes/local/maintenance_class.php b/classes/local/maintenance_class.php
new file mode 100644
index 0000000..e89ef0c
--- /dev/null
+++ b/classes/local/maintenance_class.php
@@ -0,0 +1,681 @@
+.
+
+namespace tool_opencast\local;
+
+/**
+ * Maintenance Helper class
+ *
+ * It is the brain of the maintenance system for Opencast Moodle plugins.
+ *
+ * @package tool_opencast
+ * @copyright 2024 Farbod Zamani Boroujeni, ELAN e.V.
+ * @author Farbod Zamani Boroujeni
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class maintenance_class {
+
+ /** @var int Disable mode flag id. */
+ const MODE_DISABLE = 0;
+
+ /** @var int Read-only mode flag id. */
+ const MODE_READONLY = 1;
+
+ /** @var int Enable mode flag id. */
+ const MODE_ENABLE = 2;
+
+ /** @var string Mode config id. */
+ const CONFIG_ID_MODE = 'maintenancemode';
+
+ /** @var string Nofitication level config id. */
+ const CONFIG_ID_NOTIFLEVEL = 'maintenancemode_notification_level';
+
+ /** @var string Message config id. */
+ const CONFIG_ID_MESSAGE = 'maintenancemode_message';
+
+ /** @var string Start date config id. */
+ const CONFIG_ID_STARTDATE = 'maintenancemode_startdate';
+
+ /** @var string End date config id. */
+ const CONFIG_ID_ENDDATE = 'maintenancemode_enddate';
+
+ /** @var string The name of the plugin tool_opencast. */
+ private const PLUGINNAME = 'tool_opencast';
+
+ /** @var int The maintenance mode value. */
+ private int $mode;
+
+ /** @var string The notification level value. */
+ private string $notiflevel;
+
+ /** @var string The maintenance message value. */
+ private string $message;
+
+ /** @var \stdClass The start date value. */
+ private \stdClass $startdate;
+
+ /** @var \stdClass The end date value. */
+ private \stdClass $enddate;
+
+ /** @var int The id of the Opencast instance. */
+ private int $ocinstanceid;
+
+ /**
+ * Constructor.
+ *
+ * @param int|null $ocinstanceid The id of the Opencast instance.
+ * If null, the default Opencast instance will be used.
+ */
+ public function __construct(?int $ocinstanceid) {
+ $this->ocinstanceid = $ocinstanceid ?? settings_api::get_default_ocinstance();
+ $this->init();
+ }
+
+ /**
+ * Initializes the requirements for the class.
+ * This function contains all the setters of the class.
+ *
+ * @return void
+ */
+ private function init() {
+ $this->set_mode();
+ $this->set_notiflevel();
+ $this->set_message();
+ $this->set_startdate();
+ $this->set_enddate();
+ }
+
+ /**
+ * Sets the value of the maintenance mode.
+ * Pulls the value from configs or sets a default value if not found.
+ *
+ * @return void
+ */
+ private function set_mode() {
+ $this->mode = (int) (settings_api::get_maintenancemode($this->ocinstanceid) ?? self::MODE_DISABLE);
+ }
+
+ /**
+ * Sets the value of the maintenance notification level.
+ * Pulls the value from configs or sets a default value if not found.
+ *
+ * @return void
+ */
+ private function set_notiflevel() {
+ $this->notiflevel =
+ settings_api::get_maintenancenotiflevel($this->ocinstanceid) ?? \core\output\notification::NOTIFY_WARNING;
+ }
+
+ /**
+ * Sets the value of the maintenance message.
+ * Pulls the value from configs or sets a default value if not found.
+ *
+ * @return void
+ */
+ private function set_message() {
+ $this->message = (string) settings_api::get_maintenancemessage($this->ocinstanceid) ?? '';
+ }
+
+ /**
+ * Sets the value of the maintenance start date.
+ * Pulls the value from configs or sets a default value if not found.
+ *
+ * @return void
+ */
+ private function set_startdate() {
+ $startdate = new \stdClass();
+ $configstr = settings_api::get_maintenancestartdate($this->ocinstanceid) ?? null;
+ if (!empty($configstr)) {
+ $startdate = json_decode($configstr);
+ }
+ $this->startdate = $startdate;
+ }
+
+ /**
+ * Sets the value of the maintenance end date.
+ * Pulls the value from configs or sets a default value if not found.
+ *
+ * @return void
+ */
+ private function set_enddate() {
+ $enddate = new \stdClass();
+ $configstr = settings_api::get_maintenancenddate($this->ocinstanceid) ?? null;
+ if (!empty($configstr)) {
+ $enddate = json_decode($configstr);
+ }
+ $this->enddate = $enddate;
+ }
+
+ /**
+ * Retrieves maintenance mode value.
+ *
+ * @return int maintenance mode value
+ */
+ public function get_mode() {
+ return $this->mode;
+ }
+
+ /**
+ * Retrieves maintenance notification level value.
+ *
+ * @return string maintenance notification level value
+ */
+ public function get_notiflevel() {
+ return $this->notiflevel;
+ }
+
+ /**
+ * Retrieves maintenance message value.
+ *
+ * @return string maintenance message value
+ */
+ public function get_message() {
+ return $this->message;
+ }
+
+ /**
+ * Retrieves maintenance formatted message.
+ *
+ * @param string $format message format (default FORMAT_HTML)
+ *
+ * @return string maintenance message value
+ */
+ public function get_formatted_message($format = FORMAT_HTML) {
+ return format_text($this->get_message(), $format);
+ }
+
+ /**
+ * Retrieves maintenance start date value.
+ *
+ * @return \stdClass|null maintenance start date value or null if not set.
+ */
+ public function get_startdate() {
+ return $this->startdate;
+ }
+
+ /**
+ * Retrieves maintenance end date value.
+ *
+ * @return \stdClass|null maintenance end date value or null if not set.
+ */
+ public function get_enddate() {
+ return $this->enddate;
+ }
+
+ /**
+ * Checks if the maintenance mode is activated.
+ *
+ * The function checks the current mode and time range to determine if the maintenance mode is activated.
+ * It considers the following conditions:
+ * - If the mode is disabled, it immediately returns false.
+ * - If there is no time range specified, it returns true,
+ * indicating that the maintenance mode is activated until further notice.
+ * - If the current time falls within the specified time range, it returns true.
+ * - If the current time is outside the specified time range, it returns false.
+ *
+ * @return bool True if maintenance mode is activated, false otherwise.
+ */
+ public function is_activated() {
+ // If disabled, immediately return false.
+ if ($this->mode === self::MODE_DISABLE) {
+ return false;
+ }
+
+ // Decide whether to check for date and time range.
+
+ $hastimerange = isset($this->startdate) && $this->startdate->enabled || isset($this->enddate) && $this->enddate->enabled;
+
+ // If no time range specified, that means it is activated until further notice!
+ if (!$hastimerange) {
+ return true;
+ }
+
+ // Get the now time with correct timezone!
+ $nowdatetime = new \DateTime('now', \core_date::get_user_timezone_object());
+ $nowdtimestamp = $nowdatetime->getTimestamp();
+
+ // Check if two sides of time range is enabled.
+ if ($this->startdate->enabled && $this->enddate->enabled &&
+ $nowdtimestamp >= $this->startdate->timestamp && $nowdtimestamp <= $this->enddate->timestamp) {
+ return true;
+ }
+
+ // If only the start date is enabled, we check whether the current time is after the start date.
+ if ($this->startdate->enabled && !$this->enddate->enabled && $nowdtimestamp >= $this->startdate->timestamp) {
+ return true;
+ }
+
+ // If only the end date is enabled, we check whether the current time is before the end date.
+ if (!$this->startdate->enabled && $this->enddate->enabled && $nowdtimestamp <= $this->enddate->timestamp) {
+ return true;
+ }
+
+ // If none of the above conditions are met, then it is not activated!
+ return false;
+ }
+
+ /**
+ * Evaluates whether the access to resources is possible.
+ *
+ * This function is meant to be used either in decorated proxies or in the top level methods before performing call to Opencast.
+ * It considers the following conditions:
+ * - If the maintenance is activated, if not everything is accessible.
+ * - Checks if the mode is Read-Only and the method that has been called is eligible to perform the operation.
+ *
+ * @param string $method The name of the method that is being called
+ *
+ * @return bool True if access is granted, false otherwise in case conditions are not satisfied.
+ */
+ public function can_access(string $method) {
+ // Of course, if deactivated, we allow access.
+ if (!$this->is_activated()) {
+ return true;
+ }
+
+ // Landing here means, it is activated, and we now have to check for the Read-Only mode.
+ // Read-Only mode means, only to perform methods that have word "get" in their name.
+ if ($this->mode === self::MODE_READONLY && strpos(strtolower($method), 'get') !== false) {
+ return true;
+ }
+
+ // In any case. we return false, meaning it is not accessible.
+ return false;
+ }
+
+ /**
+ * Decides how to bounce the access restriction.
+ *
+ * This function is meant to be used in the top level methods before performing call to Opencast.
+ *
+ * It simply checks the referer and/or initiator url path against the whitelist and/or blacklist in web calls,
+ * and decides what to do with the call, either by redirecting, closing window, throwing exceptions or doing nothing!
+ *
+ * It considers the following conditions:
+ * - Web calls (checks if the call is from web):
+ * - Admins are not restricted except the admin/cron.php call!
+ * - When the call is in top level course area, it means that the system is loading the plugins, so we let it pass.
+ * - By ajax calls, e.g. from Repository or H5P extension plugins, we simply throw "access_denied_exception" exception.
+ * - In case no condition from above could be met, we either redirect the call back from where it came,
+ * or to the course view page, or simply close the current window in case we could not decide what to do!
+ *
+ * - System calls (CLI):
+ * - If a call comes in from system level e.g CLI, we only throw exception.
+ *
+ * @return void
+ * @throws moodle_exception|access_denied_exception
+ * Redirection could happen.
+ */
+ public function decide_access_bounce() {
+ global $CFG, $COURSE, $SITE;
+
+ $isbahat = defined('BEHAT_SITE_RUNNING') && BEHAT_SITE_RUNNING;
+ $iscli = defined('CLI_SCRIPT') && CLI_SCRIPT;
+ $isphpunit = defined('PHPUNIT_TEST') && PHPUNIT_TEST;
+
+ $wwwroot = $CFG->wwwroot;
+
+ // Hanlde behat wwwroot.
+ if ($isbahat && empty($wwwroot)) {
+ $wwwroot = $CFG->behat_wwwroot;
+ }
+
+ $wwwrootparsed = parse_url($wwwroot);
+
+ // Make sure path exists in wwwrootparsed.
+ if (empty($wwwrootparsed) || !isset($wwwrootparsed['path'])) {
+ $wwwrootparsed['path'] = '';
+ }
+
+ $iswebrequest = isset($_SERVER['REMOTE_ADDR']); // It is a web call when the REMOTE_ADDR is set in $_SERVER!
+
+ // If it is a web request.
+ if ($iswebrequest && !$iscli && !$isphpunit) {
+ // We have to carefully decide whether to redirect back to the referer or the course page.
+ // The reason for this check is to avoid redirecting loops!
+ $referer = get_local_referer(false);
+ $requestedfrom = parse_url($referer);
+ $frompath = !empty($requestedfrom['path']) ? rtrim($requestedfrom['path'], '/') : '';
+
+ $initiator = qualified_me();
+ $requesttarget = parse_url($initiator);
+ $tagetpath = !empty($requesttarget['path']) ? rtrim($requesttarget['path'], '/') : '';
+
+ $whitelist = [];
+ $whitelist[] = $wwwrootparsed['path'];
+ $whitelist[] = $wwwrootparsed['path'] . '/course/view.php';
+ $whitelist[] = $wwwrootparsed['path'] . '/my';
+ $whitelist[] = $wwwrootparsed['path'] . '/my/courses.php';
+ $whitelist[] = $wwwrootparsed['path'] . '/course';
+
+ $blacklist = [];
+ $blacklist['block_opencast'] = $wwwrootparsed['path'] . '/blocks/opencast'; // Match for block_opencast plugin.
+ $blacklist['mod_opencast'] = $wwwrootparsed['path'] . '/mod/opencast'; // Match for mod_opencast plugin.
+ $blacklist['modedit'] = $wwwrootparsed['path'] . '/course/modedit'; // Match for mod_opencast plugin.
+ $blacklist['repository_opencast'] = $wwwrootparsed['path'] . '/repository'; // Match for repository_opencast plugin.
+ $blacklist['admin_cron'] = $wwwrootparsed['path'] . '/admin/cron'; // Match for admin cron.
+ $blacklist['admin_cron'] = $wwwrootparsed['path'] . '/local'; // Match for local plugins like och5p and och5pcore.
+
+ // If admin and it is not admin cron page,
+ // we let it pass to avoid interrupting any installation, configuration or upgrade.
+ if (is_siteadmin() && strpos($frompath, $blacklist['admin_cron']) === false) {
+ return ['code' => 404];
+ }
+
+ $fromblacklisted = $this->is_path_blacklisted($frompath, $blacklist);
+ $targetblacklisted = $this->is_path_blacklisted($tagetpath, $blacklist);
+
+ // Exception: Calls going up to course from blacklist, or nothing to do with blacklist, we do nothing!
+ if ((!$fromblacklisted && !$targetblacklisted) || // Outside reaching or loading opencast.
+ (in_array($tagetpath, $whitelist) && $fromblacklisted)) { // Going back from plugin to course or somewhere else
+ return ['code' => 404];
+ }
+
+ // Is ajax or popup, we throw error to make sure the user gets the correct form of notification.
+ if (is_in_popup() || (defined('AJAX_SCRIPT') && AJAX_SCRIPT) ||
+ isset($initiator['path']) && strpos($requesttarget['path'], 'ajax') !== false) {
+ throw new \core\exception\access_denied_exception('maintenance_exception_message', self::PLUGINNAME);
+ }
+
+ // We now have to decide whether to redirect back or close the window!
+
+ // If the requested action is from any of the white lists, we will redirect back to the same url.
+ if (in_array($frompath, $whitelist) && $targetblacklisted) {
+ redirect($referer);
+ }
+
+ if ($COURSE && $SITE && $SITE->id != $COURSE->id) {
+ $courseurl = new \moodle_url('/course/view.php', ['id' => $COURSE->id]);
+ redirect($courseurl);
+ }
+
+ // In case any of the above conditions are not met, we close the window.
+ close_window(0, true);
+ }
+
+ // If it is not yet returned, it has to throw an error, this should also cover requests coming from unit tests.
+ throw new \moodle_exception('maintenance_exception_message', self::PLUGINNAME);
+ }
+
+
+ /**
+ * Checks if a given path is blacklisted.
+ *
+ * This function determines whether the provided path matches any of the entries
+ * in the blacklist array. It uses a case-sensitive partial string match.
+ *
+ * @param string $path The path to check against the blacklist.
+ * @param array $blacklist An array of blacklisted path patterns.
+ *
+ * @return bool Returns true if the path matches any blacklist entry, false otherwise.
+ */
+ private function is_path_blacklisted(string $path, array $blacklist) {
+ if (empty($path) || empty($blacklist)) {
+ return false;
+ }
+ $filterred = array_filter($blacklist, function ($v, $k) use ($path) {
+ return strpos($path, $v) !== false;
+ }, ARRAY_FILTER_USE_BOTH);
+ return !empty($filterred);
+ }
+
+
+ /**
+ * Displays a notification message based on the maintenance mode settings.
+ *
+ * This function retrieves the formatted maintenance message, the notification level,
+ * and checks if the maintenance mode is activated. It then uses the Moodle Page API
+ * to load the 'tool_opencast/maintenance' JavaScript module and passes the necessary
+ * parameters to display the notification.
+ *
+ * @global moodle_page $PAGE The global Moodle page object.
+ * @return void
+ */
+ public function handle_notification_message_display() {
+ global $PAGE;
+
+ // Get the formatted message.
+ $message = $this->get_formatted_message();
+ // Fallback to a default maintenance message if the configured message somehow does not exist.
+ if (empty($message)) {
+ $message = get_string('maintenance_default_notification_message', self::PLUGINNAME);
+ }
+
+ // Get the level.
+ $level = $this->get_notiflevel();
+
+ // We notify only when it is activated!
+ $notify = $this->is_activated();
+
+ // Make sure that the $PAGE is ready for notifications js module load.
+ if ($PAGE) {
+ $PAGE->requires->js_call_amd('tool_opencast/maintenance', 'notification', [$message, $level, $notify]);
+ }
+ }
+
+ /**
+ * Updates the maintenance mode status fetched from opencast known as Synchronization!
+ *
+ * @param int $mode the mode to update
+ *
+ * @return bool whether the update was successful
+ */
+ public function update_mode_from_opencast(int $mode) {
+ $result = false;
+ if (in_array($mode, [self::MODE_DISABLE, self::MODE_ENABLE, self::MODE_READONLY])) {
+ $result = set_config(self::CONFIG_ID_MODE . '_' . $this->ocinstanceid, $mode, self::PLUGINNAME);
+ }
+ return $result;
+ }
+
+ // STATIC HELPER METHODS!
+
+ /**
+ * Returns mode choice options for the selection.
+ *
+ * @return array choice options
+ */
+ public static function get_admin_settings_mode_choices() {
+ return [
+ self::MODE_DISABLE => get_string('maintenancemode_disable', self::PLUGINNAME),
+ self::MODE_READONLY => get_string('maintenancemode_readonly', self::PLUGINNAME),
+ self::MODE_ENABLE => get_string('maintenancemode_enable', self::PLUGINNAME),
+ ];
+ }
+
+ /**
+ * Returns notification level choice options for the selection.
+ *
+ * @return array choice options
+ */
+ public static function get_admin_settings_notiflevel_choices() {
+ return [
+ \core\output\notification::NOTIFY_WARNING => get_string('maintenancemode_notiflevel_warning', self::PLUGINNAME),
+ \core\output\notification::NOTIFY_ERROR => get_string('maintenancemode_notiflevel_error', self::PLUGINNAME),
+ \core\output\notification::NOTIFY_INFO => get_string('maintenancemode_notiflevel_info', self::PLUGINNAME),
+ \core\output\notification::NOTIFY_SUCCESS => get_string('maintenancemode_notiflevel_success', self::PLUGINNAME),
+ ];
+ }
+
+ /**
+ * Gets the full configuration id of maintenance mode setting.
+ *
+ * @param int $ocintanceid the opencast instance id
+ * @param bool $withpluginname flag to determine whether to prepend plugin name.
+ *
+ * @return string the configuration id
+ */
+ public static function get_mode_full_config_id(int $ocintanceid, bool $withpluginname = false) {
+ return self::generate_config_id(self::CONFIG_ID_MODE, $ocintanceid, $withpluginname);
+ }
+
+ /**
+ * Gets the full configuration id of maintenance notification level setting.
+ *
+ * @param int $ocintanceid the opencast instance id
+ * @param bool $withpluginname flag to determine whether to prepend plugin name.
+ *
+ * @return string the configuration id
+ */
+ public static function get_notificationlevel_full_config_id(int $ocintanceid, bool $withpluginname = false) {
+ return self::generate_config_id(self::CONFIG_ID_NOTIFLEVEL, $ocintanceid, $withpluginname);
+ }
+
+ /**
+ * Gets the full configuration id of maintenance message setting.
+ *
+ * @param int $ocintanceid the opencast instance id
+ * @param bool $withpluginname flag to determine whether to prepend plugin name.
+ *
+ * @return string the configuration id
+ */
+ public static function get_message_full_config_id(int $ocintanceid, bool $withpluginname = false) {
+ return self::generate_config_id(self::CONFIG_ID_MESSAGE, $ocintanceid, $withpluginname);
+ }
+
+ /**
+ * Gets the full configuration id of maintenance start date setting.
+ *
+ * @param int $ocintanceid the opencast instance id
+ * @param bool $withpluginname flag to determine whether to prepend plugin name.
+ *
+ * @return string the configuration id
+ */
+ public static function get_startdate_full_config_id(int $ocintanceid, bool $withpluginname = false) {
+ return self::generate_config_id(self::CONFIG_ID_STARTDATE, $ocintanceid, $withpluginname);
+ }
+
+ /**
+ * Gets the full configuration id of maintenance end date setting.
+ *
+ * @param int $ocintanceid the opencast instance id
+ * @param bool $withpluginname flag to determine whether to prepend plugin name.
+ *
+ * @return string the configuration id
+ */
+ public static function get_enddate_full_config_id(int $ocintanceid, bool $withpluginname = false) {
+ return self::generate_config_id(self::CONFIG_ID_ENDDATE, $ocintanceid, $withpluginname);
+ }
+
+ /**
+ * Generates the full configuration id by combining the id and the instance id and if requested appending plugin name.
+ *
+ * @param string $configid the configuration id
+ * @param int $ocintanceid the opencast instance id
+ * @param bool $withpluginname flag to determine whether to prepend plugin name.
+ *
+ * @return string the generated configuration id
+ */
+ private static function generate_config_id(string $configid, int $ocintanceid, bool $withpluginname) {
+ $id = $configid . '_'. $ocintanceid;
+ if ($withpluginname) {
+ $id = self::PLUGINNAME. '/'. $id;
+ }
+ return $id;
+ }
+
+ /**
+ * An auxiliary method to be used by the admin settings in order to validate the start and end dates.
+ *
+ * It gets the some requirements, prepares and returns the validation callable function.
+ *
+ * @param string $currentid the current setting id to compare with.
+ * @param string $compsettingid the setting id to compare against.
+ * @param string $compsettingstringname the string name of the setting to compare against.
+ * @param string $compopr the comparision operator. currently used ">=" or "<="
+ *
+ * @return callable the validation callback function
+ */
+ public static function maintenance_datetime_validation(string $currentid, string $compsettingid,
+ string $compsettingstringname, string $compopr= '>=') {
+ // Preparing various parameters to be used in the validation callback function.
+ $compsettinglabel = get_string($compsettingstringname, self::PLUGINNAME);
+ $errorstrings = [
+ '>=' => get_string('maintenancemode_datetime_ge_error', self::PLUGINNAME, $compsettinglabel),
+ '<=' => get_string('maintenancemode_datetime_le_error', self::PLUGINNAME, $compsettinglabel),
+ 'expired' => get_string('maintenancemode_datetime_expired_error', self::PLUGINNAME),
+ ];
+ $domain = [
+ 'startdate' => strpos($currentid, self::CONFIG_ID_STARTDATE) !== false,
+ 'enddate' => strpos($currentid, self::CONFIG_ID_ENDDATE) !== false,
+ ];
+ return function (array $data, array $datasubmitted) use ($compsettingid, $compopr, $errorstrings, $domain): string {
+ // Get the current timestamp.
+ $thistimestamp = (int) $data['timestamp'];
+
+ // Regulate the expiration:
+ // - If is it start date we allow the time in the past, by not checking any further!
+ // - If is it end date we allow only the time in the future.
+ if ($domain['enddate'] === true && $thistimestamp < time()) {
+ return $errorstrings['expired'];
+ }
+
+ // Extract the data time of the other side to compare against from submitted data.
+ $compsubmitteddata = array_filter($datasubmitted, function ($value, $key) use ($compsettingid) {
+ return strpos($key, $compsettingid) !== false;
+ }, ARRAY_FILTER_USE_BOTH);
+
+ // If found, we proceed.
+ if (!empty($compsubmitteddata)) {
+ $compfiltereddata = [];
+ // Extract only the datetime parameters.
+ foreach ($compsubmitteddata[array_key_first($compsubmitteddata)] as $key => $value) {
+ if ($key === 'enabled' || $key === 'oldvalue') {
+ continue;
+ }
+ switch ($key) {
+ case 'year':
+ $compfiltereddata['year'] = intval($value);
+ break;
+ case 'month':
+ $compfiltereddata['month'] = intval($value);
+ break;
+ case 'day':
+ $compfiltereddata['day'] = intval($value);
+ break;
+ case 'hour':
+ $compfiltereddata['hour'] = intval($value);
+ break;
+ case 'minute':
+ $compfiltereddata['minute'] = intval($value);
+ break;
+ default:
+ break;
+ }
+ }
+ // If the compiled filtered array is not empty, we do the comparison.
+ if (!empty($compfiltereddata)) {
+ $configtimestamp = make_timestamp(
+ $compfiltereddata['year'],
+ $compfiltereddata['month'],
+ $compfiltereddata['day'],
+ $compfiltereddata['hour'],
+ $compfiltereddata['minute']
+ );
+
+ // Compare the timestamps of both sides based on provided comparison operator.
+ if ($compopr === '>=' && $thistimestamp >= $configtimestamp) {
+ // Returning error message, if the condition is met.
+ return $errorstrings[$compopr];
+ } else if ($compopr === '<=' && $thistimestamp <= $configtimestamp) {
+ // Returning error message, if the condition is met.
+ return $errorstrings[$compopr];
+ }
+ }
+ }
+ return '';
+ };
+ }
+}
diff --git a/classes/local/settings_api.php b/classes/local/settings_api.php
index 4386950..80c0952 100644
--- a/classes/local/settings_api.php
+++ b/classes/local/settings_api.php
@@ -168,6 +168,86 @@ public static function get_lticonsumersecret(int $ocinstanceid) {
return get_config('tool_opencast', 'lticonsumersecret_' . $ocinstanceid);
}
+ /**
+ * Get the maintenance mode for a specific Opencast instance.
+ *
+ * This function retrieves the maintenance mode setting for the given Opencast instance.
+ *
+ * @param int $ocinstanceid The ID of the Opencast instance to check the maintenance mode for.
+ *
+ * @return mixed The maintenance mode setting for the specified Opencast instance.
+ * Returns false if the setting is not found.
+ *
+ * @throws \dml_exception If there's an error retrieving the configuration.
+ */
+ public static function get_maintenancemode(int $ocinstanceid) {
+ return get_config('tool_opencast', maintenance_class::get_mode_full_config_id($ocinstanceid));
+ }
+
+ /**
+ * Get the maintenance notification level for a specific Opencast instance.
+ *
+ * This function retrieves the maintenance notification level setting for the given Opencast instance.
+ *
+ * @param int $ocinstanceid The ID of the Opencast instance to check the maintenance notification level for.
+ *
+ * @return mixed The maintenance notification level setting for the specified Opencast instance.
+ * Returns false if the setting is not found.
+ *
+ * @throws \dml_exception If there's an error retrieving the configuration.
+ */
+ public static function get_maintenancenotiflevel(int $ocinstanceid) {
+ return get_config('tool_opencast', maintenance_class::get_notificationlevel_full_config_id($ocinstanceid));
+ }
+
+ /**
+ * Get the maintenance message for a specific Opencast instance.
+ *
+ * This function retrieves the maintenance message setting for the given Opencast instance.
+ *
+ * @param int $ocinstanceid The ID of the Opencast instance to retrieve the maintenance message for.
+ *
+ * @return mixed The maintenance message setting for the specified Opencast instance.
+ * Returns false if the setting is not found.
+ *
+ * @throws \dml_exception If there's an error retrieving the configuration.
+ */
+ public static function get_maintenancemessage(int $ocinstanceid) {
+ return get_config('tool_opencast', maintenance_class::get_message_full_config_id($ocinstanceid));
+ }
+
+ /**
+ * Get the maintenance start date json string for a specific Opencast instance.
+ *
+ * This function retrieves the maintenance start date json string setting for the given Opencast instance.
+ *
+ * @param int $ocinstanceid The ID of the Opencast instance to retrieve the maintenance start date json string for.
+ *
+ * @return mixed The maintenance start date json string setting for the specified Opencast instance.
+ * Returns false if the setting is not found.
+ *
+ * @throws \dml_exception If there's an error retrieving the configuration.
+ */
+ public static function get_maintenancestartdate(int $ocinstanceid) {
+ return get_config('tool_opencast', maintenance_class::get_startdate_full_config_id($ocinstanceid));
+ }
+
+ /**
+ * Get the maintenance end date json string for a specific Opencast instance.
+ *
+ * This function retrieves the maintenance end date json string setting for the given Opencast instance.
+ *
+ * @param int $ocinstanceid The ID of the Opencast instance to retrieve the maintenance end date json string for.
+ *
+ * @return mixed The maintenance end date json string setting for the specified Opencast instance.
+ * Returns false if the setting is not found.
+ *
+ * @throws \dml_exception If there's an error retrieving the configuration.
+ */
+ public static function get_maintenancenddate(int $ocinstanceid) {
+ return get_config('tool_opencast', maintenance_class::get_enddate_full_config_id($ocinstanceid));
+ }
+
/**
* Return the Opencast instance for the passed Opencast instance id, if any.
* If no Opencast instance with this id is configured, null is returned.
diff --git a/classes/proxy/decorated_opencastapi_rest_client.php b/classes/proxy/decorated_opencastapi_rest_client.php
new file mode 100644
index 0000000..dd4966c
--- /dev/null
+++ b/classes/proxy/decorated_opencastapi_rest_client.php
@@ -0,0 +1,71 @@
+.
+
+namespace tool_opencast\proxy;
+
+use OpencastApi\Rest\OcRestClient;
+use tool_opencast\local\maintenance_class;
+
+/**
+ * A decorated proxy class to wrap around the Opencast API Rest Client class.
+ *
+ * This proxy class is meant to have more local control over the overall system app interaction with Opencast API Library.
+ * Its main purpose is to apply a top layer controller such as maintenance checkers.
+ *
+ * @package tool_opencast
+ * @copyright 2024 Farbod Zamani Boroujeni, ELAN e.V.
+ * @author Farbod Zamani Boroujeni
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class decorated_opencastapi_rest_client {
+
+ /** @var OcRestClient The Opencast API Rest Client */
+ private OcRestClient $restclient;
+
+ /** @var maintenance_class|null The maintenance class */
+ private ?maintenance_class $maintenance;
+
+ /**
+ * Constructor
+ * @param array $config The Opencast API configuration
+ * @param maintenance_class|null $maintenance The maintenance class
+ */
+ public function __construct(array $config, ?maintenance_class $maintenance = null) {
+ $this->restclient = new OcRestClient($config);
+ $this->maintenance = $maintenance;
+ }
+
+ /**
+ * Magic method to handle method calls on the decorated proxy object.
+ *
+ * If the maintenance class is set and the current method is not allowed, it will restrict access.
+ * Otherwise, it will call the actual Opencast API Rest Client method.
+ * @param string $method The method name to be called
+ * @param array $args An array of arguments passed to the method
+ *
+ * @return mixed|void The result of the method call, or void if it is in maintenance mode.
+ */
+ public function __call(string $method, array $args) {
+ if (!empty($this->maintenance) && !$this->maintenance->can_access($method)) {
+ return $this->maintenance->decide_access_bounce();
+ }
+ $returedresult = call_user_func_array([$this->restclient, $method], $args);
+ if ($returedresult === $this->restclient) {
+ return $this;
+ }
+ return $returedresult;
+ }
+}
diff --git a/classes/proxy/decorated_opencastapi_service.php b/classes/proxy/decorated_opencastapi_service.php
new file mode 100644
index 0000000..ac74044
--- /dev/null
+++ b/classes/proxy/decorated_opencastapi_service.php
@@ -0,0 +1,77 @@
+.
+
+namespace tool_opencast\proxy;
+
+use OpencastApi\Rest\OcRest;
+use tool_opencast\local\maintenance_class;
+
+/**
+ * A decorated proxy class to wrap around the Opencast API services.
+ *
+ * This proxy class is meant to have more local control over the overall system app interaction with Opencast API Library.
+ * Its main purpose is to apply a top layer controller such as maintenance checkers.
+ *
+ * @package tool_opencast
+ * @copyright 2024 Farbod Zamani Boroujeni, ELAN e.V.
+ * @author Farbod Zamani Boroujeni
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class decorated_opencastapi_service {
+
+ /** @var OcRest|null The Opencast API service */
+ public ?OcRest $apiservice;
+
+ /** @var maintenance_class|null The maintenance class */
+ private ?maintenance_class $maintenance;
+
+ /**
+ * Constructor
+ * @param OcRest|null $apiservice The Opencast API service
+ * @param maintenance_class|null $maintenance The maintenance class
+ */
+ public function __construct(?OcRest $apiservice = null, ?maintenance_class $maintenance = null) {
+ $this->apiservice = $apiservice;
+ $this->maintenance = $maintenance;
+ }
+
+ /**
+ * Magic method to handle method calls on the decorated proxy object.
+ *
+ * If the maintenance class is set and the current method is not allowed, it will restrict access.
+ * Otherwise, it will call the actual Opencast API service method.
+ *
+ * @param string $method The name of the method
+ * @param array $args The arguments for the method
+ *
+ * @return mixed|void The decorated proxy object or the response object obtained from original service.
+ * or void if it is in maintenance mode.
+ */
+ public function __call(string $method, array $args) {
+ // Maintenance feature checker.
+ if (!empty($this->maintenance) && !$this->maintenance->can_access($method)) {
+ return $this->maintenance->decide_access_bounce();
+ }
+ $response = call_user_func_array([$this->apiservice, $method], $args);
+
+ // Handle recursive.
+ if ($response === $this->apiservice) {
+ return $this;
+ }
+
+ return $response;
+ }
+}
diff --git a/classes/settings/admin_setting_configdatetimeselector.php b/classes/settings/admin_setting_configdatetimeselector.php
new file mode 100644
index 0000000..f0401cc
--- /dev/null
+++ b/classes/settings/admin_setting_configdatetimeselector.php
@@ -0,0 +1,403 @@
+.
+
+namespace tool_opencast\settings;
+
+/**
+ * Admin setting class which is used to create a date time selector.
+ *
+ * @package tool_opencast
+ * @copyright 2024 Farbod Zamani Boroujeni, ELAN e.V.
+ * @author Farbod Zamani Boroujeni
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class admin_setting_configdatetimeselector extends \admin_setting {
+
+ /** @var bool Flag to determine whether it is optional */
+ private $optional;
+
+ /** @var callable|null Validation function */
+ protected $validatefunction = null;
+
+ /**
+ * Constructor
+ * @param string $name setting unique ascii name
+ * @param string $visiblename localised
+ * @param string $description long localised info
+ * @param int $defaultsetting the default timestamp
+ * @param bool $optional whether the setting need to be optionally selected by an enable checkbox. Defaults to false
+ */
+ public function __construct($name, $visiblename, $description, $defaultsetting = 0, $optional = false) {
+ parent::__construct($name, $visiblename, $description, $defaultsetting);
+ $this->optional = $optional;
+ }
+
+
+ /**
+ * Retrieves the current setting value for the date and time selector.
+ *
+ * This function reads the configuration, parses it, and returns an array
+ * containing the date and time components, along with additional settings.
+ *
+ * @return array|null An array containing the following keys:
+ * - year: The year (YYYY format)
+ * - month: The month (01-12 format)
+ * - day: The day of the month (1-31 format)
+ * - hour: The hour (0-23 format)
+ * - minute: The minute (0-59 format)
+ * - timestamp: The Unix timestamp of the date and time
+ * - optional: Whether the setting is optional
+ * - enabled: Whether the setting is enabled (for optional settings)
+ * Returns null if the configuration is empty.
+ */
+ public function get_setting() {
+ $config = $this->config_read($this->name);
+ if (empty($config)) {
+ return null;
+ }
+
+ $config = json_decode($config);
+
+ $configtimestamp = !empty($config->timestamp) ? (int) $config->timestamp : time();
+
+ $configdatetime = usergetdate($configtimestamp);
+
+ $settings = [
+ 'year' => $configdatetime['year'],
+ 'month' => $configdatetime['mon'],
+ 'day' => $configdatetime['mday'],
+ 'hour' => $configdatetime['hours'],
+ 'minute' => $configdatetime['minutes'],
+ 'timestamp' => $configdatetime[0],
+ 'optional' => $this->optional,
+ 'enabled' => (bool) $config->enabled,
+ ];
+
+ return $settings;
+ }
+
+
+ /**
+ * Writes the setting to the configuration.
+ *
+ * This function processes the input data, make timestamp out of the given date info,
+ * and saves the setting in a JSON-encoded format.
+ *
+ * @param array|mixed $data The input data to be processed and saved.
+ * Expected to be an array containing date and time components.
+ *
+ * @return string Returns an empty string on success, or an error message on failure.
+ * If $data is not an array, an empty string is returned.
+ */
+ public function write_setting($data) {
+
+ if (!is_array($data)) {
+ return '';
+ }
+
+ $oldvalue = json_decode($data['oldvalue'], true);
+
+ // In case the setting is optional and disabled, we only receive "oldvalue" parameter here.
+ if (count($data) === 1 && !empty($data['oldvalue'])) {
+ $data = $oldvalue;
+ unset($data['enabled']); // When enabled is unset, that means it is disabled, so we force it here.
+ }
+
+ // Make timestamp out of data with make_timestamp method to ensure its integrity.
+ $configtimestamp = make_timestamp($data['year'], $data['month'], $data['day'], $data['hour'], $data['minute']);
+
+ $additionalsettings = [
+ 'timestamp' => $configtimestamp,
+ 'optional' => $this->optional,
+ ];
+
+ // Make sure that enabled setting is correctly recorded.
+ if (isset($data['enabled'])) {
+ $data['enabled'] = true;
+ } else {
+ $data['enabled'] = false;
+ }
+
+ // Here, before merge, we make sure that "oldvalues" parameter is not going to be stored.
+ if (isset($data['oldvalue'])) {
+ unset($data['oldvalue']);
+ }
+
+ $settings = array_merge($data, $additionalsettings);
+
+ // Validate the new setting, if it is enabled.
+ if ($settings['enabled'] == true) {
+ $error = $this->validate_setting($settings);
+ if (!empty($error)) {
+ return $error;
+ }
+ }
+
+ $result = $this->config_write($this->name, json_encode($settings));
+
+ return ($result ? '' : get_string('errorsetting', 'admin'));
+ }
+
+ /**
+ * Validate the setting. This uses the callback function if provided; subclasses could override
+ * to carry out validation directly in the class.
+ *
+ * @param array $data New values being set
+ * @return string Empty string if valid, or error message text
+ */
+ protected function validate_setting(array $data): string {
+ // If validation function is specified, call it now.
+ if ($this->validatefunction) {
+ // For more accurate dependency validation, we pass the submitted form data to the validation function.
+ $datasubmitted = (array) data_submitted();
+ return call_user_func($this->validatefunction, $data, $datasubmitted);
+ } else {
+ return '';
+ }
+ }
+
+ /**
+ * Sets a validate function.
+ *
+ * The callback will be passed one parameter, the new setting value, and should return either
+ * an empty string '' if the value is OK, or an error message if not.
+ *
+ * @param callable|null $validatefunction Validate function or null to clear
+ */
+ public function set_validate_function(?callable $validatefunction = null) {
+ $this->validatefunction = $validatefunction;
+ }
+
+ /**
+ * Returns XHTML time select fields
+ *
+ * @param array $data the current setting
+ * @param string $query
+ * @return string XHTML time select fields and wrapping div(s)
+ */
+ public function output_html($data, $query = '') {
+ global $OUTPUT;
+
+ // We have to get the setting from the get_setting function, otherwise the pass $data variable is insufficient.
+ $setting = $this->get_setting();
+ $default = $this->get_defaultsetting();
+ if (is_array($default)) {
+ $defaultinfo = userdate(intval($default), get_string('strftimedatetime', 'langconfig'));
+ } else {
+ $defaultinfo = null;
+ }
+
+ // Support internationalised calendars.
+ $calendartype = \core_calendar\type_factory::get_calendar_instance();
+
+ // Set the now datetime as default.
+ $savedtime = time();
+ if (!empty($setting)) {
+ $savedtime = intval($setting['timestamp']);
+ }
+
+ $dt = new \DateTime('@' . $savedtime, \core_date::get_user_timezone_object());
+ $dttimestamp = $dt->getTimestamp();
+
+ $getdatefields = $calendartype->timestamp_to_date_array($dttimestamp);
+ $current = [
+ 'year' => $getdatefields['year'],
+ 'month' => $getdatefields['mon'],
+ 'day' => $getdatefields['mday'],
+ 'hour' => $getdatefields['hours'],
+ 'minute' => $getdatefields['minutes'],
+ ];
+
+ // To prevent bad data when it is a very fresh config setting.
+ if (empty($setting)) {
+ $setting = $current;
+ $setting['enabled'] = false;
+ $setting['optional'] = $this->optional;
+ }
+
+ // Time part is handled the same everywhere.
+ $hours = [];
+ for ($i = 0; $i <= 23; $i++) {
+ $hours[$i] = sprintf("%02d", $i);
+ }
+ $minutes = [];
+ for ($i = 0; $i < 60; $i += 5) {
+ $minutes[$i] = sprintf("%02d", $i);
+ }
+
+ // List date fields.
+ $fields = $calendartype->get_date_order($current['year'], $calendartype->get_max_year());
+
+ // Add time fields - in RTL mode these are switched.
+ $fields['split'] = '/';
+ if (right_to_left()) {
+ $fields['minute'] = $minutes;
+ $fields['colon'] = ':';
+ $fields['hour'] = $hours;
+ } else {
+ $fields['hour'] = $hours;
+ $fields['colon'] = ':';
+ $fields['minute'] = $minutes;
+ }
+
+ // Output all date fields.
+ $spanattrs = [
+ 'class' => 'fdate_time_selector opencast_config_dt_selector',
+ 'data-settingid' => $this->get_id(),
+ ];
+ if ($this->optional) {
+ $spanattrs['data-isoptional'] = true;
+ }
+ $html = \html_writer::start_tag('span', $spanattrs);
+
+ // We record old value in a hidden input element, to avoid getting ignored when the config is optional but disabled.
+ $html .= \html_writer::empty_tag('input',
+ ['type' => 'hidden', 'name' => $this->get_element_name('oldvalue'), 'value' => json_encode($setting)]);
+
+ // Now, we try to add (enabled/disabled) checkbox if the setting is optional.
+ $html .= $this->add_optional_checkbox((bool) $setting['enabled']);
+
+ // We then continue with rendering the date time select fields as well as calendar button.
+ foreach ($fields as $field => $options) {
+ if ($options === '/') {
+ $html = rtrim($html);
+
+ // In Gregorian calendar mode only, we support a date selector popup, reusing
+ // code from form to ensure consistency.
+ if ($calendartype->get_name() === 'gregorian') {
+ $image = $OUTPUT->pix_icon('i/calendar', get_string('calendar', 'calendar'), 'moodle');
+ $html .= ' ' . \html_writer::link('#', $image,
+ [
+ 'name' => $this->get_element_name('calendar'),
+ 'id' => $this->get_element_id('calendar'),
+ ]
+ );
+ }
+ continue;
+ }
+ if ($options === ':') {
+ $html .= ': ';
+ continue;
+ }
+ $html .= \html_writer::start_tag('label', ['for' => $this->get_element_id($field)]);
+ $html .= \html_writer::span(get_string($field) . ' ', 'accesshide');
+ $html .= \html_writer::start_tag('select',
+ [
+ 'class' => 'custom-select opencast-config-dt-select',
+ 'name' => $this->get_element_name($field),
+ 'id' => $this->get_element_id($field),
+ ]
+ );
+ foreach ($options as $key => $value) {
+ $params = ['value' => $key];
+ if ($current[$field] == $key) {
+ $params['selected'] = 'selected';
+ }
+ $html .= \html_writer::tag('option', s($value), $params);
+ }
+ $html .= \html_writer::end_tag('select');
+ $html .= \html_writer::end_tag('label');
+ $html .= ' ';
+ }
+ $html = rtrim($html) . \html_writer::end_tag('span');
+
+ return format_admin_setting($this, $this->visiblename, $html, $this->description,
+ $this->get_id(), '', $defaultinfo, $query);
+ }
+
+ /**
+ * Adds an optional checkbox to enable/disable the date time selector.
+ *
+ * This function generates HTML for a checkbox that allows users to enable or disable
+ * the date time selector when it's set as optional. If the setting is not optional,
+ * an empty string is returned.
+ *
+ * @param bool $configvalue The current enabled/disabled state of the checkbox
+ * @return string HTML markup for the optional checkbox, or an empty string if not optional
+ */
+ private function add_optional_checkbox(bool $configvalue) {
+ // If it is not optional, we don't show the checkbox, and consider it as always enabled.
+ if (!$this->optional) {
+ return '';
+ }
+ $html = \html_writer::start_tag('label',
+ ['class' => 'form-check d-inline-block pr-2']
+ );
+
+ $checkboxattrs = [
+ 'id' => $this->get_enabled_element_id(),
+ 'class' => 'form-check-input',
+ ];
+ $checkboxlabelattrs = ['class' => 'mr-2'];
+ $checkboxhtml = \html_writer::checkbox( $this->get_enabled_element_name(),
+ '', $configvalue, '', $checkboxattrs, $checkboxlabelattrs);
+ $checkboxhtml .= ' ' . get_string('enable');
+ $html .= $checkboxhtml;
+ $html .= \html_writer::end_tag('label');
+
+ return $html;
+ }
+
+ /**
+ * Get the name attribute for the enabled checkbox element.
+ *
+ * This method generates the name attribute for the checkbox that enables or disables
+ * the date time selector when it's set as optional.
+ *
+ * @return string The name attribute for the enabled checkbox element
+ */
+ private function get_enabled_element_name() {
+ return $this->get_element_name('enabled');
+ }
+
+ /**
+ * Get the id attribute for the enabled checkbox element.
+ *
+ * This method generates the id attribute for the checkbox that enables or disables
+ * the date time selector when it's set as optional.
+ *
+ * @return string The name attribute for the enabled checkbox element
+ */
+ private function get_enabled_element_id() {
+ return $this->get_element_id('enabled');
+ }
+
+ /**
+ * Generates a unique element ID by appending a suffix to the base ID.
+ *
+ * This method creates a unique identifier for HTML elements by combining
+ * the base ID of the setting with a provided suffix.
+ *
+ * @param string $suffix The suffix to append to the base ID.
+ * @return string The generated element ID.
+ */
+ private function get_element_id(string $suffix) {
+ return $this->get_id() . '_' . $suffix;
+ }
+
+ /**
+ * Generates a name for the element by appending a suffix to the full name of the setting.
+ *
+ * This method creates a name for HTML elements by combining
+ * the full name of the setting with a provided suffix.
+ *
+ * @param string $suffix The suffix to append to the full setting name.
+ * @return string The generated element name.
+ */
+ private function get_element_name(string $suffix) {
+ return $this->get_full_name() . '[' . $suffix . ']';
+ }
+}
diff --git a/classes/settings/admin_settings_builder.php b/classes/settings/admin_settings_builder.php
index 1ec838f..2c9b4ec 100644
--- a/classes/settings/admin_settings_builder.php
+++ b/classes/settings/admin_settings_builder.php
@@ -17,6 +17,7 @@
namespace tool_opencast\settings;
use tool_opencast\local\settings_api;
+use tool_opencast\local\maintenance_class;
/**
* Static admin setting builder class, which is used, to create and to add admin settings for tool_opencast.
@@ -111,6 +112,7 @@ private static function create_settings_fulltree($instances): void {
self::add_notification_banner_for_demo_instance($settings, $instanceid);
self::add_config_settings_fulltree($settings, $instanceid);
+ self::add_maintenance_mode_block($settings, $instanceid);
self::add_connection_test_tool($settings, $instanceid);
self::include_admin_settingpage($settings);
@@ -241,11 +243,15 @@ private static function require_amds(string $pluginnameid): void {
return;
}
+ // Important for maintenance start and end date calendar js.
+ form_init_date_js();
$PAGE->requires->jquery();
$PAGE->requires->js_call_amd('tool_opencast/tool_testtool', 'init');
$PAGE->requires->js_call_amd('tool_opencast/tool_settings', 'init', [$pluginnameid]);
+ $PAGE->requires->js_call_amd('tool_opencast/maintenance', 'init');
$PAGE->requires->css('/admin/tool/opencast/css/tabulator.min.css');
$PAGE->requires->css('/admin/tool/opencast/css/tabulator_bootstrap4.min.css');
+ $PAGE->requires->css('/admin/tool/opencast/css/styles.css');
}
/**
@@ -413,6 +419,180 @@ private static function add_admin_setting_configpasswordunmask(\admin_settingpag
$settings->add($settingconfigpasswordunmask);
}
+ /**
+ * Adds an admin setting configselect to the passed admin settingpage.
+ *
+ * @param \admin_settingpage $settings
+ * The admin settingpage, the configselect is added to.
+ *
+ * @param string $name
+ * The internal name for the configselect.
+ *
+ * @param string $visiblenameidentifier
+ * The identifier for the string, that is used for the visible name of the configselect.
+ *
+ * @param string $descriptionidentifier
+ * The identifier for the string, that is used for the visible description of the configselect.
+ *
+ * @param string $defaultsetting
+ * The default setting for the configselect.
+ *
+ * @param array $choices
+ * The choices options of the configselect.
+ *
+ * @return void
+ */
+ private static function add_admin_setting_configselect(\admin_settingpage $settings,
+ string $name,
+ string $visiblenameidentifier,
+ string $descriptionidentifier,
+ string $defaultsetting,
+ array $choices): void {
+ $settingconfigselect = new \admin_setting_configselect(
+ $name,
+ get_string($visiblenameidentifier, self::PLUGINNAME),
+ get_string($descriptionidentifier, self::PLUGINNAME),
+ $defaultsetting,
+ $choices
+ );
+ $settings->add($settingconfigselect);
+ }
+
+ /**
+ * Adds an admin setting configtextarea to the passed admin settingpage.
+ *
+ * @param \admin_settingpage $settings
+ * The admin settingpage, the configtextarea is added to.
+ *
+ * @param string $name
+ * The internal name for the configtextarea.
+ *
+ * @param string $visiblenameidentifier
+ * The identifier for the string, that is used for the visible name of the configtextarea.
+ *
+ * @param string $descriptionidentifier
+ * The identifier for the string, that is used for the visible description of the configtextarea.
+ *
+ * @param string $defaultsetting
+ * The default setting for the configtextarea.
+ *
+ * @param mixed $paramtype
+ * The parameter type of the configtext.
+ *
+ * @param string $cols
+ * The number of columns to make the editor.
+ *
+ * @param string $rows
+ * The number of rows to make the editor.
+ *
+ * @return void
+ */
+ private static function add_admin_setting_configtextarea(\admin_settingpage $settings,
+ string $name,
+ string $visiblenameidentifier,
+ string $descriptionidentifier,
+ string $defaultsetting,
+ $paramtype = PARAM_RAW,
+ string $cols='60',
+ string $rows='8'): void {
+ $settingconfigtextarea = new admin_setting_configtextarea(
+ $name,
+ get_string($visiblenameidentifier, self::PLUGINNAME),
+ get_string($descriptionidentifier, self::PLUGINNAME),
+ $defaultsetting, $paramtype, $cols, $rows
+ );
+ $settings->add($settingconfigtextarea);
+ }
+
+
+ /**
+ * Adds an admin setting confightmleditor to the passed admin settingpage.
+ *
+ * @param \admin_settingpage $settings
+ * The admin settingpage, the confightmleditor is added to.
+ *
+ * @param string $name
+ * The internal name for the confightmleditor.
+ *
+ * @param string $visiblenameidentifier
+ * The identifier for the string, that is used for the visible name of the confightmleditor.
+ *
+ * @param string $descriptionidentifier
+ * The identifier for the string, that is used for the visible description of the confightmleditor.
+ *
+ * @param string $defaultsetting
+ * The default setting for the confightmleditor.
+ *
+ * @param mixed $paramtype
+ * The parameter type of the configtext.
+ *
+ * @param string $cols
+ * The number of columns to make the editor.
+ *
+ * @param string $rows
+ * The number of rows to make the editor.
+ *
+ * @return void
+ */
+ private static function add_admin_setting_confightmleditor(\admin_settingpage $settings,
+ string $name,
+ string $visiblenameidentifier,
+ string $descriptionidentifier,
+ string $defaultsetting,
+ $paramtype = PARAM_RAW,
+ string $cols='60',
+ string $rows='8'): void {
+ $settingconfightmleditor = new \admin_setting_confightmleditor(
+ $name,
+ get_string($visiblenameidentifier, self::PLUGINNAME),
+ get_string($descriptionidentifier, self::PLUGINNAME),
+ $defaultsetting, $paramtype, $cols, $rows
+ );
+ $settings->add($settingconfightmleditor);
+ }
+
+ /**
+ * Adds an admin setting configdatetimeselector to the passed admin settingpage.
+ *
+ * @param \admin_settingpage $settings
+ * The admin settingpage, the configdatetimeselector is added to.
+ *
+ * @param string $name
+ * The internal name for the configdatetimeselector.
+ *
+ * @param string $visiblenameidentifier
+ * The identifier for the string, that is used for the visible name of the configdatetimeselector.
+ *
+ * @param string $descriptionidentifier
+ * The identifier for the string, that is used for the visible description of the configdatetimeselector.
+ *
+ * @param int $defaultsetting
+ * The default setting timestamp for the configdatetimeselector.
+ *
+ * @param bool $optional
+ * Flag indicating whether this config should be optional with enable checkbox to disable/enable.
+ *
+ * @param callable|null $validatefunction Validate function or null to clear
+ *
+ * @return void
+ */
+ private static function add_admin_setting_configdatetimeselector(\admin_settingpage $settings,
+ string $name,
+ string $visiblenameidentifier,
+ string $descriptionidentifier,
+ int $defaultsetting = 0,
+ bool $optional = false,
+ ?callable $validatefunction = null): void {
+ $settingconfigdatetimeselector = new admin_setting_configdatetimeselector(
+ $name,
+ get_string($visiblenameidentifier, self::PLUGINNAME),
+ get_string($descriptionidentifier, self::PLUGINNAME),
+ $defaultsetting, $optional
+ );
+ $settingconfigdatetimeselector->set_validate_function($validatefunction);
+ $settings->add($settingconfigdatetimeselector);
+ }
+
/**
* Adds the connection test tool to the passed admin settingpage for the passed Opencast instance id,
* where a button with its description is added to the passed admin settingpage,
@@ -452,4 +632,104 @@ private static function add_connection_test_tool(\admin_settingpage $settings,
get_string('testtoolheaderdesc', self::PLUGINNAME, $connectiontoolbutton))
);
}
+
+ /**
+ * Adds the maintenance mode block to the passed admin setting page for the given Opencast instance ID.
+ *
+ * This block includes a button to sync the maintenance mode settings with the corresponding Opencast instance,
+ * a dropdown to select the maintenance mode, a dropdown to select the notification level, a textarea/htmleditor to enter the
+ * maintenance message, and two datetime selectors to set the start and end dates of the maintenance period.
+ *
+ * @param \admin_settingpage $settings The admin setting page to add the maintenance mode block to.
+ * @param int $instanceid The ID of the Opencast instance to add the maintenance mode block for.
+ * @return void
+ */
+ private static function add_maintenance_mode_block(\admin_settingpage $settings,
+ int $instanceid): void {
+
+ // Prepare the Opencast maintenance sync button.
+ $attributes = [
+ 'class' => 'btn btn-warning disabled maintenance-sync-btn mb-3 mt-2',
+ 'disabled' => 'disabled',
+ 'title' => get_string('maintenancemode_btn_disabled', self::PLUGINNAME),
+ 'data-ocinstanceid' => strval($instanceid),
+ ];
+ // Get the API fetch (sync) button HTML.
+ $apifetchbutton = \html_writer::tag(
+ 'button',
+ get_string('maintenancemode_btn', self::PLUGINNAME),
+ $attributes
+ );
+ // Place the button inside the header description.
+ $settings->add(new \admin_setting_heading(
+ 'tool_opencast/maintenancemodesection',
+ get_string('maintenanceheader', self::PLUGINNAME),
+ get_string('maintenanceheader_desc', self::PLUGINNAME, $apifetchbutton))
+ );
+
+ // Render the maintenance mode option.
+ // Record ID outside in order to apply hide_if dependency option.
+ $maintenancemodeid = maintenance_class::get_mode_full_config_id($instanceid, true);
+ self::add_admin_setting_configselect($settings,
+ $maintenancemodeid,
+ 'maintenancemode', 'maintenancemode_desc',
+ maintenance_class::MODE_DISABLE,
+ maintenance_class::get_admin_settings_mode_choices()
+ );
+
+ // Render the maintenance notify level option.
+ $maintenancemodenotiflevelid = maintenance_class::get_notificationlevel_full_config_id($instanceid, true);
+ self::add_admin_setting_configselect($settings,
+ $maintenancemodenotiflevelid,
+ 'maintenancemode_notiflevel', 'maintenancemode_notiflevel_desc',
+ \core\output\notification::NOTIFY_WARNING,
+ maintenance_class::get_admin_settings_notiflevel_choices()
+ );
+ // Apply hide_if dependency option.
+ $settings->hide_if($maintenancemodenotiflevelid, $maintenancemodeid, 'eq', maintenance_class::MODE_DISABLE);
+
+ // Render the maintenance message option.
+ $maintenancemessageid = maintenance_class::get_message_full_config_id($instanceid, true);
+ self::add_admin_setting_confightmleditor($settings,
+ $maintenancemessageid,
+ 'maintenancemode_message', 'maintenancemode_message_desc',
+ '',
+ );
+ // Apply hide_if dependency option.
+ $settings->hide_if($maintenancemessageid, $maintenancemodeid, 'eq', maintenance_class::MODE_DISABLE);
+
+ // Render the maintenance start date options.
+ $maintenancestartdateid = maintenance_class::get_startdate_full_config_id($instanceid, true);
+ self::add_admin_setting_configdatetimeselector($settings,
+ $maintenancestartdateid,
+ 'maintenancemode_start', 'maintenancemode_start_desc',
+ 0,
+ true,
+ maintenance_class::maintenance_datetime_validation(
+ $maintenancestartdateid,
+ maintenance_class::get_enddate_full_config_id($instanceid),
+ 'maintenancemode_end',
+ '>='
+ )
+ );
+ // Apply hide_if dependency option.
+ $settings->hide_if($maintenancestartdateid, $maintenancemodeid, 'eq', maintenance_class::MODE_DISABLE);
+
+ // Render the maintenance end date options.
+ $maintenanceenddateid = maintenance_class::get_enddate_full_config_id($instanceid, true);
+ self::add_admin_setting_configdatetimeselector($settings,
+ $maintenanceenddateid,
+ 'maintenancemode_end', 'maintenancemode_end_desc',
+ 0,
+ true,
+ maintenance_class::maintenance_datetime_validation(
+ $maintenanceenddateid,
+ maintenance_class::get_startdate_full_config_id($instanceid),
+ 'maintenancemode_start',
+ '<='
+ )
+ );
+ // Apply hide_if dependency option.
+ $settings->hide_if($maintenanceenddateid, $maintenancemodeid, 'eq', maintenance_class::MODE_DISABLE);
+ }
}
diff --git a/css/styles.css b/css/styles.css
new file mode 100644
index 0000000..89083aa
--- /dev/null
+++ b/css/styles.css
@@ -0,0 +1,4 @@
+div#dateselector-calendar-panel {
+ /* Set higher than the z-index of the htmleditor */
+ z-index: 10 !important; /* stylelint-disable-line */
+}
diff --git a/db/services.php b/db/services.php
index b18c864..a40cf78 100644
--- a/db/services.php
+++ b/db/services.php
@@ -59,6 +59,16 @@
'ajax' => true,
'loginrequired' => true,
],
+ 'tool_opencast_maintenance_sync' => [
+ 'classname' => 'tool_opencast_external',
+ 'methodname' => 'maintenance_sync',
+ 'classpath' => 'admin/tool/opencast/external.php',
+ 'description' => 'Service to Sync Maintenance Mode with Opencast',
+ 'type' => 'read',
+ 'capabilities' => 'tool/opencast:externalapi',
+ 'ajax' => true,
+ 'loginrequired' => true,
+ ],
];
$services = [
diff --git a/external.php b/external.php
index ddb699a..dc2927e 100644
--- a/external.php
+++ b/external.php
@@ -225,6 +225,19 @@ public static function connection_test_tool_returns() {
);
}
+ /**
+ * Describes the maintenance_sync return value.
+ *
+ * @return external_single_structure array the result of the connection test
+ */
+ public static function maintenance_sync_returns() {
+ return new external_single_structure(
+ [
+ 'status' => new external_value(PARAM_BOOL, 'Maintenance Synchronization result status'),
+ ]
+ );
+ }
+
/**
* Describes the parameters for testing the connection.
*
@@ -243,6 +256,20 @@ public static function connection_test_tool_parameters() {
);
}
+ /**
+ * Describes the parameters for syncing the maintenance.
+ *
+ * @return external_function_parameters
+ * @throws coding_exception
+ */
+ public static function maintenance_sync_parameters() {
+ return new external_function_parameters(
+ [
+ 'ocinstanceid' => new external_value(PARAM_INT, 'Opencast instance id'),
+ ]
+ );
+ }
+
/**
* Builds a html tag for the alert of the connection test tool.
*
@@ -324,4 +351,33 @@ public static function connection_test_tool($apiurl, $apiusername, $apipassword,
'testresult' => $resulthtml,
];
}
+
+ /**
+ * Perform fetching and syncing maintenance mode data from Opencast.
+ *
+ * @param int $ocinstanceid Opencast instance id.
+ * @return array
+ * @throws coding_exception
+ * @throws dml_exception
+ * @throws invalid_parameter_exception
+ * @throws required_capability_exception
+ */
+ public static function maintenance_sync($ocinstanceid) {
+
+ // Validate the parameters.
+ $params = self::validate_parameters(self::maintenance_sync_parameters(),
+ [
+ 'ocinstanceid' => $ocinstanceid,
+ ]
+ );
+
+ // Get a customized api instance to use.
+ $api = \tool_opencast\local\api::get_instance($params['ocinstanceid']);
+
+ $result = $api->sync_maintenance_with_opencast();
+
+ return [
+ 'status' => $result,
+ ];
+ }
}
diff --git a/lang/en/tool_opencast.php b/lang/en/tool_opencast.php
index 30d25ea..c800dd4 100644
--- a/lang/en/tool_opencast.php
+++ b/lang/en/tool_opencast.php
@@ -26,8 +26,8 @@
defined('MOODLE_INTERNAL') || die();
$string['addinstance'] = 'Add instance';
-$string['apicreadentialstestfailedshort'] = 'Opencast API User Credentials test failed with http code: {$a}';
$string['apicreadentialstestfailedlong'] = 'The given Username or Password for the Opencast API is not valid.
Please use valid Username and Password in order to avoid fatal error during tasks which use this setting.';
+$string['apicreadentialstestfailedshort'] = 'Opencast API User Credentials test failed with http code: {$a}';
$string['apicreadentialstestsuccessfulshort'] = 'Opencast API User Credentials test successful.';
$string['apipassword'] = 'Password of Opencast API user';
$string['apipassworddesc'] = 'Configure the password of the Opencast user who is used to do the Opencast API calls.';
@@ -59,6 +59,39 @@
$string['lticonsumerkey_desc'] = 'LTI Consumer key for the integration of Opencast services that require authentication such as Studio or the editor.';
$string['lticonsumersecret'] = 'Consumer secret';
$string['lticonsumersecret_desc'] = 'LTI Consumer secret for the integration of Opencast services that require authentication.';
+$string['maintenance_default_notification_message'] = 'Opencast Maintenance Notice
Please note that Opencast is currently undergoing maintenance. As a result, some or all features related to Opencast may be temporarily unavailable. Thank you for your understanding.';
+$string['maintenance_exception_message'] = 'Opencast is currently undergoing maintenance. Interactions are temporarily disabled.';
+$string['maintenanceheader'] = 'Maintenance';
+$string['maintenanceheader_desc'] = 'In this section the maintenance mode can be configured with the following settings.
Depending on Opencast feature and settings availability, is it also possible to {$a}';
+$string['maintenancemode'] = 'Maintenance mode';
+$string['maintenancemode_btn'] = 'Sync Opencast Maintenance Mode';
+$string['maintenancemode_btn_disabled'] = 'Required js modules are not loaded.';
+$string['maintenancemode_datetime_expired_error'] = 'This field should not be in the past!';
+$string['maintenancemode_datetime_ge_error'] = 'This field should be before "{$a}"';
+$string['maintenancemode_datetime_le_error'] = 'This field should be after "{$a}"';
+$string['maintenancemode_desc'] = 'Setting maintenance mode to avoid conflict during Opencast downtime.
If Read-Only mode is selected, only reading resources from Opencast will be allowed.';
+$string['maintenancemode_disable'] = 'Disable';
+$string['maintenancemode_enable'] = 'Enable';
+$string['maintenancemode_end'] = 'Maintenance ends at';
+$string['maintenancemode_end_desc'] = 'The end date and time of maintenance';
+$string['maintenancemode_message'] = 'Maintenance Message';
+$string['maintenancemode_message_desc'] = 'An error message to display during maintenance.';
+$string['maintenancemode_modal_sync_confirmation_btn'] = 'Sync';
+$string['maintenancemode_modal_sync_confirmation_text'] = 'Are you sure to sync the maintenance mode with Opencast? This wil overwrite the current configuration.';
+$string['maintenancemode_modal_sync_confirmation_title'] = 'Sync Opencast Maintenance Mode';
+$string['maintenancemode_modal_sync_error_noinstance_message'] = 'Unable to find the Opencast instance id!';
+$string['maintenancemode_modal_sync_error_title'] = 'Syncing Error';
+$string['maintenancemode_modal_sync_failed'] = 'Maintenance Synchronization Unsuccessful!';
+$string['maintenancemode_modal_sync_succeeded'] = 'Maintenance successfully synchronized. The page will refresh in 3 seconds to apply the updated changes.';
+$string['maintenancemode_notiflevel'] = 'Notification Level';
+$string['maintenancemode_notiflevel_desc'] = 'By this setting you can set the level of notification message which helps rendering it in different styles and color based on the level e.g. Error Level will print a notification in a red box.';
+$string['maintenancemode_notiflevel_error'] = 'Error';
+$string['maintenancemode_notiflevel_info'] = 'Information';
+$string['maintenancemode_notiflevel_success'] = 'Success';
+$string['maintenancemode_notiflevel_warning'] = 'Warning';
+$string['maintenancemode_readonly'] = 'Read Only';
+$string['maintenancemode_start'] = 'Maintenance starts at';
+$string['maintenancemode_start_desc'] = 'The start date and time of maintenance';
$string['name'] = 'Name';
$string['needphp55orhigher'] = 'PHP Version 5.5 or higher is needed';
$string['nomockhandler'] = 'The Opencast Api Object is unable to handle the responses for testing purposes.';
diff --git a/tests/behat/behat_tool_opencast.php b/tests/behat/behat_tool_opencast.php
new file mode 100644
index 0000000..6843e4f
--- /dev/null
+++ b/tests/behat/behat_tool_opencast.php
@@ -0,0 +1,83 @@
+.
+
+/**
+ * Behat steps definitions for tool opencast.
+ *
+ * @package tool_opencast
+ * @category test
+ * @copyright 2024 Farbod Zamani Boroujeni, ELAN e.V.
+ * @author Farbod Zamani Boroujeni
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
+
+use tool_opencast\seriesmapping;
+
+require_once(__DIR__ . '/../../../../../lib/behat/behat_base.php');
+
+/**
+ * Steps definitions related with the opencast tool API.
+ *
+ * @package tool_opencast
+ * @category test
+ * @copyright 2024 Farbod Zamani Boroujeni, ELAN e.V.
+ * @author Farbod Zamani Boroujeni
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class behat_tool_opencast extends behat_base {
+
+ /**
+ * Setup of block block by creating series mapping.
+ *
+ * @Given /^I setup block plugin$/
+ */
+ public function i_setup_block_plugin() {
+ $courses = \core_course_category::search_courses(['search' => 'Course 1']);
+
+ $mapping = new seriesmapping();
+ $mapping->set('courseid', reset($courses)->id);
+ $mapping->set('series', '1234-1234-1234-1234-1234');
+ $mapping->set('isdefault', '1');
+ $mapping->set('ocinstanceid', 1);
+ $mapping->create();
+ }
+
+ /**
+ * adds a breakpoints in tool
+ * stops the execution until you hit enter in the console
+ *
+ * @Then /^breakpoint in tool/
+ */
+ public function breakpoint_in_tool() {
+ fwrite(STDOUT, "\033[s \033[93m[Breakpoint] Press \033[1;93m[RETURN]\033[0;93m to continue...\033[0m");
+ while (fgets(STDIN, 1024) == '') {
+ continue;
+ }
+ fwrite(STDOUT, "\033[u");
+ return;
+ }
+
+ /**
+ * Adds a step to make sure the block drawer keeps opened.
+ *
+ * @Given /^I make sure the block drawer keeps opened/
+ */
+ public function i_make_sure_the_block_drawer_keeps_opened() {
+ set_user_preference('behat_keep_drawer_closed', 0);
+ }
+}
diff --git a/tests/behat/tool_opencast_maintenace.feature b/tests/behat/tool_opencast_maintenace.feature
new file mode 100644
index 0000000..9e1cf3f
--- /dev/null
+++ b/tests/behat/tool_opencast_maintenace.feature
@@ -0,0 +1,111 @@
+@tool @tool_opencast
+Feature: Configure and check maintenance
+ In order to configure and check the maintenance period
+ As an admin
+ I need to be able to set and configure the maintenance for each instance
+ And check if the maintenance is properly set and displayed.
+
+ Background:
+ Given the following "users" exist:
+ | username | firstname | lastname | email | idnumber |
+ | teacher1 | Teacher | 1 | teacher1@example.com | T1 |
+ And the following "courses" exist:
+ | fullname | shortname | category |
+ | Course 1 | C1 | 0 |
+ And the following "course enrolments" exist:
+ | user | course | role |
+ | teacher1 | C1 | editingteacher |
+ And the following config values are set as admin:
+ | config | value | plugin |
+ | apiurl_1 | https://stable.opencast.org | tool_opencast |
+ | apipassword_1 | opencast | tool_opencast |
+ | apiusername_1 | admin | tool_opencast |
+ | ocinstances | [{"id":1,"name":"Default","isvisible":true,"isdefault":true}] | tool_opencast |
+ | limituploadjobs_1 | 0 | block_opencast |
+ | group_creation_1 | 0 | block_opencast |
+ | group_name_1 | Moodle_course_[COURSEID] | block_opencast |
+ | series_name_1 | Course_Series_[COURSEID] | block_opencast |
+ | enablechunkupload_1 | 0 | block_opencast |
+ | uploadworkflow_1 | schedule-and-upload | block_opencast |
+ | enableuploadwfconfigpanel_1 | 1 | block_opencast |
+ | alloweduploadwfconfigs_1 | straightToPublishing | block_opencast |
+
+ @javascript
+ Scenario: As an admin I should be able to configure the maintenance for an instance
+ Given I log in as "admin"
+ When I navigate to "Plugins > Admin tools > Opencast API > Configuration" in site administration
+ Then "Enable" "option" should exist in the "#id_s_tool_opencast_maintenancemode_1" "css_element"
+ And "Read Only" "option" should exist in the "#id_s_tool_opencast_maintenancemode_1" "css_element"
+ And "Disable" "option" should exist in the "#id_s_tool_opencast_maintenancemode_1" "css_element"
+ And I set the field "Maintenance mode" to "Enable"
+ And I should see "Notification Level"
+ And "Warning" "option" should exist in the "#id_s_tool_opencast_maintenancemode_notification_level_1" "css_element"
+ And "Error" "option" should exist in the "#id_s_tool_opencast_maintenancemode_notification_level_1" "css_element"
+ And "Information" "option" should exist in the "#id_s_tool_opencast_maintenancemode_notification_level_1" "css_element"
+ And "Success" "option" should exist in the "#id_s_tool_opencast_maintenancemode_notification_level_1" "css_element"
+ And I set the field "Notification Level" to "Error"
+ And I set the field "Maintenance Message" to "Opencast Maintenance Notification"
+ And I click on "#id_s_tool_opencast_maintenancemode_startdate_1_enabled" "css_element"
+ And I select "00" from the "s_tool_opencast_maintenancemode_startdate_1[hour]" singleselect
+ And I select "00" from the "s_tool_opencast_maintenancemode_startdate_1[minute]" singleselect
+ And I click on "#id_s_tool_opencast_maintenancemode_enddate_1_enabled" "css_element"
+ And I select "23" from the "s_tool_opencast_maintenancemode_enddate_1[hour]" singleselect
+ And I select "55" from the "s_tool_opencast_maintenancemode_enddate_1[minute]" singleselect
+ When I press "Save changes"
+ Then I should see "Changes saved"
+
+ @javascript
+ Scenario: As an admin I should not be able to configure the maintenance in the past
+ Given I log in as "admin"
+ When I navigate to "Plugins > Admin tools > Opencast API > Configuration" in site administration
+ Then I set the field "Maintenance mode" to "Enable"
+ And I click on "#id_s_tool_opencast_maintenancemode_enddate_1_enabled" "css_element"
+ And I select "00" from the "s_tool_opencast_maintenancemode_enddate_1[hour]" singleselect
+ And I select "00" from the "s_tool_opencast_maintenancemode_enddate_1[minute]" singleselect
+ When I press "Save changes"
+ Then I should not see "Changes saved"
+ And I should see "This field should not be in the past!" in the "#admin-maintenancemode_enddate_1" "css_element"
+
+ @javascript
+ Scenario: As an admin I should not be able to configure the false maintenance start date and end date
+ Given I log in as "admin"
+ When I navigate to "Plugins > Admin tools > Opencast API > Configuration" in site administration
+ Then I set the field "Maintenance mode" to "Enable"
+ And I click on "#id_s_tool_opencast_maintenancemode_enddate_1_enabled" "css_element"
+ And I select "23" from the "s_tool_opencast_maintenancemode_enddate_1[hour]" singleselect
+ And I select "55" from the "s_tool_opencast_maintenancemode_enddate_1[minute]" singleselect
+ And I click on "#id_s_tool_opencast_maintenancemode_startdate_1_enabled" "css_element"
+ And I select "23" from the "s_tool_opencast_maintenancemode_startdate_1[hour]" singleselect
+ And I select "55" from the "s_tool_opencast_maintenancemode_startdate_1[minute]" singleselect
+ When I press "Save changes"
+ Then I should not see "Changes saved"
+ And I should see "This field should be before \"Maintenance ends at\"" in the "#admin-maintenancemode_startdate_1" "css_element"
+ And I should see "This field should be after \"Maintenance starts at\"" in the "#admin-maintenancemode_enddate_1" "css_element"
+
+ @javascript
+ Scenario: Teachers should not be able to access the Opencast plugin during maintenance period
+ Given I log in as "teacher1"
+ And I setup block plugin
+ And I make sure the block drawer keeps opened
+ And I am on "Course 1" course homepage with editing mode on
+ And I add the "Opencast Videos" block
+ And I wait "2" seconds
+ And I reload the page
+ And I should see "Opencast Videos"
+ And the following config values are set as admin:
+ | maintenancemode_1 | 2 | tool_opencast |
+ | maintenancemode_notification_level_1 | error | tool_opencast |
+ | maintenancemode_message_1 | Opencast Maintenance Notification | tool_opencast |
+ | maintenancemode_startdate_1 | {"enabled":false} | tool_opencast |
+ | maintenancemode_enddate_1 | {"enabled":false} | tool_opencast |
+ When I reload the page
+ And I wait "2" seconds
+ And I click on "Add video" "button"
+ Then I should see "Opencast Maintenance Notification" in the "#user-notifications" "css_element"
+ And I should not see "Videos available in this course" in the "#region-main" "css_element"
+ And the following config values are set as admin:
+ | maintenancemode_1 | 0 | tool_opencast |
+ And I reload the page
+ When I click on "Add video" "button"
+ Then I should not see "Opencast Maintenance Notification" in the "#user-notifications" "css_element"
+ And I should see "Opencast Videos" in the "#page-header" "css_element"