diff --git a/amd/build/tablebulkactions.min.js b/amd/build/tablebulkactions.min.js new file mode 100644 index 0000000000000000000000000000000000000000..b899d9847d0498228f1a697236e18e9e313176e5 --- /dev/null +++ b/amd/build/tablebulkactions.min.js @@ -0,0 +1,2 @@ +define ("tool_lifecycle/tablebulkactions",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.init=function(){var a=document.querySelectorAll("input[name=\"procerror-select\"]"),b=document.querySelectorAll("*[data-lifecycle-action]");b.forEach(function(b){b.onclick=function(c){c.preventDefault();var d=[{k:"action",v:b.getAttribute("data-lifecycle-action")},{k:"sesskey",v:M.cfg.sesskey}];if("1"===b.getAttribute("data-lifecycle-forall")){d.push({k:"all",v:"1"});e(window.location,d)}else{a.forEach(function(a){if(a.checked){d.push({k:"id[]",v:a.value})}});e(window.location,d)}}})};function b(a){if("undefined"==typeof Symbol||null==a[Symbol.iterator]){if(Array.isArray(a)||(a=c(a))){var b=0,d=function(){};return{s:d,n:function n(){if(b>=a.length)return{done:!0};return{done:!1,value:a[b++]}},e:function e(a){throw a},f:d}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var e,f=!0,g=!1,h;return{s:function s(){e=a[Symbol.iterator]()},n:function n(){var a=e.next();f=a.done;return a},e:function e(a){g=!0;h=a},f:function f(){try{if(!f&&null!=e.return)e.return()}finally{if(g)throw h}}}}function c(a,b){if(!a)return;if("string"==typeof a)return d(a,b);var c=Object.prototype.toString.call(a).slice(8,-1);if("Object"===c&&a.constructor)c=a.constructor.name;if("Map"===c||"Set"===c)return Array.from(c);if("Arguments"===c||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(c))return d(a,b)}function d(a,b){if(null==b||b>a.length)b=a.length;for(var c=0,d=Array(b);c<b;c++){d[c]=a[c]}return d}function e(a,c){var d=document.createElement("form");document.body.appendChild(d);d.method="post";d.action=a;var e=b(c),f;try{for(e.s();!(f=e.n()).done;){var g=f.value,h=document.createElement("input");h.type="hidden";h.name=g.k;h.value=g.v;d.appendChild(h)}}catch(a){e.e(a)}finally{e.f()}d.submit()}}); +//# sourceMappingURL=tablebulkactions.min.js.map diff --git a/amd/build/tablebulkactions.min.js.map b/amd/build/tablebulkactions.min.js.map new file mode 100644 index 0000000000000000000000000000000000000000..ade9ff54eeee348eec8c06028506811da483ec53 --- /dev/null +++ b/amd/build/tablebulkactions.min.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../src/tablebulkactions.js"],"names":["checkboxes","document","querySelectorAll","action","forEach","a","onclick","e","preventDefault","data","k","v","getAttribute","M","cfg","sesskey","push","redirectPost","window","location","c","checked","value","url","form","createElement","body","appendChild","method","pair","input","type","name","submit"],"mappings":"uIA6CO,UAAgB,IACbA,CAAAA,CAAU,CAAGC,QAAQ,CAACC,gBAAT,CAA0B,kCAA1B,CADA,CAGbC,CAAM,CAAGF,QAAQ,CAACC,gBAAT,CAA0B,0BAA1B,CAHI,CAInBC,CAAM,CAACC,OAAP,CAAe,SAACC,CAAD,CAAO,CAClBA,CAAC,CAACC,OAAF,CAAY,SAACC,CAAD,CAAO,CACfA,CAAC,CAACC,cAAF,GACA,GAAIC,CAAAA,CAAI,CAAG,CACP,CAACC,CAAC,CAAE,QAAJ,CAAcC,CAAC,CAAEN,CAAC,CAACO,YAAF,CAAe,uBAAf,CAAjB,CADO,CAEP,CAACF,CAAC,CAAE,SAAJ,CAAeC,CAAC,CAAEE,CAAC,CAACC,GAAF,CAAMC,OAAxB,CAFO,CAAX,CAIA,GAAgD,GAA5C,GAAAV,CAAC,CAACO,YAAF,CAAe,uBAAf,CAAJ,CAAqD,CACjDH,CAAI,CAACO,IAAL,CAAU,CAACN,CAAC,CAAE,KAAJ,CAAWC,CAAC,CAAE,GAAd,CAAV,EACAM,CAAY,CAACC,MAAM,CAACC,QAAR,CAAkBV,CAAlB,CACf,CAHD,IAGQ,CACJT,CAAU,CAACI,OAAX,CAAmB,SAACgB,CAAD,CAAO,CACtB,GAAIA,CAAC,CAACC,OAAN,CAAe,CACXZ,CAAI,CAACO,IAAL,CAAU,CAACN,CAAC,CAAE,MAAJ,CAAYC,CAAC,CAAES,CAAC,CAACE,KAAjB,CAAV,CACH,CACJ,CAJD,EAKAL,CAAY,CAACC,MAAM,CAACC,QAAR,CAAkBV,CAAlB,CACf,CACJ,CACJ,CAnBD,CAoBH,C,u/BA1CD,QAASQ,CAAAA,CAAT,CAAsBM,CAAtB,CAA2Bd,CAA3B,CAAiC,CAC7B,GAAMe,CAAAA,CAAI,CAAGvB,QAAQ,CAACwB,aAAT,CAAuB,MAAvB,CAAb,CACAxB,QAAQ,CAACyB,IAAT,CAAcC,WAAd,CAA0BH,CAA1B,EACAA,CAAI,CAACI,MAAL,CAAc,MAAd,CACAJ,CAAI,CAACrB,MAAL,CAAcoB,CAAd,CAJ6B,QAKVd,CALU,QAK7B,2BAAyB,IAAdoB,CAAAA,CAAc,SACfC,CAAK,CAAG7B,QAAQ,CAACwB,aAAT,CAAuB,OAAvB,CADO,CAErBK,CAAK,CAACC,IAAN,CAAa,QAAb,CACAD,CAAK,CAACE,IAAN,CAAaH,CAAI,CAACnB,CAAlB,CACAoB,CAAK,CAACR,KAAN,CAAcO,CAAI,CAAClB,CAAnB,CACAa,CAAI,CAACG,WAAL,CAAiBG,CAAjB,CACH,CAX4B,+BAY7BN,CAAI,CAACS,MAAL,EACH,C","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 <http://www.gnu.org/licenses/>.\n\n/**\n * Javascript controller for checkboxed table.\n * @module tool_lifecycle/tablebulkactions\n * @copyright 2021 Justus Dieckmann WWU\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n/**\n * Helper function to redirect via POST\n * @param {String} url redirect to\n * @param {Array} data redirect with data\n */\nfunction redirectPost(url, data) {\n const form = document.createElement('form');\n document.body.appendChild(form);\n form.method = 'post';\n form.action = url;\n for (const pair of data) {\n const input = document.createElement('input');\n input.type = 'hidden';\n input.name = pair.k;\n input.value = pair.v;\n form.appendChild(input);\n }\n form.submit();\n}\n\n/**\n * Init function\n */\nexport function init() {\n const checkboxes = document.querySelectorAll('input[name=\"procerror-select\"]');\n\n const action = document.querySelectorAll('*[data-lifecycle-action]');\n action.forEach((a) => {\n a.onclick = (e) => {\n e.preventDefault();\n let data = [\n {k: 'action', v: a.getAttribute('data-lifecycle-action')},\n {k: 'sesskey', v: M.cfg.sesskey}\n ];\n if (a.getAttribute('data-lifecycle-forall') === '1') {\n data.push({k: 'all', v: '1'});\n redirectPost(window.location, data);\n } else {\n checkboxes.forEach((c) => {\n if (c.checked) {\n data.push({k: 'id[]', v: c.value});\n }\n });\n redirectPost(window.location, data);\n }\n };\n });\n}"],"file":"tablebulkactions.min.js"} \ No newline at end of file diff --git a/amd/src/tablebulkactions.js b/amd/src/tablebulkactions.js new file mode 100644 index 0000000000000000000000000000000000000000..5e0afbad517e8e478e1551d8002996ab26d9f330 --- /dev/null +++ b/amd/src/tablebulkactions.js @@ -0,0 +1,70 @@ +// 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 <http://www.gnu.org/licenses/>. + +/** + * Javascript controller for checkboxed table. + * @module tool_lifecycle/tablebulkactions + * @copyright 2021 Justus Dieckmann WWU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +/** + * Helper function to redirect via POST + * @param {String} url redirect to + * @param {Array} data redirect with data + */ +function redirectPost(url, data) { + const form = document.createElement('form'); + document.body.appendChild(form); + form.method = 'post'; + form.action = url; + for (const pair of data) { + const input = document.createElement('input'); + input.type = 'hidden'; + input.name = pair.k; + input.value = pair.v; + form.appendChild(input); + } + form.submit(); +} + +/** + * Init function + */ +export function init() { + const checkboxes = document.querySelectorAll('input[name="procerror-select"]'); + + const action = document.querySelectorAll('*[data-lifecycle-action]'); + action.forEach((a) => { + a.onclick = (e) => { + e.preventDefault(); + let data = [ + {k: 'action', v: a.getAttribute('data-lifecycle-action')}, + {k: 'sesskey', v: M.cfg.sesskey} + ]; + if (a.getAttribute('data-lifecycle-forall') === '1') { + data.push({k: 'all', v: '1'}); + redirectPost(window.location, data); + } else { + checkboxes.forEach((c) => { + if (c.checked) { + data.push({k: 'id[]', v: c.value}); + } + }); + redirectPost(window.location, data); + } + }; + }); +} \ No newline at end of file diff --git a/classes/local/manager/process_manager.php b/classes/local/manager/process_manager.php index f8b28c9fc9b7a16eced8bc6eb1e5f9efe940b451..96e43b4021785953edc6e0f7af56865453353966 100644 --- a/classes/local/manager/process_manager.php +++ b/classes/local/manager/process_manager.php @@ -24,6 +24,7 @@ namespace tool_lifecycle\local\manager; use core\event\course_deleted; +use Exception; use tool_lifecycle\local\entity\process; use tool_lifecycle\event\process_proceeded; use tool_lifecycle\event\process_rollback; @@ -245,4 +246,66 @@ class process_manager { $steplib->abort_course($process); self::remove_process($process); } + + /** + * Moves a process into the procerror table. + * + * @param process $process The process + * @param Exception $e The exception + * @return void + */ + public static function insert_process_error(process $process, Exception $e) { + global $DB; + + $procerror = (object) clone $process; + $procerror->errormessage = get_class($e) . ': ' . $e->getMessage(); + $procerror->errortrace = $e->getTraceAsString(); + $m = ''; + foreach ($e->getTrace() as $v) { + $m .= $v['file'] . ':' . $v['line'] . '::'; + } + $procerror->errorhash = md5($m); + $procerror->waiting = intval($procerror->waiting); + + $DB->insert_record_raw('tool_lifecycle_proc_error', $procerror, false, false, true); + $DB->delete_records('tool_lifecycle_process', ['id' => $process->id]); + } + + /** + * Proceed process from procerror back into the process board. + * @param int $processid the processid + * @return void + */ + public static function proceed_process_after_error(int $processid) { + global $DB; + $process = $DB->get_record('tool_lifecycle_proc_error', ['id' => $processid]); + // Unset process error entries. + unset($process->errormessage); + unset($process->errortrace); + unset($process->errorhash); + + $DB->insert_record_raw('tool_lifecycle_process', $process, false, false, true); + $DB->delete_records('tool_lifecycle_proc_error', ['id' => $process->id]); + } + + /** + * Rolls back a process from procerror table + * @param int $processid the processid + * @return void + */ + public static function rollback_process_after_error(int $processid) { + global $DB; + + $process = $DB->get_record('tool_lifecycle_proc_error', ['id' => $processid]); + // Unset process error entries. + unset($process->errormessage); + unset($process->errortrace); + unset($process->errorhash); + + $DB->insert_record_raw('tool_lifecycle_process', $process, false, false, true); + $DB->delete_records('tool_lifecycle_proc_error', ['id' => $process->id]); + + delayed_courses_manager::set_course_delayed_for_workflow($process->courseid, true, $process->workflowid); + self::rollback_process($process); + } } diff --git a/classes/local/table/process_errors_table.php b/classes/local/table/process_errors_table.php new file mode 100644 index 0000000000000000000000000000000000000000..d7d0f9765c013363480d9f8ad26e20e9391bf004 --- /dev/null +++ b/classes/local/table/process_errors_table.php @@ -0,0 +1,200 @@ +<?php +// 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 <http://www.gnu.org/licenses/>. + +/** + * Table listing all process errors + * + * @package tool_lifecycle + * @copyright 2021 Justus Dieckmann WWU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace tool_lifecycle\local\table; + +defined('MOODLE_INTERNAL') || die; + +require_once($CFG->libdir . '/tablelib.php'); + +/** + * Table listing all process errors + * + * @package tool_lifecycle + * @copyright 2021 Justus Dieckmann WWU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class process_errors_table extends \table_sql { + + /** + * @var array "cached" lang strings + */ + private $strings; + + /** + * Constructor for delayed_courses_table. + * + * @throws \coding_exception + */ + public function __construct() { + global $OUTPUT; + + parent::__construct('tool_lifecycle-process_errors'); + + $this->strings = [ + 'proceed' => get_string('proceed', 'tool_lifecycle'), + 'rollback' => get_string('rollback', 'tool_lifecycle') + ]; + + $fields = 'c.fullname as course, w.title as workflow, s.instancename as step, pe.*'; + + $from = '{tool_lifecycle_proc_error} pe ' . + 'JOIN {tool_lifecycle_workflow} w ON pe.workflowid = w.id ' . + 'JOIN {tool_lifecycle_step} s ON pe.workflowid = s.workflowid AND pe.stepindex = s.sortindex ' . + 'LEFT JOIN {course} c ON pe.courseid = c.id '; + + $this->set_sql($fields, $from, 'TRUE'); + $this->column_nosort = ['select', 'tools']; + $this->define_columns(['select', 'workflow', 'step', 'courseid', 'course', 'error', 'tools']); + $this->define_headers([ + $OUTPUT->render(new \core\output\checkbox_toggleall('procerrors-table', true, [ + 'id' => 'select-all-procerrors', + 'name' => 'select-all-procerrors', + 'label' => get_string('selectall'), + 'labelclasses' => 'sr-only', + 'classes' => 'm-1', + 'checked' => false, + ])), + get_string('workflow', 'tool_lifecycle'), + get_string('step', 'tool_lifecycle'), + get_string('courseid', 'tool_lifecycle'), + get_string('course'), + get_string('error'), + get_string('tools', 'tool_lifecycle') + ]); + } + + /** + * Render error column. + * + * @param object $row Row data. + * @return string error cell + * @throws \coding_exception + * @throws \moodle_exception + */ + public function col_error($row) { + return "<details><summary>" . + nl2br(htmlentities($row->errormessage)) . + "</summary>" . + nl2br(htmlentities($row->errortrace)) . + "</details>"; + } + + /** + * Render tools column. + * + * @param object $row Row data. + * @return string pluginname of the subplugin + * @throws \coding_exception + * @throws \moodle_exception + */ + public function col_tools($row) { + global $OUTPUT; + + $actionmenu = new \action_menu(); + $actionmenu->add_primary_action( + new \action_menu_link_primary( + new \moodle_url('', ['action' => 'proceed', 'id[]' => $row->id, 'sesskey' => sesskey()]), + new \pix_icon('e/tick', $this->strings['proceed']), + $this->strings['proceed'] + ) + ); + $actionmenu->add_primary_action( + new \action_menu_link_primary( + new \moodle_url('', ['action' => 'rollback', 'id[]' => $row->id, 'sesskey' => sesskey()]), + new \pix_icon('e/undo', $this->strings['rollback']), + $this->strings['rollback'] + ) + ); + return $OUTPUT->render($actionmenu); + } + + /** + * Generate the select column. + * + * @param \stdClass $data + * @return string + */ + public function col_select($data) { + global $OUTPUT; + + $checkbox = new \core\output\checkbox_toggleall('procerrors-table', false, [ + 'classes' => 'usercheckbox m-1', + 'id' => 'procerror' . $data->id, + 'name' => 'procerror-select', + 'value' => $data->id, + 'checked' => false, + 'label' => get_string('selectitem', 'moodle', $data->id), + 'labelclasses' => 'accesshide', + ]); + + return $OUTPUT->render($checkbox); + } + + /** + * Override the table show_hide_link to not show for select column. + * + * @param string $column the column name, index into various names. + * @param int $index numerical index of the column. + * @return string HTML fragment. + */ + protected function show_hide_link($column, $index) { + if ($index > 0) { + return parent::show_hide_link($column, $index); + } + return ''; + } + + /** + * Hook that can be overridden in child classes to wrap a table in a form + * for example. Called only when there is data to display and not + * downloading. + */ + public function wrap_html_finish() { + global $OUTPUT; + parent::wrap_html_finish(); + echo "<br>"; + + $actionmenu = new \action_menu(); + $actionmenu->add_secondary_action( + new \action_menu_link_secondary( + new \moodle_url(''), + new \pix_icon('e/tick', $this->strings['proceed']), + $this->strings['proceed'], + ['data-lifecycle-action' => 'proceed'] + ) + ); + + $actionmenu->add_secondary_action( + new \action_menu_link_secondary( + new \moodle_url(''), + new \pix_icon('e/undo', $this->strings['rollback']), + $this->strings['rollback'], + ['data-lifecycle-action' => 'rollback'] + ) + ); + + $actionmenu->set_menu_trigger(get_string('forselected', 'tool_lifecycle')); + echo $OUTPUT->render_action_menu($actionmenu); + } +} diff --git a/classes/processor.php b/classes/processor.php index f152055be9e81c931a4d79e87e48a60feb97674f..5413dfb03bcbda9ccbb978ddfc1e5062a7bebe06 100644 --- a/classes/processor.php +++ b/classes/processor.php @@ -119,10 +119,15 @@ class processor { $step = step_manager::get_step_instance_by_workflow_index($process->workflowid, $process->stepindex); $lib = lib_manager::get_step_lib($step->subpluginname); - if ($process->waiting) { - $result = $lib->process_waiting_course($process->id, $step->id, $course); - } else { - $result = $lib->process_course($process->id, $step->id, $course); + try { + if ($process->waiting) { + $result = $lib->process_waiting_course($process->id, $step->id, $course); + } else { + $result = $lib->process_course($process->id, $step->id, $course); + } + } catch (\Exception $e) { + process_manager::insert_process_error($process, $e); + break; } if ($result == step_response::waiting()) { process_manager::set_process_waiting($process); @@ -220,7 +225,9 @@ class processor { $sql = 'SELECT {course}.* from {course} '. 'left join {tool_lifecycle_process} '. 'ON {course}.id = {tool_lifecycle_process}.courseid '. - 'WHERE {tool_lifecycle_process}.courseid is null AND ' . $where; + 'LEFT JOIN {tool_lifecycle_proc_error} pe ON {course}.id = pe.courseid ' . + 'WHERE {tool_lifecycle_process}.courseid is null AND ' . + 'pe.courseid IS NULL AND '. $where; return $DB->get_recordset_sql($sql, $whereparams); } diff --git a/db/install.xml b/db/install.xml index d6877a526fa7c2e2ef70adfb40008d09f4d4c12d..4c085807c9e53826ea4b51b0cc4eb8702099eac0 100644 --- a/db/install.xml +++ b/db/install.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<XMLDB PATH="admin/tool/lifecycle/db" VERSION="20190822" COMMENT="XMLDB file for Moodle tool/lifecycle" +<XMLDB PATH="admin/tool/lifecycle/db" VERSION="20211122" COMMENT="XMLDB file for Moodle tool/lifecycle" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../lib/xmldb/xmldb.xsd" > @@ -146,5 +146,23 @@ <KEY NAME="userid_fk" TYPE="foreign" FIELDS="userid" REFTABLE="user" REFFIELDS="id"/> </KEYS> </TABLE> + <TABLE NAME="tool_lifecycle_proc_error" COMMENT="table containing all errored lifecycle processes"> + <FIELDS> + <FIELD NAME="id" TYPE="int" LENGTH="20" NOTNULL="true" SEQUENCE="true" COMMENT="id of the process"/> + <FIELD NAME="courseid" TYPE="int" LENGTH="20" NOTNULL="true" SEQUENCE="false" COMMENT="course id"/> + <FIELD NAME="workflowid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="id of the workflow"/> + <FIELD NAME="stepindex" TYPE="int" LENGTH="5" NOTNULL="true" SEQUENCE="false" COMMENT="sortindex of the step within the workflow"/> + <FIELD NAME="waiting" TYPE="int" LENGTH="1" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="tells if the process is in status waiting"/> + <FIELD NAME="timestepchanged" TYPE="int" LENGTH="11" NOTNULL="true" SEQUENCE="false" COMMENT="unix timestamp - time the step instance of the process was changed last."/> + <FIELD NAME="errormessage" TYPE="text" NOTNULL="true" SEQUENCE="false" COMMENT="Message of the error"/> + <FIELD NAME="errortrace" TYPE="text" NOTNULL="true" SEQUENCE="false"/> + <FIELD NAME="errorhash" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false" COMMENT="Where the error occured in the form 'path/to/filename.php:line'"/> + </FIELDS> + <KEYS> + <KEY NAME="primary" TYPE="primary" FIELDS="id"/> + <KEY NAME="courseid_fk" TYPE="foreign-unique" FIELDS="courseid" REFTABLE="course" REFFIELDS="id" COMMENT="Foreign key on course table"/> + <KEY NAME="workflowid_fk" TYPE="foreign" FIELDS="workflowid" REFTABLE="tool_lifecycle_workflow" REFFIELDS="id"/> + </KEYS> + </TABLE> </TABLES> </XMLDB> \ No newline at end of file diff --git a/db/upgrade.php b/db/upgrade.php index 1e5a172354a00cdabea132fb5926a5b7746187fc..c0b808b91fbc60d28f68f9537f6d67bfc6a7a74a 100644 --- a/db/upgrade.php +++ b/db/upgrade.php @@ -446,5 +446,35 @@ function xmldb_tool_lifecycle_upgrade($oldversion) { upgrade_plugin_savepoint(true, 2020091800, 'tool', 'lifecycle'); } + if ($oldversion < 2021112300) { + + // Define table tool_lifecycle_proc_error to be created. + $table = new xmldb_table('tool_lifecycle_proc_error'); + + // Adding fields to table tool_lifecycle_proc_error. + $table->add_field('id', XMLDB_TYPE_INTEGER, '20', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('courseid', XMLDB_TYPE_INTEGER, '20', null, XMLDB_NOTNULL, null, null); + $table->add_field('workflowid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + $table->add_field('stepindex', XMLDB_TYPE_INTEGER, '5', null, XMLDB_NOTNULL, null, null); + $table->add_field('waiting', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '0'); + $table->add_field('timestepchanged', XMLDB_TYPE_INTEGER, '11', null, XMLDB_NOTNULL, null, null); + $table->add_field('errormessage', XMLDB_TYPE_TEXT, null, null, XMLDB_NOTNULL, null, null); + $table->add_field('errortrace', XMLDB_TYPE_TEXT, null, null, XMLDB_NOTNULL, null, null); + $table->add_field('errorhash', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); + + // Adding keys to table tool_lifecycle_proc_error. + $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']); + $table->add_key('courseid_fk', XMLDB_KEY_FOREIGN_UNIQUE, ['courseid'], 'course', ['id']); + $table->add_key('workflowid_fk', XMLDB_KEY_FOREIGN, ['workflowid'], 'tool_lifecycle_workflow', ['id']); + + // Conditionally launch create table for tool_lifecycle_proc_error. + if (!$dbman->table_exists($table)) { + $dbman->create_table($table); + } + + // Lifecycle savepoint reached. + upgrade_plugin_savepoint(true, 2021112300, 'tool', 'lifecycle'); + } + return true; } diff --git a/errors.php b/errors.php new file mode 100644 index 0000000000000000000000000000000000000000..3f8032c1356721150a3bd622af6a8d692bbc522f --- /dev/null +++ b/errors.php @@ -0,0 +1,68 @@ +<?php +// 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 <http://www.gnu.org/licenses/>. + +/** + * Displays the process errors + * + * @package tool_lifecycle + * @copyright 2021 Justus Dieckmann WWU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +use tool_lifecycle\local\manager\process_manager; +use tool_lifecycle\local\table\process_errors_table; + +require_once(__DIR__ . '/../../../config.php'); +require_once($CFG->libdir . '/adminlib.php'); + +$PAGE->set_context(context_system::instance()); +require_login(); +require_capability('moodle/site:config', context_system::instance()); + +admin_externalpage_setup('tool_lifecycle_process_errors'); + +$PAGE->set_url(new \moodle_url('/admin/tool/lifecycle/errors.php')); + +// Action handling (delete, bulk-delete). +$action = optional_param('action', null, PARAM_ALPHANUMEXT); +if ($action) { + global $DB; + require_sesskey(); + $ids = required_param_array('id', PARAM_INT); + if ($action == 'proceed') { + foreach ($ids as $id) { + process_manager::proceed_process_after_error($id); + } + } else if ($action == 'rollback') { + foreach ($ids as $id) { + process_manager::rollback_process_after_error($id); + } + } + redirect($PAGE->url); +} + +$PAGE->set_title(get_string('process_errors_header', 'tool_lifecycle')); +$PAGE->set_heading(get_string('process_errors_header', 'tool_lifecycle')); + +$table = new process_errors_table(); +$table->define_baseurl($PAGE->url); + +$PAGE->requires->js_call_amd('tool_lifecycle/tablebulkactions', 'init'); + +echo $OUTPUT->header(); +$table->out(100, false); + +echo $OUTPUT->footer(); diff --git a/lang/de/tool_lifecycle.php b/lang/de/tool_lifecycle.php index 5ce7b49b0715151ca750421d97f4260c13bd8d4a..6409cc02877d8c3e55d17a1b40a0985858768558 100644 --- a/lang/de/tool_lifecycle.php +++ b/lang/de/tool_lifecycle.php @@ -175,3 +175,7 @@ $string['restore_trigger_does_not_exist'] = 'Der Trigger {$a} ist nicht installi $string['process_triggered_event'] = 'Ein Prozess wurde ausgelöst'; $string['process_proceeded_event'] = 'Ein Prozess wurde fortgeführt'; $string['process_rollback_event'] = 'Ein Prozess wurde zurückgesetzt'; + +$string['courseid'] = 'Kurs-ID'; +$string['process_errors_header'] = 'Prozessfehler'; +$string['forselected'] = 'Für alle ausgewählten Prozesse'; diff --git a/lang/en/tool_lifecycle.php b/lang/en/tool_lifecycle.php index dcf69f46e01aea6dc83a4e46ed13eb963e59a36f..25aba077bd8646f1eeb41ba77b8057484d137864 100644 --- a/lang/en/tool_lifecycle.php +++ b/lang/en/tool_lifecycle.php @@ -210,3 +210,9 @@ $string['all_delays'] = 'All delays'; $string['globally'] = 'Global delays'; $string['delays_for_workflow'] = 'Delays for "{$a}"'; $string['delete_all_delays'] = 'Delete all delays'; + +$string['courseid'] = 'Course ID'; +$string['process_errors_header'] = 'Process errors'; +$string['proceed'] = 'Proceed'; +$string['rollback'] = 'Rollback'; +$string['forselected'] = 'For all selected processes'; diff --git a/settings.php b/settings.php index 71a26f829a060b2c7f39bffabce1824c70c10f15..ce6ad36f9395ad5b095bd4093de8c40272881ed1 100644 --- a/settings.php +++ b/settings.php @@ -50,6 +50,9 @@ if ($hassiteconfig) { $ADMIN->add('lifecycle_category', new admin_externalpage('tool_lifecycle_delayed_courses', get_string('delayed_courses_header', 'tool_lifecycle'), new moodle_url('/admin/tool/lifecycle/delayedcourses.php'))); + $ADMIN->add('lifecycle_category', new admin_externalpage('tool_lifecycle_process_errors', + get_string('process_errors_header', 'tool_lifecycle'), + new moodle_url('/admin/tool/lifecycle/errors.php'))); if ($ADMIN->fulltree) { $triggers = core_component::get_plugin_list('lifecycletrigger'); diff --git a/version.php b/version.php index 46092b8c42cf5b5f3c379d2bad970be330eb3819..814e1fb9f09a02bec29cfd2f6c825ab7706c1860 100644 --- a/version.php +++ b/version.php @@ -25,7 +25,7 @@ defined('MOODLE_INTERNAL') || die; $plugin->maturity = MATURITY_BETA; -$plugin->version = 2021051700; +$plugin->version = 2021112300; $plugin->component = 'tool_lifecycle'; $plugin->requires = 2017111300; // Require Moodle 3.4 (or above). $plugin->release = 'v3.11-r1';