From 4761bb5571acc6b5896d5cbda69dd9a177e498fd Mon Sep 17 00:00:00 2001 From: NinaHerrmann Date: Mon, 18 Mar 2019 12:20:58 +0100 Subject: [PATCH 01/73] added sublugin type --- db/subplugins.php | 19 +++++++++++++++++++ lang/en/ratingallocate.php | 5 +++++ 2 files changed, 24 insertions(+) create mode 100644 db/subplugins.php diff --git a/db/subplugins.php b/db/subplugins.php new file mode 100644 index 00000000..7552ff21 --- /dev/null +++ b/db/subplugins.php @@ -0,0 +1,19 @@ +. + +$subplugins = array( + 'raalgo' => 'mod/ratingallocate/algorithm', +); diff --git a/lang/en/ratingallocate.php b/lang/en/ratingallocate.php index bfef88c5..039047ce 100644 --- a/lang/en/ratingallocate.php +++ b/lang/en/ratingallocate.php @@ -232,6 +232,11 @@ $string['choice_table_tools'] = 'Edit'; // +// +$string['subplugintype_raalgo'] = 'Allocation algorithm'; +$string['subplugintype_raalgo_plural'] = 'Allocation algorithms'; +// + $string['is_published'] = 'Published'; $string['strategy_settings_label'] = 'Designation for "{$a}"'; From 140132ecff318b9d2d3a183a2312e130146a629c Mon Sep 17 00:00:00 2001 From: NinaHerrmann Date: Mon, 18 Mar 2019 12:22:01 +0100 Subject: [PATCH 02/73] fixed typo --- solver/ford-fulkerson-koegel.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/solver/ford-fulkerson-koegel.php b/solver/ford-fulkerson-koegel.php index d8fcd922..69ef6820 100644 --- a/solver/ford-fulkerson-koegel.php +++ b/solver/ford-fulkerson-koegel.php @@ -62,7 +62,7 @@ public function compute_distribution($choicerecords, $ratings, $usercount) { for ($i = 1; $i <= $usercount; $i++) { // Look for an augmenting path (a shortest path from the source to the sink) $path = $this->find_shortest_path_bellmanf_koegel($source, $sink); - // If ther is no such path, it is impossible to fit any more users into groups. + // If there is no such path, it is impossible to fit any more users into groups. if (is_null($path)) { // Stop the algorithm continue; From e03779b9cb14a83e10999021df045c42b4ebbc6f Mon Sep 17 00:00:00 2001 From: Justus Dieckmann Date: Mon, 18 Mar 2019 13:04:39 +0100 Subject: [PATCH 03/73] Added formelement for minsize --- form_modify_choice.php | 10 ++++++++++ lang/en/ratingallocate.php | 2 ++ 2 files changed, 12 insertions(+) diff --git a/form_modify_choice.php b/form_modify_choice.php index b6306b28..33059a90 100644 --- a/form_modify_choice.php +++ b/form_modify_choice.php @@ -78,6 +78,16 @@ public function definition() { $mform->addElement('text', $elementname, get_string('choice_explanation', ratingallocate_MOD_NAME)); $mform->setType($elementname, PARAM_TEXT); + if (true) { + $elementname = 'minsize'; + $mform->addElement('text', $elementname, get_string('choice_minsize', ratingallocate_MOD_NAME)); + $mform->setType($elementname, PARAM_TEXT); + $mform->addRule($elementname, get_string('err_required', 'form') , 'required', null, 'server'); + $mform->addRule($elementname, get_string('err_numeric', 'form') , 'numeric', null, 'server'); + $mform->addRule($elementname, get_string('err_positivnumber', 'ratingallocate') , 'regex', '/^[1-9][0-9]*|0/', 'server'); + $mform->addRule(array($elementname, "maxsize"), get_string('err_gte', 'ratingallocate'), "compare", "lte", "server"); + } + $elementname = 'maxsize'; $mform->addElement('text', $elementname, get_string('choice_maxsize', ratingallocate_MOD_NAME)); $mform->setType($elementname, PARAM_TEXT); diff --git a/lang/en/ratingallocate.php b/lang/en/ratingallocate.php index bfef88c5..5722192e 100644 --- a/lang/en/ratingallocate.php +++ b/lang/en/ratingallocate.php @@ -196,6 +196,7 @@ $string['choice_explanation'] = 'Description (optional)'; $string['choice_maxsize'] = 'Max. number of participants'; $string['choice_maxsize_display'] = 'Maximum number of students'; +$string['choice_minsize'] = 'Min. number of participants'; $string['choice_title'] = 'Title'; $string['choice_title_help'] = 'Title of the choice. *Attention* all active choices will be displayed while ordered by title.'; $string['edit_choice'] = 'Edit choice'; @@ -221,6 +222,7 @@ $string['err_required'] = 'You need to provide a value for this field.'; $string['err_minimum'] = 'The minimum value for this field is {$a}.'; $string['err_maximum'] = 'The maximum value for this field is {$a}.'; +$string['err_gte'] = 'The minimum number of participants is greater than the maximum number of participants.'; // // $string['show_choices_header'] = 'List of all choices'; From 2a355e89f52200bea85a4995d8622aaa3322a5b1 Mon Sep 17 00:00:00 2001 From: NinaHerrmann Date: Mon, 18 Mar 2019 13:20:10 +0100 Subject: [PATCH 04/73] defined subplugin interface --- classes/algorithm.php | 56 +++++++++++++++++++++++++++++++++++ classes/plugininfo/raalgo.php | 20 +++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 classes/algorithm.php create mode 100644 classes/plugininfo/raalgo.php diff --git a/classes/algorithm.php b/classes/algorithm.php new file mode 100644 index 00000000..2081dc79 --- /dev/null +++ b/classes/algorithm.php @@ -0,0 +1,56 @@ +. + +namespace mod_ratingallocate; + +defined('MOODLE_INTERNAL') || die(); + +require_once(__DIR__ . '/../locallib.php'); + +abstract class algorithm { + + protected abstract function compute_distribution($choicerecords, $ratings, $usercount); + + /** + * Entry-point for the \ratingallocate object to call a solver + * @param \ratingallocate $ratingallocate + */ + public function distribute_users(\ratingallocate $ratingallocate) { + + // Load data from database. + $choicerecords = $ratingallocate->get_rateable_choices(); + $ratings = $ratingallocate->get_ratings_for_rateable_choices(); + + // Randomize the order of the enrties to prevent advantages for early entry. + shuffle($ratings); + + $usercount = count($ratingallocate->get_raters_in_course()); + + $distributions = $this->compute_distribution($choicerecords, $ratings, $usercount); + + // Perform all allocation manipulation / inserts in one transaction. + $transaction = $ratingallocate->db->start_delegated_transaction(); + + $ratingallocate->clear_all_allocations(); + + foreach ($distributions as $choiceid => $users) { + foreach ($users as $userid) { + $ratingallocate->add_allocation($choiceid, $userid); + } + } + $transaction->allow_commit(); + } +} \ No newline at end of file diff --git a/classes/plugininfo/raalgo.php b/classes/plugininfo/raalgo.php new file mode 100644 index 00000000..bdca6b91 --- /dev/null +++ b/classes/plugininfo/raalgo.php @@ -0,0 +1,20 @@ +. + +namespace mod_ratingallocate\plugininfo; +class raalgo extends \core\plugininfo\base { + +} \ No newline at end of file From 7839ffdcafa66e13bacada2dd135012c4b181c86 Mon Sep 17 00:00:00 2001 From: Justus Dieckmann Date: Mon, 18 Mar 2019 13:22:19 +0100 Subject: [PATCH 05/73] WIP add databasefield for minsize --- db/install.xml | 3 ++- db/upgrade.php | 18 +++++++++++++++++- form_modify_choice.php | 6 +++++- version.php | 2 +- 4 files changed, 25 insertions(+), 4 deletions(-) diff --git a/db/install.xml b/db/install.xml index b5a7b6f5..e92ab706 100644 --- a/db/install.xml +++ b/db/install.xml @@ -1,5 +1,5 @@ - @@ -38,6 +38,7 @@ + diff --git a/db/upgrade.php b/db/upgrade.php index 14875d7f..c52ee98f 100644 --- a/db/upgrade.php +++ b/db/upgrade.php @@ -130,7 +130,23 @@ function xmldb_ratingallocate_upgrade($oldversion) { return false; } + if ($oldversion < 2018112900) { + + // Define field minsize to be added to ratingallocate_choices. + $table = new xmldb_table('ratingallocate_choices'); + $field = new xmldb_field('minsize', XMLDB_TYPE_INTEGER, '10', null, null, null, '0', 'maxsize'); + + // Conditionally launch add field minsize. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Ratingallocate savepoint reached. + upgrade_mod_savepoint(true, 2018112900, 'ratingallocate'); + } + + } - + return true; } diff --git a/form_modify_choice.php b/form_modify_choice.php index 33059a90..c85e84e1 100644 --- a/form_modify_choice.php +++ b/form_modify_choice.php @@ -85,7 +85,6 @@ public function definition() { $mform->addRule($elementname, get_string('err_required', 'form') , 'required', null, 'server'); $mform->addRule($elementname, get_string('err_numeric', 'form') , 'numeric', null, 'server'); $mform->addRule($elementname, get_string('err_positivnumber', 'ratingallocate') , 'regex', '/^[1-9][0-9]*|0/', 'server'); - $mform->addRule(array($elementname, "maxsize"), get_string('err_gte', 'ratingallocate'), "compare", "lte", "server"); } $elementname = 'maxsize'; @@ -95,6 +94,11 @@ public function definition() { $mform->addRule($elementname, get_string('err_numeric', 'form') , 'numeric', null, 'server'); $mform->addRule($elementname, get_string('err_positivnumber', 'ratingallocate') , 'regex', '/^[1-9][0-9]*|0/', 'server'); + if (true) { + $mform->addRule(array("minsize", "maxsize"), get_string('err_gte', 'ratingallocate'), "compare", "lte", "server"); + } + + $elementname = 'active'; $mform->addElement('advcheckbox', $elementname, get_string('choice_active', ratingallocate_MOD_NAME), null, null, array(0, 1)); diff --git a/version.php b/version.php index 2b5c59e8..59c7cb50 100644 --- a/version.php +++ b/version.php @@ -26,7 +26,7 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2018112900; // The current module version (Date: YYYYMMDDXX) +$plugin->version = 2019031800; // The current module version (Date: YYYYMMDDXX) $plugin->requires = 2017111300; // Requires this Moodle version $plugin->maturity = MATURITY_STABLE; $plugin->release = 'v3.6-r1'; From e29e4dc439b5c7fe989a4f72e3fc1ce8dd09fe76 Mon Sep 17 00:00:00 2001 From: Justus Dieckmann Date: Mon, 18 Mar 2019 13:56:23 +0100 Subject: [PATCH 06/73] save min into db --- db/upgrade.php | 21 ++++++++++----------- version.php | 2 +- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/db/upgrade.php b/db/upgrade.php index c52ee98f..37848871 100644 --- a/db/upgrade.php +++ b/db/upgrade.php @@ -130,22 +130,21 @@ function xmldb_ratingallocate_upgrade($oldversion) { return false; } - if ($oldversion < 2018112900) { + } - // Define field minsize to be added to ratingallocate_choices. - $table = new xmldb_table('ratingallocate_choices'); - $field = new xmldb_field('minsize', XMLDB_TYPE_INTEGER, '10', null, null, null, '0', 'maxsize'); + if ($oldversion < 2019031802) { - // Conditionally launch add field minsize. - if (!$dbman->field_exists($table, $field)) { - $dbman->add_field($table, $field); - } + // Define field minsize to be added to ratingallocate_choices. + $table = new xmldb_table('ratingallocate_choices'); + $field = new xmldb_field('minsize', XMLDB_TYPE_INTEGER, '10', null, null, null, '0', 'maxsize'); - // Ratingallocate savepoint reached. - upgrade_mod_savepoint(true, 2018112900, 'ratingallocate'); + // Conditionally launch add field minsize. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); } - + // Ratingallocate savepoint reached. + upgrade_mod_savepoint(true, 2019031802, 'ratingallocate'); } return true; diff --git a/version.php b/version.php index 59c7cb50..cec6fd8e 100644 --- a/version.php +++ b/version.php @@ -26,7 +26,7 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2019031800; // The current module version (Date: YYYYMMDDXX) +$plugin->version = 2019031802; // The current module version (Date: YYYYMMDDXX) $plugin->requires = 2017111300; // Requires this Moodle version $plugin->maturity = MATURITY_STABLE; $plugin->release = 'v3.6-r1'; From bdcac4af545cd923b1e31b79a535859345ac9b4a Mon Sep 17 00:00:00 2001 From: NinaHerrmann Date: Mon, 18 Mar 2019 14:02:51 +0100 Subject: [PATCH 07/73] added edmondskarp as subplugin --- .../edmondskarp/classes/algorithm_impl.php | 283 ++++++++++++++++++ algorithm/edmondskarp/classes/edge.php | 49 +++ .../lang/en/raalgo_edmondskarp.php | 25 ++ algorithm/edmondskarp/version.php | 30 ++ classes/algorithm.php | 1 + db/subplugins.php | 2 + version.php | 2 +- 7 files changed, 391 insertions(+), 1 deletion(-) create mode 100644 algorithm/edmondskarp/classes/algorithm_impl.php create mode 100644 algorithm/edmondskarp/classes/edge.php create mode 100644 algorithm/edmondskarp/lang/en/raalgo_edmondskarp.php create mode 100644 algorithm/edmondskarp/version.php diff --git a/algorithm/edmondskarp/classes/algorithm_impl.php b/algorithm/edmondskarp/classes/algorithm_impl.php new file mode 100644 index 00000000..2107ddc0 --- /dev/null +++ b/algorithm/edmondskarp/classes/algorithm_impl.php @@ -0,0 +1,283 @@ +. + +/** + * + * Contains the algorithm for the distribution + * + * @package raalgo_edmondskarp + * @copyright 2014 M Schulze + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +defined('MOODLE_INTERNAL') || die(); + +class algorithm_impl extends \mod_ratingallocate\algorithm { + + public function get_name() { + return 'edmonds_karp'; + } + + public function compute_distribution($choicerecords, $ratings, $usercount) { + $choicedata = array(); + foreach ($choicerecords as $record) { + $choicedata[$record->id] = $record; + } + + $choicecount = count($choicedata); + // Index of source and sink in the graph + $source = 0; + $sink = $choicecount + $usercount + 1; + + list($fromuserid, $touserid, $fromchoiceid, $tochoiceid) = $this->setup_id_conversions($usercount, $ratings); + + $this->setup_graph($choicecount, $usercount, $fromuserid, $fromchoiceid, $ratings, $choicedata, $source, $sink, -1); + + // Now that the datastructure is complete, we can start the algorithm + // This is an adaptation of the Ford-Fulkerson algorithm + // with Bellman-Ford as search function (see: Edmonds-Karp in Introduction to Algorithms) + // http://stackoverflow.com/questions/6681075/while-loop-in-php-with-assignment-operator + // Look for an augmenting path (a shortest path from the source to the sink) + while ($path = $this->find_shortest_path_bellf($source, $sink)) { // if the function returns null, the while will stop. + // Reverse the augmentin path, thereby distributing a user into a group + $this->augment_flow($path); + unset($path); // clear up old path + } + return $this->extract_allocation($touserid, $tochoiceid); + } + + /** + * Bellman-Ford acc. to Cormen + * + * @param $from index of starting node + * @param $to index of end node + * @return array with the of the nodes in the path + */ + private function find_shortest_path_bellf($from, $to) { + // Table of distances known so far + $dists = array(); + // Table of predecessors (used to reconstruct the shortest path later) + $preds = array(); + + // Number of nodes in the graph + $count = $this->graph['count']; + + // Step 1: initialize graph + for ($i = 0; $i < $count; $i++) { // for each vertex v in vertices: + if ($i == $from) {// if v is source then weight[v] := 0 + $dists[$i] = 0; + } else {// else weight[v] := infinity + $dists[$i] = INF; + } + $preds[$i] = null; // predecessor[v] := null + } + + // Step 2: relax edges repeatedly + for ($i = 0; $i < $count; $i++) { // for i from 1 to size(vertices)-1: + $updatedsomething = false; + foreach ($this->graph as $key => $edges) { // for each edge (u, v) with weight w in edges: + if (is_array($edges)) { + foreach ($edges as $key2 => $edge) { + /* @var $edge edge */ + if ($dists[$edge->from] + $edge->weight < $dists[$edge->to]) { // if weight[u] + w < weight[v]: + $dists[$edge->to] = $dists[$edge->from] + $edge->weight; // weight[v] := weight[u] + w + $preds[$edge->to] = $edge->from; // predecessor[v] := u + $updatedsomething = true; + } + } + } + } + if (!$updatedsomething) { + break; // leave + } + } + + // Step 3: check for negative-weight cycles + /*foreach ($graph as $key => $edges) { // for each edge (u, v) with weight w in edges: + if (is_array($edges)) { + foreach ($edges as $key2 => $edge) { + + if ($dists[$edge->to] + $edge->weight < $dists[$edge->to]) { // if weight[u] + w < weight[v]: + print_error('negative_cycle', 'ratingallocate'); + } + } + } + }*/ + + // If there is no path to $to, return null + if (is_null($preds[$to])) { + return null; + } + + // cleanup dists to save some space + unset($dists); + + // Use the preds table to reconstruct the shortest path + $path = array(); + $p = $to; + while ($p != $from) { + $path[] = $p; + $p = $preds[$p]; + } + $path[] = $from; + return $path; + } + + /** + * Extracts a distribution/allocation from the graph. + * + * @param $touserid a map mapping from indexes in the graph to userids + * @param $tochoiceid a map mapping from indexes in the graph to choiceids + * @return an array of the form array(groupid => array(userid, ...), ...) + */ + protected function extract_allocation($touserid, $tochoiceid) { + $distribution = array(); + foreach ($tochoiceid as $index => $groupid) { + $group = $this->graph[$index]; + $distribution[$groupid] = array(); + foreach ($group as $assignment) { + /* @var $assignment edge */ + $user = intval($assignment->to); + if (array_key_exists($user, $touserid)) { + $distribution[$groupid][] = $touserid[$user]; + } + } + } + return $distribution; + } + + /** + * Setup conversions between ids of users and choices to their node-ids in the graph + * @param type $usercount + * @param type $ratings + * @return array($fromuserid, $touserid, $fromchoiceid, $tochoiceid); + */ + public static function setup_id_conversions($usercount, $ratings) { + // These tables convert userids to their index in the graph + // The range is [1..$usercount] + $fromuserid = array(); + $touserid = array(); + // These tables convert choiceids to their index in the graph + // The range is [$usercount + 1 .. $usercount + $choicecount] + $fromchoiceid = array(); + $tochoiceid = array(); + + // User counter + $ui = 1; + // Group counter + $gi = $usercount + 1; + + // Fill the conversion tables for group and user ids + foreach ($ratings as $rating) { + if (!array_key_exists($rating->userid, $fromuserid)) { + $fromuserid[$rating->userid] = $ui; + $touserid[$ui] = $rating->userid; + $ui++; + } + if (!array_key_exists($rating->choiceid, $fromchoiceid)) { + $fromchoiceid[$rating->choiceid] = $gi; + $tochoiceid[$gi] = $rating->choiceid; + $gi++; + } + } + + return array($fromuserid, $touserid, $fromchoiceid, $tochoiceid); + } + + /** + * Sets up $this->graph + * @param type $choicecount + * @param type $usercount + * @param type $fromuserid + * @param type $fromchoiceid + * @param type $ratings + * @param type $choicedata + * @param type $source + * @param type $sink + */ + protected function setup_graph($choicecount, $usercount, $fromuserid, $fromchoiceid, $ratings, $choicedata, $source, $sink, $weightmult = 1) { + // Construct the datastructures for the algorithm + // A directed weighted bipartite graph. + // A source is connected to all users with unit cost. + // The users are connected to their choices with cost equal to their rating. + // The choices are connected to a sink with 0 cost + $this->graph = array(); + // Add source, sink and number of nodes to the graph + $this->graph[$source] = array(); + $this->graph[$sink] = array(); + $this->graph['count'] = $choicecount + $usercount + 2; + + // Add users and choices to the graph and connect them to the source and sink + foreach ($fromuserid as $id => $user) { + $this->graph[$user] = array(); + $this->graph[$source][] = new edge($source, $user, 0); + } + foreach ($fromchoiceid as $id => $choice) { + $this->graph[$choice] = array(); + $this->graph[$choice][] = new edge($choice, $sink, 0, $choicedata[$id]->maxsize); + } + + // Add the edges representing the ratings to the graph + foreach ($ratings as $id => $rating) { + $user = $fromuserid[$rating->userid]; + $choice = $fromchoiceid[$rating->choiceid]; + $weight = $rating->rating; + if ($weight > 0) { + $this->graph[$user][] = new edge($user, $choice, $weightmult * $weight); + } + } + } + + /** + * Augments the flow in the network, i.e. augments the overall 'satisfaction' + * by distributing users to choices + * Reverses all edges along $path in $graph + * @param type $path path from t to s + */ + protected function augment_flow($path) { + if (is_null($path) or count($path) < 2) { + print_error('invalid_path', 'ratingallocate'); + } + + // Walk along the path, from s to t + for ($i = count($path) - 1; $i > 0; $i--) { + $from = $path[$i]; + $to = $path[$i - 1]; + $edge = null; + $foundedgeid = -1; + // Find the edge + foreach ($this->graph[$from] as $index => &$edge) { + /* @var $edge edge */ + if ($edge->to == $to) { + $foundedgeid = $index; + break; + } + } + // The second to last node in a path has to be a choice-node. + // Reduce its space by one, because one user just got distributed into it. + if ($i == 1 and $edge->space > 1) { + $edge->space --; + } else { + // Remove the edge + array_splice($this->graph[$from], $foundedgeid, 1); + // Add a new edge in the opposite direction whose weight has an opposite sign + // array_push($this->graph[$to], new edge($to, $from, -1 * $edge->weight)); + // according to php doc, this is faster + $this->graph[$to][] = new edge($to, $from, -1 * $edge->weight); + } + } + } + +} diff --git a/algorithm/edmondskarp/classes/edge.php b/algorithm/edmondskarp/classes/edge.php new file mode 100644 index 00000000..f2ebe23b --- /dev/null +++ b/algorithm/edmondskarp/classes/edge.php @@ -0,0 +1,49 @@ +. + +/** + * + * Contains the algorithm for the distribution + * + * @package raalgo_edmondskarp + * @copyright 2014 M Schulze + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace raalgo_edmondskarp; +defined('MOODLE_INTERNAL') || die(); + +/** + * Represents an Edge in the graph to have fixed fields instead of array-fields + */ +class edge { + /** @var from int */ + public $from; + /** @var to int */ + public $to; + /** @var weight int Cost for this edge (rating of user) */ + public $weight; + /** @var space int (places left for choices) */ + public $space; + + public function __construct($from, $to, $weight, $space = 0) { + $this->from = $from; + $this->to = $to; + $this->weight = $weight; + $this->space = $space; + } + +} \ No newline at end of file diff --git a/algorithm/edmondskarp/lang/en/raalgo_edmondskarp.php b/algorithm/edmondskarp/lang/en/raalgo_edmondskarp.php new file mode 100644 index 00000000..6f5690c2 --- /dev/null +++ b/algorithm/edmondskarp/lang/en/raalgo_edmondskarp.php @@ -0,0 +1,25 @@ +. + + +/** + * English strings for raalgo_edmondskarp + * + * @package raalgo_edmondskarp + * @copyright 2019 J Dageförde, N Herrmann + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +$string['pluginname'] = 'Edmonds-Karp algorithm'; \ No newline at end of file diff --git a/algorithm/edmondskarp/version.php b/algorithm/edmondskarp/version.php new file mode 100644 index 00000000..1f3c5a5d --- /dev/null +++ b/algorithm/edmondskarp/version.php @@ -0,0 +1,30 @@ +. + + +/** + * Defines the version of ratingallocate + * + * @package raalgo_edmondskarp + * @copyright 2019 J Dageförde, N Herrmann + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$plugin->version = 2019031800; // The current module version (Date: YYYYMMDDXX) +$plugin->requires = 2017111300; // Requires this Moodle version +$plugin->component = 'raalgo_edmondskarp'; // To check on upgrade, that module sits in correct place diff --git a/classes/algorithm.php b/classes/algorithm.php index 2081dc79..fc20107f 100644 --- a/classes/algorithm.php +++ b/classes/algorithm.php @@ -22,6 +22,7 @@ abstract class algorithm { + public abstract function get_name(); protected abstract function compute_distribution($choicerecords, $ratings, $usercount); /** diff --git a/db/subplugins.php b/db/subplugins.php index 7552ff21..c63edbfb 100644 --- a/db/subplugins.php +++ b/db/subplugins.php @@ -14,6 +14,8 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . +defined('MOODLE_INTERNAL') || die(); + $subplugins = array( 'raalgo' => 'mod/ratingallocate/algorithm', ); diff --git a/version.php b/version.php index 2b5c59e8..59c7cb50 100644 --- a/version.php +++ b/version.php @@ -26,7 +26,7 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2018112900; // The current module version (Date: YYYYMMDDXX) +$plugin->version = 2019031800; // The current module version (Date: YYYYMMDDXX) $plugin->requires = 2017111300; // Requires this Moodle version $plugin->maturity = MATURITY_STABLE; $plugin->release = 'v3.6-r1'; From ec2314164ec39dc99634a3f260f147283848709b Mon Sep 17 00:00:00 2001 From: Justus Dieckmann Date: Mon, 18 Mar 2019 14:06:42 +0100 Subject: [PATCH 08/73] set previous min field in form --- form_modify_choice.php | 1 + locallib.php | 1 + 2 files changed, 2 insertions(+) diff --git a/form_modify_choice.php b/form_modify_choice.php index c85e84e1..964c7e17 100644 --- a/form_modify_choice.php +++ b/form_modify_choice.php @@ -107,6 +107,7 @@ public function definition() { if ($this->choice) { $mform->setDefault('title', $this->choice->title); $mform->setDefault('explanation', $this->choice->explanation); + $mform->setDefault('minsize', $this->choice->minsize); $mform->setDefault('maxsize', $this->choice->maxsize); $mform->setDefault('active', $this->choice->active); $mform->setDefault('choiceid', $this->choice->id); diff --git a/locallib.php b/locallib.php index 040b61a4..f34e50d4 100644 --- a/locallib.php +++ b/locallib.php @@ -1511,6 +1511,7 @@ public function is_setup_ok() { * @property string explanation * @property int $maxsize * @property bool $active + * @property int minsize */ class ratingallocate_choice { /** @var stdClass original db record */ From 3e6db5f27f7a661075f4c3a810e7b44747838da3 Mon Sep 17 00:00:00 2001 From: NinaHerrmann Date: Mon, 18 Mar 2019 14:11:35 +0100 Subject: [PATCH 09/73] changed reference in locallib to subplugin and coing style fixes in subplugin --- .../edmondskarp/classes/algorithm_impl.php | 109 +++++++++--------- locallib.php | 2 +- 2 files changed, 56 insertions(+), 55 deletions(-) diff --git a/algorithm/edmondskarp/classes/algorithm_impl.php b/algorithm/edmondskarp/classes/algorithm_impl.php index 2107ddc0..a95f6e40 100644 --- a/algorithm/edmondskarp/classes/algorithm_impl.php +++ b/algorithm/edmondskarp/classes/algorithm_impl.php @@ -22,6 +22,7 @@ * @copyright 2014 M Schulze * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +namespace raalgo_edmondskarp; defined('MOODLE_INTERNAL') || die(); class algorithm_impl extends \mod_ratingallocate\algorithm { @@ -37,7 +38,7 @@ public function compute_distribution($choicerecords, $ratings, $usercount) { } $choicecount = count($choicedata); - // Index of source and sink in the graph + // Index of source and sink in the graph. $source = 0; $sink = $choicecount + $usercount + 1; @@ -51,9 +52,9 @@ public function compute_distribution($choicerecords, $ratings, $usercount) { // http://stackoverflow.com/questions/6681075/while-loop-in-php-with-assignment-operator // Look for an augmenting path (a shortest path from the source to the sink) while ($path = $this->find_shortest_path_bellf($source, $sink)) { // if the function returns null, the while will stop. - // Reverse the augmentin path, thereby distributing a user into a group + // Reverse the augmentin path, thereby distributing a user into a group. $this->augment_flow($path); - unset($path); // clear up old path + unset($path); // Clear up old path. } return $this->extract_allocation($touserid, $tochoiceid); } @@ -66,46 +67,46 @@ public function compute_distribution($choicerecords, $ratings, $usercount) { * @return array with the of the nodes in the path */ private function find_shortest_path_bellf($from, $to) { - // Table of distances known so far + // Table of distances known so far. $dists = array(); - // Table of predecessors (used to reconstruct the shortest path later) + // Table of predecessors (used to reconstruct the shortest path later). $preds = array(); - // Number of nodes in the graph + // Number of nodes in the graph. $count = $this->graph['count']; - // Step 1: initialize graph - for ($i = 0; $i < $count; $i++) { // for each vertex v in vertices: - if ($i == $from) {// if v is source then weight[v] := 0 + // Step 1: initialize graph. + for ($i = 0; $i < $count; $i++) { // For each vertex v in vertices: + if ($i == $from) {// If v is source then weight[v] := 0. $dists[$i] = 0; - } else {// else weight[v] := infinity + } else {// Else weight[v] := infinity. $dists[$i] = INF; } - $preds[$i] = null; // predecessor[v] := null + $preds[$i] = null; // Predecessor[v] := null. } - // Step 2: relax edges repeatedly - for ($i = 0; $i < $count; $i++) { // for i from 1 to size(vertices)-1: + // Step 2: relax edges repeatedly. + for ($i = 0; $i < $count; $i++) { // For i from 1 to size(vertices)-1: $updatedsomething = false; - foreach ($this->graph as $key => $edges) { // for each edge (u, v) with weight w in edges: + foreach ($this->graph as $key => $edges) { // For each edge (u, v) with weight w in edges: if (is_array($edges)) { foreach ($edges as $key2 => $edge) { /* @var $edge edge */ - if ($dists[$edge->from] + $edge->weight < $dists[$edge->to]) { // if weight[u] + w < weight[v]: - $dists[$edge->to] = $dists[$edge->from] + $edge->weight; // weight[v] := weight[u] + w - $preds[$edge->to] = $edge->from; // predecessor[v] := u + if ($dists[$edge->from] + $edge->weight < $dists[$edge->to]) { // If weight[u] + w < weight[v]: + $dists[$edge->to] = $dists[$edge->from] + $edge->weight; // Weight[v] := weight[u] + w. + $preds[$edge->to] = $edge->from; // Predecessor[v] := u. $updatedsomething = true; } } } } if (!$updatedsomething) { - break; // leave + break; // Leave. } } - // Step 3: check for negative-weight cycles - /*foreach ($graph as $key => $edges) { // for each edge (u, v) with weight w in edges: + // Step 3: check for negative-weight cycles. + /*Foreach ($graph as $key => $edges) { // for each edge (u, v) with weight w in edges: if (is_array($edges)) { foreach ($edges as $key2 => $edge) { @@ -116,15 +117,15 @@ private function find_shortest_path_bellf($from, $to) { } }*/ - // If there is no path to $to, return null + // If there is no path to $to, return null. if (is_null($preds[$to])) { return null; } - // cleanup dists to save some space + // Cleanup dists to save some space. unset($dists); - // Use the preds table to reconstruct the shortest path + // Use the preds table to reconstruct the shortest path. $path = array(); $p = $to; while ($p != $from) { @@ -140,7 +141,7 @@ private function find_shortest_path_bellf($from, $to) { * * @param $touserid a map mapping from indexes in the graph to userids * @param $tochoiceid a map mapping from indexes in the graph to choiceids - * @return an array of the form array(groupid => array(userid, ...), ...) + * @return array of the form array(groupid => array(userid, ...), ...) */ protected function extract_allocation($touserid, $tochoiceid) { $distribution = array(); @@ -160,26 +161,26 @@ protected function extract_allocation($touserid, $tochoiceid) { /** * Setup conversions between ids of users and choices to their node-ids in the graph - * @param type $usercount - * @param type $ratings + * @param $usercount + * @param $ratings * @return array($fromuserid, $touserid, $fromchoiceid, $tochoiceid); */ public static function setup_id_conversions($usercount, $ratings) { - // These tables convert userids to their index in the graph - // The range is [1..$usercount] + // These tables convert userids to their index in the graph. + // The range is [1..$usercount]. $fromuserid = array(); $touserid = array(); - // These tables convert choiceids to their index in the graph - // The range is [$usercount + 1 .. $usercount + $choicecount] + // These tables convert choiceids to their index in the graph. + // The range is [$usercount + 1 .. $usercount + $choicecount]. $fromchoiceid = array(); $tochoiceid = array(); - // User counter + // User counter. $ui = 1; - // Group counter + // Group counter. $gi = $usercount + 1; - // Fill the conversion tables for group and user ids + // Fill the conversion tables for group and user ids. foreach ($ratings as $rating) { if (!array_key_exists($rating->userid, $fromuserid)) { $fromuserid[$rating->userid] = $ui; @@ -198,28 +199,28 @@ public static function setup_id_conversions($usercount, $ratings) { /** * Sets up $this->graph - * @param type $choicecount - * @param type $usercount - * @param type $fromuserid - * @param type $fromchoiceid - * @param type $ratings - * @param type $choicedata - * @param type $source - * @param type $sink + * @param $choicecount + * @param $usercount + * @param $fromuserid + * @param $fromchoiceid + * @param $ratings + * @param $choicedata + * @param $source + * @param $sink */ protected function setup_graph($choicecount, $usercount, $fromuserid, $fromchoiceid, $ratings, $choicedata, $source, $sink, $weightmult = 1) { - // Construct the datastructures for the algorithm + // Construct the datastructures for the algorithm. // A directed weighted bipartite graph. // A source is connected to all users with unit cost. // The users are connected to their choices with cost equal to their rating. - // The choices are connected to a sink with 0 cost + // The choices are connected to a sink with 0 cost. $this->graph = array(); - // Add source, sink and number of nodes to the graph + // Add source, sink and number of nodes to the graph. $this->graph[$source] = array(); $this->graph[$sink] = array(); $this->graph['count'] = $choicecount + $usercount + 2; - // Add users and choices to the graph and connect them to the source and sink + // Add users and choices to the graph and connect them to the source and sink. foreach ($fromuserid as $id => $user) { $this->graph[$user] = array(); $this->graph[$source][] = new edge($source, $user, 0); @@ -229,7 +230,7 @@ protected function setup_graph($choicecount, $usercount, $fromuserid, $fromchoic $this->graph[$choice][] = new edge($choice, $sink, 0, $choicedata[$id]->maxsize); } - // Add the edges representing the ratings to the graph + // Add the edges representing the ratings to the graph. foreach ($ratings as $id => $rating) { $user = $fromuserid[$rating->userid]; $choice = $fromchoiceid[$rating->choiceid]; @@ -244,20 +245,20 @@ protected function setup_graph($choicecount, $usercount, $fromuserid, $fromchoic * Augments the flow in the network, i.e. augments the overall 'satisfaction' * by distributing users to choices * Reverses all edges along $path in $graph - * @param type $path path from t to s + * @param $path path from t to s */ protected function augment_flow($path) { if (is_null($path) or count($path) < 2) { print_error('invalid_path', 'ratingallocate'); } - // Walk along the path, from s to t + // Walk along the path, from s to t. for ($i = count($path) - 1; $i > 0; $i--) { $from = $path[$i]; $to = $path[$i - 1]; $edge = null; $foundedgeid = -1; - // Find the edge + // Find the edge. foreach ($this->graph[$from] as $index => &$edge) { /* @var $edge edge */ if ($edge->to == $to) { @@ -270,12 +271,12 @@ protected function augment_flow($path) { if ($i == 1 and $edge->space > 1) { $edge->space --; } else { - // Remove the edge + // Remove the edge. array_splice($this->graph[$from], $foundedgeid, 1); - // Add a new edge in the opposite direction whose weight has an opposite sign - // array_push($this->graph[$to], new edge($to, $from, -1 * $edge->weight)); - // according to php doc, this is faster - $this->graph[$to][] = new edge($to, $from, -1 * $edge->weight); + // Add a new edge in the opposite direction whose weight has an opposite sign. + // Array_push($this->graph[$to], new edge($to, $from, -1 * $edge->weight)); + // According to php doc, this is faster. + $this->graph[$to][] = new edge($to, $from, -1 * $edge->weight); } } } diff --git a/locallib.php b/locallib.php index 040b61a4..50f88dd7 100644 --- a/locallib.php +++ b/locallib.php @@ -882,7 +882,7 @@ public function distrubute_choices() { $this->origdbrecord->algorithmstarttime = time(); $this->db->update_record(this_db\ratingallocate::TABLE, $this->origdbrecord); - $distributor = new solver_edmonds_karp(); + $distributor = new raalgo_edmondskarp\algorithm_impl(); // $distributor = new solver_ford_fulkerson(); $timestart = microtime(true); $distributor->distribute_users($this); From 53425c56bab6bc7cf7a1e4f24ce648852c0baa96 Mon Sep 17 00:00:00 2001 From: NinaHerrmann Date: Mon, 18 Mar 2019 14:13:43 +0100 Subject: [PATCH 10/73] coding style fixes in subplugin edmondskarp --- algorithm/edmondskarp/classes/algorithm_impl.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/algorithm/edmondskarp/classes/algorithm_impl.php b/algorithm/edmondskarp/classes/algorithm_impl.php index a95f6e40..21d24e5f 100644 --- a/algorithm/edmondskarp/classes/algorithm_impl.php +++ b/algorithm/edmondskarp/classes/algorithm_impl.php @@ -86,13 +86,13 @@ private function find_shortest_path_bellf($from, $to) { } // Step 2: relax edges repeatedly. - for ($i = 0; $i < $count; $i++) { // For i from 1 to size(vertices)-1: + for ($i = 0; $i < $count; $i++) { // For i from 1 to size(vertices)-1. $updatedsomething = false; - foreach ($this->graph as $key => $edges) { // For each edge (u, v) with weight w in edges: + foreach ($this->graph as $key => $edges) { // For each edge (u, v) with weight w in edges. if (is_array($edges)) { foreach ($edges as $key2 => $edge) { /* @var $edge edge */ - if ($dists[$edge->from] + $edge->weight < $dists[$edge->to]) { // If weight[u] + w < weight[v]: + if ($dists[$edge->from] + $edge->weight < $dists[$edge->to]) { // If weight[u] + w < weight[v]. $dists[$edge->to] = $dists[$edge->from] + $edge->weight; // Weight[v] := weight[u] + w. $preds[$edge->to] = $edge->from; // Predecessor[v] := u. $updatedsomething = true; @@ -106,7 +106,7 @@ private function find_shortest_path_bellf($from, $to) { } // Step 3: check for negative-weight cycles. - /*Foreach ($graph as $key => $edges) { // for each edge (u, v) with weight w in edges: + /* Foreach ($graph as $key => $edges) { // for each edge (u, v) with weight w in edges: if (is_array($edges)) { foreach ($edges as $key2 => $edge) { @@ -208,7 +208,8 @@ public static function setup_id_conversions($usercount, $ratings) { * @param $source * @param $sink */ - protected function setup_graph($choicecount, $usercount, $fromuserid, $fromchoiceid, $ratings, $choicedata, $source, $sink, $weightmult = 1) { + protected function setup_graph($choicecount, $usercount, $fromuserid, $fromchoiceid, $ratings, $choicedata, $source, + $sink, $weightmult = 1) { // Construct the datastructures for the algorithm. // A directed weighted bipartite graph. // A source is connected to all users with unit cost. From 23bfd6644e28422a5457e7fd8827922f3f8eaae6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Dagef=C3=B6rde?= Date: Mon, 18 Mar 2019 14:47:49 +0100 Subject: [PATCH 11/73] use subplugin for test --- solver/edmonds-karp.php | 142 ------------- solver/solver-template.php | 260 ----------------------- tests/mod_ratingallocate_solver_test.php | 9 +- 3 files changed, 4 insertions(+), 407 deletions(-) delete mode 100644 solver/edmonds-karp.php delete mode 100644 solver/solver-template.php diff --git a/solver/edmonds-karp.php b/solver/edmonds-karp.php deleted file mode 100644 index 746e068a..00000000 --- a/solver/edmonds-karp.php +++ /dev/null @@ -1,142 +0,0 @@ -. - -/** - * - * Contains the algorithm for the distribution - * - * @package mod_ratingallocate - * @subpackage mod_ratingallocate - * @copyright 2014 M Schulze - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -defined('MOODLE_INTERNAL') || die(); - -require_once(dirname(__FILE__) . '/../locallib.php'); -require_once(dirname(__FILE__) . '/solver-template.php'); - -class solver_edmonds_karp extends distributor { - - public function get_name() { - return 'edmonds_karp'; - } - - public function compute_distribution($choicerecords, $ratings, $usercount) { - $choicedata = array(); - foreach ($choicerecords as $record) { - $choicedata[$record->id] = $record; - } - - $choicecount = count($choicedata); - // Index of source and sink in the graph - $source = 0; - $sink = $choicecount + $usercount + 1; - - list($fromuserid, $touserid, $fromchoiceid, $tochoiceid) = $this->setup_id_conversions($usercount, $ratings); - - $this->setup_graph($choicecount, $usercount, $fromuserid, $fromchoiceid, $ratings, $choicedata, $source, $sink, -1); - - // Now that the datastructure is complete, we can start the algorithm - // This is an adaptation of the Ford-Fulkerson algorithm - // with Bellman-Ford as search function (see: Edmonds-Karp in Introduction to Algorithms) - // http://stackoverflow.com/questions/6681075/while-loop-in-php-with-assignment-operator - // Look for an augmenting path (a shortest path from the source to the sink) - while ($path = $this->find_shortest_path_bellf($source, $sink)) { // if the function returns null, the while will stop. - // Reverse the augmentin path, thereby distributing a user into a group - $this->augment_flow($path); - unset($path); // clear up old path - } - return $this->extract_allocation($touserid, $tochoiceid); - } - - /** - * Bellman-Ford acc. to Cormen - * - * @param $from index of starting node - * @param $to index of end node - * @return array with the of the nodes in the path - */ - private function find_shortest_path_bellf($from, $to) { - // Table of distances known so far - $dists = array(); - // Table of predecessors (used to reconstruct the shortest path later) - $preds = array(); - - // Number of nodes in the graph - $count = $this->graph['count']; - - // Step 1: initialize graph - for ($i = 0; $i < $count; $i++) { // for each vertex v in vertices: - if ($i == $from) {// if v is source then weight[v] := 0 - $dists[$i] = 0; - } else {// else weight[v] := infinity - $dists[$i] = INF; - } - $preds[$i] = null; // predecessor[v] := null - } - - // Step 2: relax edges repeatedly - for ($i = 0; $i < $count; $i++) { // for i from 1 to size(vertices)-1: - $updatedsomething = false; - foreach ($this->graph as $key => $edges) { // for each edge (u, v) with weight w in edges: - if (is_array($edges)) { - foreach ($edges as $key2 => $edge) { - /* @var $edge edge */ - if ($dists[$edge->from] + $edge->weight < $dists[$edge->to]) { // if weight[u] + w < weight[v]: - $dists[$edge->to] = $dists[$edge->from] + $edge->weight; // weight[v] := weight[u] + w - $preds[$edge->to] = $edge->from; // predecessor[v] := u - $updatedsomething = true; - } - } - } - } - if (!$updatedsomething) { - break; // leave - } - } - - // Step 3: check for negative-weight cycles - /*foreach ($graph as $key => $edges) { // for each edge (u, v) with weight w in edges: - if (is_array($edges)) { - foreach ($edges as $key2 => $edge) { - - if ($dists[$edge->to] + $edge->weight < $dists[$edge->to]) { // if weight[u] + w < weight[v]: - print_error('negative_cycle', 'ratingallocate'); - } - } - } - }*/ - - // If there is no path to $to, return null - if (is_null($preds[$to])) { - return null; - } - - // cleanup dists to save some space - unset($dists); - - // Use the preds table to reconstruct the shortest path - $path = array(); - $p = $to; - while ($p != $from) { - $path[] = $p; - $p = $preds[$p]; - } - $path[] = $from; - return $path; - } - -} diff --git a/solver/solver-template.php b/solver/solver-template.php deleted file mode 100644 index ddbab4c7..00000000 --- a/solver/solver-template.php +++ /dev/null @@ -1,260 +0,0 @@ -. - -/** - * Internal library of functions for module groupdistribution. - * - * Contains the algorithm for the group distribution and some helper functions - * that wrap useful SQL querys. - * - * @package mod_ratingallocate - * @subpackage mod_ratingallocate - * @copyright 2014 M Schulze, C Usener - * @copyright based on code by Stefan Koegel copyright (C) 2013 Stefan Koegel - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -/** - * Represents an Edge in the graph to have fixed fields instead of array-fields - */ -class edge { - /** @var from int */ - public $from; - /** @var to int */ - public $to; - /** @var weight int Cost for this edge (rating of user) */ - public $weight; - /** @var space int (places left for choices) */ - public $space; - - public function __construct($from, $to, $weight, $space = 0) { - $this->from = $from; - $this->to = $to; - $this->weight = $weight; - $this->space = $space; - } - -} - -/** - * Template Class for distribution algorithms - */ -class distributor { - - /** @var $graph Flow-Graph built */ - protected $graph; - - /** - * Compute the 'satisfaction' functions that is to be maximized by adding the - * ratings users gave to their allocated choices - * @param array $ratings - * @param array $distribution - * @return integer - */ - public static function compute_target_function($ratings, $distribution) { - $functionvalue = 0; - foreach ($distribution as $choiceid => $choice) { - // $choice ist jetzt ein array von userids - foreach ($choice as $userid) { - // jetzt das richtige Rating rausfinden - foreach ($ratings as $rating) { - if ($rating->userid == $userid && $rating->choiceid == $choiceid) { - $functionvalue += $rating->rating; - continue; // aus der Such-Schleife raus und weitermachen - } - } - } - } - return $functionvalue; - } - - /** - * Entry-point for the \ratingallocate object to call a solver - * @param \ratingallocate $ratingallocate - */ - public function distribute_users(\ratingallocate $ratingallocate) { - // Extend PHP time limit -// core_php_time_limit::raise(); - - // Load data from database - $choicerecords = $ratingallocate->get_rateable_choices(); - $ratings = $ratingallocate->get_ratings_for_rateable_choices(); - - // Randomize the order of the enrties to prevent advantages for early entry - shuffle($ratings); - - $usercount = count($ratingallocate->get_raters_in_course()); - - $distributions = $this->compute_distribution($choicerecords, $ratings, $usercount); - - $transaction = $ratingallocate->db->start_delegated_transaction(); // perform all allocation manipulation / inserts in one transaction - - $ratingallocate->clear_all_allocations(); - - foreach ($distributions as $choiceid => $users) { - foreach ($users as $userid) { - $ratingallocate->add_allocation($choiceid, $userid, $ratingallocate->ratingallocate->id); - } - } - $transaction->allow_commit(); - } - - /** - * Extracts a distribution/allocation from the graph. - * - * @param $touserid a map mapping from indexes in the graph to userids - * @param $tochoiceid a map mapping from indexes in the graph to choiceids - * @return an array of the form array(groupid => array(userid, ...), ...) - */ - protected function extract_allocation($touserid, $tochoiceid) { - $distribution = array(); - foreach ($tochoiceid as $index => $groupid) { - $group = $this->graph[$index]; - $distribution[$groupid] = array(); - foreach ($group as $assignment) { - /* @var $assignment edge */ - $user = intval($assignment->to); - if (array_key_exists($user, $touserid)) { - $distribution[$groupid][] = $touserid[$user]; - } - } - } - return $distribution; - } - - /** - * Setup conversions between ids of users and choices to their node-ids in the graph - * @param type $usercount - * @param type $ratings - * @return array($fromuserid, $touserid, $fromchoiceid, $tochoiceid); - */ - public static function setup_id_conversions($usercount, $ratings) { - // These tables convert userids to their index in the graph - // The range is [1..$usercount] - $fromuserid = array(); - $touserid = array(); - // These tables convert choiceids to their index in the graph - // The range is [$usercount + 1 .. $usercount + $choicecount] - $fromchoiceid = array(); - $tochoiceid = array(); - - // User counter - $ui = 1; - // Group counter - $gi = $usercount + 1; - - // Fill the conversion tables for group and user ids - foreach ($ratings as $rating) { - if (!array_key_exists($rating->userid, $fromuserid)) { - $fromuserid[$rating->userid] = $ui; - $touserid[$ui] = $rating->userid; - $ui++; - } - if (!array_key_exists($rating->choiceid, $fromchoiceid)) { - $fromchoiceid[$rating->choiceid] = $gi; - $tochoiceid[$gi] = $rating->choiceid; - $gi++; - } - } - - return array($fromuserid, $touserid, $fromchoiceid, $tochoiceid); - } - - /** - * Sets up $this->graph - * @param type $choicecount - * @param type $usercount - * @param type $fromuserid - * @param type $fromchoiceid - * @param type $ratings - * @param type $choicedata - * @param type $source - * @param type $sink - */ - protected function setup_graph($choicecount, $usercount, $fromuserid, $fromchoiceid, $ratings, $choicedata, $source, $sink, $weightmult = 1) { - // Construct the datastructures for the algorithm - // A directed weighted bipartite graph. - // A source is connected to all users with unit cost. - // The users are connected to their choices with cost equal to their rating. - // The choices are connected to a sink with 0 cost - $this->graph = array(); - // Add source, sink and number of nodes to the graph - $this->graph[$source] = array(); - $this->graph[$sink] = array(); - $this->graph['count'] = $choicecount + $usercount + 2; - - // Add users and choices to the graph and connect them to the source and sink - foreach ($fromuserid as $id => $user) { - $this->graph[$user] = array(); - $this->graph[$source][] = new edge($source, $user, 0); - } - foreach ($fromchoiceid as $id => $choice) { - $this->graph[$choice] = array(); - $this->graph[$choice][] = new edge($choice, $sink, 0, $choicedata[$id]->maxsize); - } - - // Add the edges representing the ratings to the graph - foreach ($ratings as $id => $rating) { - $user = $fromuserid[$rating->userid]; - $choice = $fromchoiceid[$rating->choiceid]; - $weight = $rating->rating; - if ($weight > 0) { - $this->graph[$user][] = new edge($user, $choice, $weightmult * $weight); - } - } - } - - /** - * Augments the flow in the network, i.e. augments the overall 'satisfaction' - * by distributing users to choices - * Reverses all edges along $path in $graph - * @param type $path path from t to s - */ - protected function augment_flow($path) { - if (is_null($path) or count($path) < 2) { - print_error('invalid_path', 'ratingallocate'); - } - - // Walk along the path, from s to t - for ($i = count($path) - 1; $i > 0; $i--) { - $from = $path[$i]; - $to = $path[$i - 1]; - $edge = null; - $foundedgeid = -1; - // Find the edge - foreach ($this->graph[$from] as $index => &$edge) { - /* @var $edge edge */ - if ($edge->to == $to) { - $foundedgeid = $index; - break; - } - } - // The second to last node in a path has to be a choice-node. - // Reduce its space by one, because one user just got distributed into it. - if ($i == 1 and $edge->space > 1) { - $edge->space --; - } else { - // Remove the edge - array_splice($this->graph[$from], $foundedgeid, 1); - // Add a new edge in the opposite direction whose weight has an opposite sign - // array_push($this->graph[$to], new edge($to, $from, -1 * $edge->weight)); - // according to php doc, this is faster - $this->graph[$to][] = new edge($to, $from, -1 * $edge->weight); - } - } - } - -} diff --git a/tests/mod_ratingallocate_solver_test.php b/tests/mod_ratingallocate_solver_test.php index 1413ea82..131bfb12 100644 --- a/tests/mod_ratingallocate_solver_test.php +++ b/tests/mod_ratingallocate_solver_test.php @@ -28,7 +28,6 @@ global $CFG; require_once($CFG->dirroot . '/mod/ratingallocate/locallib.php'); -require_once($CFG->dirroot . '/mod/ratingallocate/solver/edmonds-karp.php'); require_once($CFG->dirroot . '/mod/ratingallocate/solver/ford-fulkerson-koegel.php'); class edmonds_karp_test extends basic_testcase { @@ -73,7 +72,7 @@ private function perform_race($groupsnum, $ratersnum) { $usercount = $ratersnum; - $solvers = array('solver_edmonds_karp', 'solver_ford_fulkerson'); + $solvers = array('\raalgo_edmondskarp\algorithm_impl', 'solver_ford_fulkerson'); foreach ($solvers as $solver) { $solver1 = new $solver; $timestart = microtime(true); @@ -188,7 +187,7 @@ public function test_edmondskarp() { $usercount = 5; - $solver = new solver_edmonds_karp(); + $solver = new \raalgo_edmondskarp\algorithm_impl(); $distribution = $solver->compute_distribution($choices, $ratings, $usercount); $expected = array(1 => array(2, 5), 2 => array(4, 1)); // echo "gesamtpunktzahl: " . $solver->compute_target_function($choices, $ratings, $distribution); @@ -236,7 +235,7 @@ public function test_negweightcycle() { $usercount = 2; - $solver = new solver_edmonds_karp(); + $solver = new \raalgo_edmondskarp\algorithm_impl(); $distribution = $solver->compute_distribution($choices, $ratings, $usercount); $this->assertEquals($solver::compute_target_function($ratings, $distribution), 10); @@ -300,7 +299,7 @@ public function test_setupids() { $ratings[4]->rating = 2; $usercount = 2; - list($fromuserid, $touserid, $fromchoiceid, $tochoiceid) = solver_edmonds_karp::setup_id_conversions($usercount, $ratings); + list($fromuserid, $touserid, $fromchoiceid, $tochoiceid) = \raalgo_edmondskarp\algorithm_impl::setup_id_conversions($usercount, $ratings); $this->assertEquals(array(3 => 1, 2 => 2), $fromuserid); $this->assertEquals(array(1 => 3, 2 => 2), $touserid); From 8c1e0f6abde605855877c6961ba7c4fe7a832aa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Dagef=C3=B6rde?= Date: Mon, 18 Mar 2019 14:48:29 +0100 Subject: [PATCH 12/73] cleanup imports and fields --- algorithm/edmondskarp/classes/algorithm_impl.php | 3 +++ solver/ford-fulkerson-koegel.php | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/algorithm/edmondskarp/classes/algorithm_impl.php b/algorithm/edmondskarp/classes/algorithm_impl.php index 21d24e5f..6a0fc211 100644 --- a/algorithm/edmondskarp/classes/algorithm_impl.php +++ b/algorithm/edmondskarp/classes/algorithm_impl.php @@ -27,6 +27,9 @@ class algorithm_impl extends \mod_ratingallocate\algorithm { + /** @var $graph Flow-Graph built */ + protected $graph; + public function get_name() { return 'edmonds_karp'; } diff --git a/solver/ford-fulkerson-koegel.php b/solver/ford-fulkerson-koegel.php index 69ef6820..37a7e6b2 100644 --- a/solver/ford-fulkerson-koegel.php +++ b/solver/ford-fulkerson-koegel.php @@ -28,7 +28,6 @@ defined('MOODLE_INTERNAL') || die(); require_once(dirname(__FILE__) . '/../locallib.php'); -require_once(dirname(__FILE__) . '/solver-template.php'); class solver_ford_fulkerson extends distributor { From 710f9eec4775dc7f95efafff77ef279d2df6f5a8 Mon Sep 17 00:00:00 2001 From: Justus Dieckmann Date: Mon, 18 Mar 2019 14:58:29 +0100 Subject: [PATCH 13/73] insert min number of participants into list of all choices table. --- db/db_structure.php | 1 + lang/en/ratingallocate.php | 1 + renderer.php | 25 ++++++++++++++----------- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/db/db_structure.php b/db/db_structure.php index 2e5b30d1..fe6a7eca 100644 --- a/db/db_structure.php +++ b/db/db_structure.php @@ -49,6 +49,7 @@ class ratingallocate_choices { const RATINGALLOCATEID = 'ratingallocateid'; const TITLE = 'title'; const EXPLANATION = 'explanation'; + const MINSIZE = 'minsize'; const MAXSIZE = 'maxsize'; const ACTIVE = 'active'; } diff --git a/lang/en/ratingallocate.php b/lang/en/ratingallocate.php index 5722192e..b30b22f1 100644 --- a/lang/en/ratingallocate.php +++ b/lang/en/ratingallocate.php @@ -229,6 +229,7 @@ $string['newchoice'] = 'Add new choice'; $string['choice_table_title'] = 'Title'; $string['choice_table_explanation'] = 'Description'; +$string['choice_table_minsize'] = 'Min. Size'; $string['choice_table_maxsize'] = 'Max. Size'; $string['choice_table_active'] = 'Active'; $string['choice_table_tools'] = 'Edit'; diff --git a/renderer.php b/renderer.php index 14d6c480..c9097370 100644 --- a/renderer.php +++ b/renderer.php @@ -424,20 +424,22 @@ public function ratingallocate_show_choices_table(ratingallocate $ratingallocate echo $OUTPUT->single_button($starturl->out(), get_string('newchoice', 'mod_ratingallocate'), 'get'); $table = new \flexible_table('show_ratingallocate_options'); $table->define_baseurl($PAGE->url); - if ($choicesmodifiably) { - $table->define_columns(array('title', 'explanation', 'maxsize', 'active', 'tools')); - $table->define_headers(array(get_string('choice_table_title', 'mod_ratingallocate'), - get_string('choice_table_explanation', 'mod_ratingallocate'), - get_string('choice_table_maxsize', 'mod_ratingallocate'), - get_string('choice_table_active', 'mod_ratingallocate'), - get_string('choice_table_tools', 'mod_ratingallocate'))); - } else { - $table->define_columns(array('title', 'explanation', 'maxsize', 'active')); - $table->define_headers(array(get_string('choice_table_title', 'mod_ratingallocate'), + + $columns = array('title', 'explanation', 'minsize', 'maxsize', 'active'); + $headers = array(get_string('choice_table_title', 'mod_ratingallocate'), get_string('choice_table_explanation', 'mod_ratingallocate'), + get_string('choice_table_minsize', 'mod_ratingallocate'), get_string('choice_table_maxsize', 'mod_ratingallocate'), - get_string('choice_table_tools', 'mod_ratingallocate'))); + get_string('choice_table_tools', 'mod_ratingallocate')); + if ($choicesmodifiably) { + array_push($columns, 'tools'); + // Pushes the active header into headers table at index 3. + $headers = array_merge(array_splice($headers, 0, 5, false), + array(get_string('choice_table_active', 'mod_ratingallocate')), + array_splice($headers, 3, 5, false)); } + $table->define_columns($columns); + $table->define_headers($headers); $table->set_attribute('id', 'mod_ratingallocateshowoptions'); $table->set_attribute('class', 'admintable generaltable'); $table->setup(); @@ -453,6 +455,7 @@ public function ratingallocate_show_choices_table(ratingallocate $ratingallocate $class = ''; $row[] = $choice->{this_db\ratingallocate_choices::TITLE}; $row[] = $choice->{this_db\ratingallocate_choices::EXPLANATION}; + $row[] = $choice->{this_db\ratingallocate_choices::MINSIZE}; $row[] = $choice->{this_db\ratingallocate_choices::MAXSIZE}; if ($choice->{this_db\ratingallocate_choices::ACTIVE}) { $row[] = get_string('yes'); From 52512395efdb660f5099364a82e6827fcfd3ac1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Dagef=C3=B6rde?= Date: Mon, 18 Mar 2019 15:10:19 +0100 Subject: [PATCH 14/73] load algorithm dynamically --- classes/algorithm.php | 36 ++++++++++++++++++++++++++++++++++++ locallib.php | 6 +----- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/classes/algorithm.php b/classes/algorithm.php index fc20107f..3652e40d 100644 --- a/classes/algorithm.php +++ b/classes/algorithm.php @@ -54,4 +54,40 @@ public function distribute_users(\ratingallocate $ratingallocate) { } $transaction->allow_commit(); } + + /** + * Compute the 'satisfaction' functions that is to be maximized by adding the + * ratings users gave to their allocated choices + * @param array $ratings + * @param array $distribution + * @return integer + */ + public static function compute_target_function($ratings, $distribution) { + $functionvalue = 0; + foreach ($distribution as $choiceid => $choice) { + // $choice ist jetzt ein array von userids + foreach ($choice as $userid) { + // jetzt das richtige Rating rausfinden + foreach ($ratings as $rating) { + if ($rating->userid == $userid && $rating->choiceid == $choiceid) { + $functionvalue += $rating->rating; + continue; // aus der Such-Schleife raus und weitermachen + } + } + + } + } + return $functionvalue; + } + + /** + * @param string $name Subplugin name without 'raalgo_'-prefix. + * @return algorithm Algorithm instance + */ + public static function get_instance(string $name) { + $subplugins = \core_plugin_manager::get_subplugins_of_plugin('mod_ratingallocate'); + // TODO Check whether the specified plugin is installed. + $classname = '\raalgo_'.$name.'\algorithm_impl'; + return new $classname(); + } } \ No newline at end of file diff --git a/locallib.php b/locallib.php index 50f88dd7..7dfafceb 100644 --- a/locallib.php +++ b/locallib.php @@ -37,10 +37,6 @@ require_once($CFG->dirroot.'/group/lib.php'); require_once(__DIR__.'/classes/algorithm_status.php'); -// Takes care of loading all the solvers. -require_once(dirname(__FILE__) . '/solver/ford-fulkerson-koegel.php'); -require_once(dirname(__FILE__) . '/solver/edmonds-karp.php'); - // Now come all the strategies. require_once(dirname(__FILE__) . '/strategy/strategy01_yes_no.php'); require_once(dirname(__FILE__) . '/strategy/strategy02_yes_maybe_no.php'); @@ -882,7 +878,7 @@ public function distrubute_choices() { $this->origdbrecord->algorithmstarttime = time(); $this->db->update_record(this_db\ratingallocate::TABLE, $this->origdbrecord); - $distributor = new raalgo_edmondskarp\algorithm_impl(); + $distributor = \mod_ratingallocate\algorithm::get_instance('edmondskarp'); // $distributor = new solver_ford_fulkerson(); $timestart = microtime(true); $distributor->distribute_users($this); From 2170ad8ba9ab9cd75a8d6067e27c6dc649171a1a Mon Sep 17 00:00:00 2001 From: Justus Dieckmann Date: Mon, 18 Mar 2019 15:51:06 +0100 Subject: [PATCH 15/73] added minsize to test --- tests/generator/lib.php | 6 ++++-- tests/mod_generator_test.php | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/generator/lib.php b/tests/generator/lib.php index 2a610484..7df181d2 100644 --- a/tests/generator/lib.php +++ b/tests/generator/lib.php @@ -90,11 +90,13 @@ public static function get_default_choice_data() { array('title' => 'Choice 1', 'explanation' => 'Some explanatory text for choice 1', 'maxsize' => '10', - 'active' => true), + 'active' => true, + 'minsize' => '0'), array('title' => 'Choice 2', 'explanation' => 'Some explanatory text for choice 2', 'maxsize' => '5', - 'active' => false + 'active' => false, + 'minsize' => '0' ) ); } diff --git a/tests/mod_generator_test.php b/tests/mod_generator_test.php index ed734793..cf8538a9 100644 --- a/tests/mod_generator_test.php +++ b/tests/mod_generator_test.php @@ -85,6 +85,7 @@ public function test_create_instance() { 'ratingallocateid' => $mod->id, 'explanation' => 'Some explanatory text for choice 1', 'maxsize' => '10', + 'minsize' => '0', 'active' => '1' ), $choice_ids[1] => (object) array( @@ -93,6 +94,7 @@ public function test_create_instance() { 'ratingallocateid' => $mod->id, 'explanation' => 'Some explanatory text for choice 2', 'maxsize' => '5', + 'minsize' => '0', 'active' => '0' ) ); From 425e50f8a263f80299cfe39bcb3972dbd4e8110e Mon Sep 17 00:00:00 2001 From: Justus Dieckmann Date: Mon, 18 Mar 2019 16:20:04 +0100 Subject: [PATCH 16/73] added optional setting --- db/install.xml | 1 + db/upgrade.php | 11 +++++++++-- form_modify_choice.php | 7 ++++++- lang/en/ratingallocate.php | 3 +++ locallib.php | 1 + renderer.php | 14 +++++++------- version.php | 2 +- 7 files changed, 28 insertions(+), 11 deletions(-) diff --git a/db/install.xml b/db/install.xml index e92ab706..e7a3795c 100644 --- a/db/install.xml +++ b/db/install.xml @@ -40,6 +40,7 @@ + diff --git a/db/upgrade.php b/db/upgrade.php index 37848871..b1638bb0 100644 --- a/db/upgrade.php +++ b/db/upgrade.php @@ -132,7 +132,7 @@ function xmldb_ratingallocate_upgrade($oldversion) { } - if ($oldversion < 2019031802) { + if ($oldversion < 2019031803) { // Define field minsize to be added to ratingallocate_choices. $table = new xmldb_table('ratingallocate_choices'); @@ -143,8 +143,15 @@ function xmldb_ratingallocate_upgrade($oldversion) { $dbman->add_field($table, $field); } + $field = new xmldb_field('optional', XMLDB_TYPE_INTEGER, '4', null, null, null, '0', 'active'); + + // Conditionally launch add field optional. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + // Ratingallocate savepoint reached. - upgrade_mod_savepoint(true, 2019031802, 'ratingallocate'); + upgrade_mod_savepoint(true, 2019031803, 'ratingallocate'); } return true; diff --git a/form_modify_choice.php b/form_modify_choice.php index 964c7e17..2e693d3d 100644 --- a/form_modify_choice.php +++ b/form_modify_choice.php @@ -98,12 +98,16 @@ public function definition() { $mform->addRule(array("minsize", "maxsize"), get_string('err_gte', 'ratingallocate'), "compare", "lte", "server"); } - $elementname = 'active'; $mform->addElement('advcheckbox', $elementname, get_string('choice_active', ratingallocate_MOD_NAME), null, null, array(0, 1)); $mform->addHelpButton($elementname, 'choice_active', ratingallocate_MOD_NAME); + $elementname = 'optional'; + $mform->addElement('advcheckbox', $elementname, get_string('choice_optional', ratingallocate_MOD_NAME), + null, null, array(0, 1)); + $mform->addHelpButton($elementname, 'choice_optional', ratingallocate_MOD_NAME); + if ($this->choice) { $mform->setDefault('title', $this->choice->title); $mform->setDefault('explanation', $this->choice->explanation); @@ -111,6 +115,7 @@ public function definition() { $mform->setDefault('maxsize', $this->choice->maxsize); $mform->setDefault('active', $this->choice->active); $mform->setDefault('choiceid', $this->choice->id); + $mform->setDefault('optional', $this->choice->optional); } else { $mform->setDefault('active', true); } diff --git a/lang/en/ratingallocate.php b/lang/en/ratingallocate.php index b30b22f1..42e9b39d 100644 --- a/lang/en/ratingallocate.php +++ b/lang/en/ratingallocate.php @@ -193,6 +193,8 @@ // $string['choice_active'] = 'Choice is active'; $string['choice_active_help'] = 'Only active choices are displayed to the user. Inactive choices are not displayed.'; +$string['choice_optional'] = 'Optional'; +$string['choice_optional_help'] = 'Whether the choice is allowed to be cancelled'; $string['choice_explanation'] = 'Description (optional)'; $string['choice_maxsize'] = 'Max. number of participants'; $string['choice_maxsize_display'] = 'Maximum number of students'; @@ -232,6 +234,7 @@ $string['choice_table_minsize'] = 'Min. Size'; $string['choice_table_maxsize'] = 'Max. Size'; $string['choice_table_active'] = 'Active'; +$string['choice_table_optional'] = 'Optional'; $string['choice_table_tools'] = 'Edit'; // diff --git a/locallib.php b/locallib.php index f34e50d4..2e093d60 100644 --- a/locallib.php +++ b/locallib.php @@ -1512,6 +1512,7 @@ public function is_setup_ok() { * @property int $maxsize * @property bool $active * @property int minsize + * @property bool optional */ class ratingallocate_choice { /** @var stdClass original db record */ diff --git a/renderer.php b/renderer.php index c9097370..33f51ed5 100644 --- a/renderer.php +++ b/renderer.php @@ -425,19 +425,19 @@ public function ratingallocate_show_choices_table(ratingallocate $ratingallocate $table = new \flexible_table('show_ratingallocate_options'); $table->define_baseurl($PAGE->url); - $columns = array('title', 'explanation', 'minsize', 'maxsize', 'active'); + $columns = array('title', 'explanation', 'minsize', 'maxsize', 'optional', 'active'); $headers = array(get_string('choice_table_title', 'mod_ratingallocate'), get_string('choice_table_explanation', 'mod_ratingallocate'), get_string('choice_table_minsize', 'mod_ratingallocate'), get_string('choice_table_maxsize', 'mod_ratingallocate'), - get_string('choice_table_tools', 'mod_ratingallocate')); + get_string('choice_table_optional', 'mod_ratingallocate'), + get_string('choice_table_active', 'mod_ratingallocate')); + if ($choicesmodifiably) { - array_push($columns, 'tools'); - // Pushes the active header into headers table at index 3. - $headers = array_merge(array_splice($headers, 0, 5, false), - array(get_string('choice_table_active', 'mod_ratingallocate')), - array_splice($headers, 3, 5, false)); + $columns[] = "tools"; + $headers[] = get_string('choice_table_tools', 'mod_ratingallocate'); } + $table->define_columns($columns); $table->define_headers($headers); $table->set_attribute('id', 'mod_ratingallocateshowoptions'); diff --git a/version.php b/version.php index cec6fd8e..9ea763dc 100644 --- a/version.php +++ b/version.php @@ -26,7 +26,7 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2019031802; // The current module version (Date: YYYYMMDDXX) +$plugin->version = 2019031803; // The current module version (Date: YYYYMMDDXX) $plugin->requires = 2017111300; // Requires this Moodle version $plugin->maturity = MATURITY_STABLE; $plugin->release = 'v3.6-r1'; From da9e112ab4cfcdf57b85f2140cf5d7f81551c2f9 Mon Sep 17 00:00:00 2001 From: Justus Dieckmann Date: Mon, 18 Mar 2019 16:31:02 +0100 Subject: [PATCH 17/73] add column for optional field in list of all choices table --- db/db_structure.php | 1 + renderer.php | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/db/db_structure.php b/db/db_structure.php index fe6a7eca..dea217c1 100644 --- a/db/db_structure.php +++ b/db/db_structure.php @@ -51,6 +51,7 @@ class ratingallocate_choices { const EXPLANATION = 'explanation'; const MINSIZE = 'minsize'; const MAXSIZE = 'maxsize'; + const OPTIONAL = 'optional'; const ACTIVE = 'active'; } class ratingallocate_ratings { diff --git a/renderer.php b/renderer.php index 33f51ed5..ae1fcbeb 100644 --- a/renderer.php +++ b/renderer.php @@ -457,6 +457,11 @@ public function ratingallocate_show_choices_table(ratingallocate $ratingallocate $row[] = $choice->{this_db\ratingallocate_choices::EXPLANATION}; $row[] = $choice->{this_db\ratingallocate_choices::MINSIZE}; $row[] = $choice->{this_db\ratingallocate_choices::MAXSIZE}; + if ($choice->{this_db\ratingallocate_choices::OPTIONAL}) { + $row[] = get_string('yes'); + } else { + $row[] = get_string('no'); + } if ($choice->{this_db\ratingallocate_choices::ACTIVE}) { $row[] = get_string('yes'); } else { From 65e595f59f7758212299b9a7d3057f46c2ec74fc Mon Sep 17 00:00:00 2001 From: Justus Dieckmann Date: Mon, 18 Mar 2019 16:33:37 +0100 Subject: [PATCH 18/73] adjusted testcases for optional field --- tests/mod_generator_test.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/mod_generator_test.php b/tests/mod_generator_test.php index cf8538a9..514e5a73 100644 --- a/tests/mod_generator_test.php +++ b/tests/mod_generator_test.php @@ -86,7 +86,8 @@ public function test_create_instance() { 'explanation' => 'Some explanatory text for choice 1', 'maxsize' => '10', 'minsize' => '0', - 'active' => '1' + 'active' => '1', + 'optional' => '0' ), $choice_ids[1] => (object) array( 'title' => 'Choice 2', @@ -95,7 +96,8 @@ public function test_create_instance() { 'explanation' => 'Some explanatory text for choice 2', 'maxsize' => '5', 'minsize' => '0', - 'active' => '0' + 'active' => '0', + 'optional' => '0' ) ); $this->assertEquals($expected_choices, $records); From cb77a28c14908cf6141d7e697aeafd95386f6279 Mon Sep 17 00:00:00 2001 From: NinaHerrmann Date: Mon, 18 Mar 2019 16:40:00 +0100 Subject: [PATCH 19/73] Add fordfulkersonkoegel algorithm --- .../edmondskarp/classes/algorithm_impl.php | 1 - .../classes/algorithm_impl.php | 299 ++++++++++++++++++ .../fordfulkersonkoegel/classes/edge.php | 49 +++ .../lang/en/raalgo_fordfulkersonkoegel.php | 25 ++ algorithm/fordfulkersonkoegel/version.php | 30 ++ classes/algorithm.php | 2 +- solver/ford-fulkerson-koegel.php | 157 --------- tests/mod_ratingallocate_solver_test.php | 19 +- 8 files changed, 413 insertions(+), 169 deletions(-) create mode 100644 algorithm/fordfulkersonkoegel/classes/algorithm_impl.php create mode 100644 algorithm/fordfulkersonkoegel/classes/edge.php create mode 100644 algorithm/fordfulkersonkoegel/lang/en/raalgo_fordfulkersonkoegel.php create mode 100644 algorithm/fordfulkersonkoegel/version.php delete mode 100644 solver/ford-fulkerson-koegel.php diff --git a/algorithm/edmondskarp/classes/algorithm_impl.php b/algorithm/edmondskarp/classes/algorithm_impl.php index 6a0fc211..ecbb74b0 100644 --- a/algorithm/edmondskarp/classes/algorithm_impl.php +++ b/algorithm/edmondskarp/classes/algorithm_impl.php @@ -284,5 +284,4 @@ protected function augment_flow($path) { } } } - } diff --git a/algorithm/fordfulkersonkoegel/classes/algorithm_impl.php b/algorithm/fordfulkersonkoegel/classes/algorithm_impl.php new file mode 100644 index 00000000..c15acbac --- /dev/null +++ b/algorithm/fordfulkersonkoegel/classes/algorithm_impl.php @@ -0,0 +1,299 @@ +. + +/** + * + * Contains the algorithm for the distribution + * + * @package raalgo_fordfulkersonkoegel + * @copyright 2014 M Schulze + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace raalgo_fordfulkersonkoegel; +defined('MOODLE_INTERNAL') || die(); + +class algorithm_impl extends \mod_ratingallocate\algorithm { + + /** @var $graph Flow-Graph built */ + /** + * Starts the distribution algorithm. + * Uses the users' ratings and a minimum-cost maximum-flow algorithm + * to distribute the users fairly into the groups. + * (see http://en.wikipedia.org/wiki/Minimum-cost_flow_problem) + * After the algorithm is done, users are removed from their current + * groups (see clear_all_groups_in_course()) and redistributed + * according to the computed distriution. + * + */ + public function compute_distribution($choicerecords, $ratings, $usercount) { + $groupdata = array(); + foreach ($choicerecords as $record) { + $groupdata[$record->id] = $record; + } + + $groupcount = count($groupdata); + // Index of source and sink in the graph + $source = 0; + $sink = $groupcount + $usercount + 1; + list($fromuserid, $touserid, $fromgroupid, $togroupid) = $this->setup_id_conversions($usercount, $ratings); + + $this->setup_graph($groupcount, $usercount, $fromuserid, $fromgroupid, $ratings, $groupdata, $source, $sink); + + // Now that the datastructure is complete, we can start the algorithm + // This is an adaptation of the Ford-Fulkerson algorithm + // (http://en.wikipedia.org/wiki/Ford%E2%80%93Fulkerson_algorithm) + for ($i = 1; $i <= $usercount; $i++) { + // Look for an augmenting path (a shortest path from the source to the sink) + $path = $this->find_shortest_path_bellmanf_koegel($source, $sink); + // If there is no such path, it is impossible to fit any more users into groups. + if (is_null($path)) { + // Stop the algorithm + continue; + } + // Reverse the augmenting path, thereby distributing a user into a group + $this->augment_flow($path); + } + + return $this->extract_allocation($touserid, $togroupid); + } + + /** + * Uses a modified Bellman-Ford algorithm to find a shortest path + * from $from to $to in $graph. We can't use Dijkstra here, because + * the graph contains edges with negative weight. + * + * @param $from index of starting node + * @param $to index of end node + * @return array with the of the nodes in the path + */ + public function find_shortest_path_bellmanf_koegel($from, $to) { + + // Table of distances known so far + $dists = array(); + // Table of predecessors (used to reconstruct the shortest path later) + $preds = array(); + // Stack of the edges we need to test next + $edges = $this->graph[$from]; + // Number of nodes in the graph + $count = $this->graph['count']; + + // To prevent the algorithm from getting stuck in a loop with + // with negative weight, we stop it after $count ^ 3 iterations + $counter = 0; + $limit = $count * $count * $count; + + // Initialize dists and preds + for ($i = 0; $i < $count; $i++) { + if ($i == $from) { + $dists[$i] = 0; + } else { + $dists[$i] = -INF; + } + $preds[$i] = null; + } + + while (!empty($edges) and $counter < $limit) { + $counter++; + + /* @var e edge */ + $e = array_pop($edges); + + $f = $e->from; + $t = $e->to; + $dist = $e->weight + $dists[$f]; + + // If this edge improves a distance update the tables and the edges stack + if ($dist > $dists[$t]) { + $dists[$t] = $dist; + $preds[$t] = $f; + foreach ($this->graph[$t] as $newedge) { + $edges[] = $newedge; + } + } + } + + // A valid groupdistribution graph can't contain a negative edge + if ($counter == $limit) { + print_error('negative_cycle', 'ratingallocate'); + } + + // If there is no path to $to, return null + if (is_null($preds[$to])) { + return null; + } + + // Use the preds table to reconstruct the shortest path + $path = array(); + $p = $to; + while ($p != $from) { + $path[] = $p; + $p = $preds[$p]; + } + $path[] = $from; + + return $path; + } + + public function get_name() { + return "ford-fulkerson Koegel2014"; + } + + /** + * Extracts a distribution/allocation from the graph. + * + * @param $touserid a map mapping from indexes in the graph to userids + * @param $tochoiceid a map mapping from indexes in the graph to choiceids + * @return array of the form array(groupid => array(userid, ...), ...) + */ + protected function extract_allocation($touserid, $tochoiceid) { + $distribution = array(); + foreach ($tochoiceid as $index => $groupid) { + $group = $this->graph[$index]; + $distribution[$groupid] = array(); + foreach ($group as $assignment) { + /* @var $assignment edge */ + $user = intval($assignment->to); + if (array_key_exists($user, $touserid)) { + $distribution[$groupid][] = $touserid[$user]; + } + } + } + return $distribution; + } + + /** + * Setup conversions between ids of users and choices to their node-ids in the graph + * @param $usercount + * @param $ratings + * @return array($fromuserid, $touserid, $fromchoiceid, $tochoiceid); + */ + public static function setup_id_conversions($usercount, $ratings) { + // These tables convert userids to their index in the graph. + // The range is [1..$usercount]. + $fromuserid = array(); + $touserid = array(); + // These tables convert choiceids to their index in the graph. + // The range is [$usercount + 1 .. $usercount + $choicecount]. + $fromchoiceid = array(); + $tochoiceid = array(); + + // User counter. + $ui = 1; + // Group counter. + $gi = $usercount + 1; + + // Fill the conversion tables for group and user ids. + foreach ($ratings as $rating) { + if (!array_key_exists($rating->userid, $fromuserid)) { + $fromuserid[$rating->userid] = $ui; + $touserid[$ui] = $rating->userid; + $ui++; + } + if (!array_key_exists($rating->choiceid, $fromchoiceid)) { + $fromchoiceid[$rating->choiceid] = $gi; + $tochoiceid[$gi] = $rating->choiceid; + $gi++; + } + } + + return array($fromuserid, $touserid, $fromchoiceid, $tochoiceid); + } + + /** + * Sets up $this->graph + * @param $choicecount + * @param $usercount + * @param $fromuserid + * @param $fromchoiceid + * @param $ratings + * @param $choicedata + * @param $source + * @param $sink + */ + protected function setup_graph($choicecount, $usercount, $fromuserid, $fromchoiceid, $ratings, $choicedata, $source, + $sink, $weightmult = 1) { + // Construct the datastructures for the algorithm. + // A directed weighted bipartite graph. + // A source is connected to all users with unit cost. + // The users are connected to their choices with cost equal to their rating. + // The choices are connected to a sink with 0 cost. + $this->graph = array(); + // Add source, sink and number of nodes to the graph. + $this->graph[$source] = array(); + $this->graph[$sink] = array(); + $this->graph['count'] = $choicecount + $usercount + 2; + + // Add users and choices to the graph and connect them to the source and sink. + foreach ($fromuserid as $id => $user) { + $this->graph[$user] = array(); + $this->graph[$source][] = new edge($source, $user, 0); + } + foreach ($fromchoiceid as $id => $choice) { + $this->graph[$choice] = array(); + $this->graph[$choice][] = new edge($choice, $sink, 0, $choicedata[$id]->maxsize); + } + + // Add the edges representing the ratings to the graph. + foreach ($ratings as $id => $rating) { + $user = $fromuserid[$rating->userid]; + $choice = $fromchoiceid[$rating->choiceid]; + $weight = $rating->rating; + if ($weight > 0) { + $this->graph[$user][] = new edge($user, $choice, $weightmult * $weight); + } + } + } + + /** + * Augments the flow in the network, i.e. augments the overall 'satisfaction' + * by distributing users to choices + * Reverses all edges along $path in $graph + * @param $path path from t to s + */ + protected function augment_flow($path) { + if (is_null($path) or count($path) < 2) { + print_error('invalid_path', 'ratingallocate'); + } + + // Walk along the path, from s to t. + for ($i = count($path) - 1; $i > 0; $i--) { + $from = $path[$i]; + $to = $path[$i - 1]; + $edge = null; + $foundedgeid = -1; + // Find the edge. + foreach ($this->graph[$from] as $index => &$edge) { + /* @var $edge edge */ + if ($edge->to == $to) { + $foundedgeid = $index; + break; + } + } + // The second to last node in a path has to be a choice-node. + // Reduce its space by one, because one user just got distributed into it. + if ($i == 1 and $edge->space > 1) { + $edge->space --; + } else { + // Remove the edge. + array_splice($this->graph[$from], $foundedgeid, 1); + // Add a new edge in the opposite direction whose weight has an opposite sign. + // Array_push($this->graph[$to], new edge($to, $from, -1 * $edge->weight)); + // According to php doc, this is faster. + $this->graph[$to][] = new edge($to, $from, -1 * $edge->weight); + } + } + } +} diff --git a/algorithm/fordfulkersonkoegel/classes/edge.php b/algorithm/fordfulkersonkoegel/classes/edge.php new file mode 100644 index 00000000..ce6c44a3 --- /dev/null +++ b/algorithm/fordfulkersonkoegel/classes/edge.php @@ -0,0 +1,49 @@ +. + +/** + * + * Contains the algorithm for the distribution + * + * @package raalgo_fordfulkersonkoegel + * @copyright 2014 M Schulze + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace raalgo_fordfulkersonkoegel; +defined('MOODLE_INTERNAL') || die(); + +/** + * Represents an Edge in the graph to have fixed fields instead of array-fields + */ +class edge { + /** @var from int */ + public $from; + /** @var to int */ + public $to; + /** @var weight int Cost for this edge (rating of user) */ + public $weight; + /** @var space int (places left for choices) */ + public $space; + + public function __construct($from, $to, $weight, $space = 0) { + $this->from = $from; + $this->to = $to; + $this->weight = $weight; + $this->space = $space; + } + +} \ No newline at end of file diff --git a/algorithm/fordfulkersonkoegel/lang/en/raalgo_fordfulkersonkoegel.php b/algorithm/fordfulkersonkoegel/lang/en/raalgo_fordfulkersonkoegel.php new file mode 100644 index 00000000..5e421b1a --- /dev/null +++ b/algorithm/fordfulkersonkoegel/lang/en/raalgo_fordfulkersonkoegel.php @@ -0,0 +1,25 @@ +. + + +/** + * English strings for raalgo_fordfulkersonkoegel + * + * @package raalgo_fordfulkersonkoegel + * @copyright 2019 J Dageförde, N Herrmann + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +$string['pluginname'] = 'Ford–Fulkerson-Koegel algorithm'; \ No newline at end of file diff --git a/algorithm/fordfulkersonkoegel/version.php b/algorithm/fordfulkersonkoegel/version.php new file mode 100644 index 00000000..e0a91046 --- /dev/null +++ b/algorithm/fordfulkersonkoegel/version.php @@ -0,0 +1,30 @@ +. + + +/** + * Defines the version of raalgo_fordfulkersonkoegel + * + * @package raalgo_fordfulkersonkoegel + * @copyright 2019 J Dageförde, N Herrmann + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$plugin->version = 2019031800; // The current module version (Date: YYYYMMDDXX) +$plugin->requires = 2017111300; // Requires this Moodle version +$plugin->component = 'raalgo_fordfulkersonkoegel'; // To check on upgrade, that module sits in correct place diff --git a/classes/algorithm.php b/classes/algorithm.php index 3652e40d..77334746 100644 --- a/classes/algorithm.php +++ b/classes/algorithm.php @@ -85,7 +85,7 @@ public static function compute_target_function($ratings, $distribution) { * @return algorithm Algorithm instance */ public static function get_instance(string $name) { - $subplugins = \core_plugin_manager::get_subplugins_of_plugin('mod_ratingallocate'); + $subplugins = \core_plugin_manager::instance()->get_plugins_of_type('raalgo'); // TODO Check whether the specified plugin is installed. $classname = '\raalgo_'.$name.'\algorithm_impl'; return new $classname(); diff --git a/solver/ford-fulkerson-koegel.php b/solver/ford-fulkerson-koegel.php deleted file mode 100644 index 37a7e6b2..00000000 --- a/solver/ford-fulkerson-koegel.php +++ /dev/null @@ -1,157 +0,0 @@ -. - -/** - * Internal library of functions for module groupdistribution. - * - * Contains the algorithm for the group distribution - * - * @package mod_ratingallocate - * @subpackage mod_ratingallocate originally mod_groupdistribution - * @copyright 2014 M Schulze - * @copyright based on code by Stefan Koegel copyright (C) 2013 Stefan Koegel - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -defined('MOODLE_INTERNAL') || die(); - -require_once(dirname(__FILE__) . '/../locallib.php'); - -class solver_ford_fulkerson extends distributor { - - /** - * Starts the distribution algorithm. - * Uses the users' ratings and a minimum-cost maximum-flow algorithm - * to distribute the users fairly into the groups. - * (see http://en.wikipedia.org/wiki/Minimum-cost_flow_problem) - * After the algorithm is done, users are removed from their current - * groups (see clear_all_groups_in_course()) and redistributed - * according to the computed distriution. - * - */ - public function compute_distribution($choicerecords, $ratings, $usercount) { - $groupdata = array(); - foreach ($choicerecords as $record) { - $groupdata[$record->id] = $record; - } - - $groupcount = count($groupdata); - // Index of source and sink in the graph - $source = 0; - $sink = $groupcount + $usercount + 1; - list($fromuserid, $touserid, $fromgroupid, $togroupid) = $this->setup_id_conversions($usercount, $ratings); - - $this->setup_graph($groupcount, $usercount, $fromuserid, $fromgroupid, $ratings, $groupdata, $source, $sink); - - // Now that the datastructure is complete, we can start the algorithm - // This is an adaptation of the Ford-Fulkerson algorithm - // (http://en.wikipedia.org/wiki/Ford%E2%80%93Fulkerson_algorithm) - for ($i = 1; $i <= $usercount; $i++) { - // Look for an augmenting path (a shortest path from the source to the sink) - $path = $this->find_shortest_path_bellmanf_koegel($source, $sink); - // If there is no such path, it is impossible to fit any more users into groups. - if (is_null($path)) { - // Stop the algorithm - continue; - } - // Reverse the augmenting path, thereby distributing a user into a group - $this->augment_flow($path); - } - - return $this->extract_allocation($touserid, $togroupid); - } - - /** - * Uses a modified Bellman-Ford algorithm to find a shortest path - * from $from to $to in $graph. We can't use Dijkstra here, because - * the graph contains edges with negative weight. - * - * @param $from index of starting node - * @param $to index of end node - * @return array with the of the nodes in the path - */ - public function find_shortest_path_bellmanf_koegel($from, $to) { - - // Table of distances known so far - $dists = array(); - // Table of predecessors (used to reconstruct the shortest path later) - $preds = array(); - // Stack of the edges we need to test next - $edges = $this->graph[$from]; - // Number of nodes in the graph - $count = $this->graph['count']; - - // To prevent the algorithm from getting stuck in a loop with - // with negative weight, we stop it after $count ^ 3 iterations - $counter = 0; - $limit = $count * $count * $count; - - // Initialize dists and preds - for ($i = 0; $i < $count; $i++) { - if ($i == $from) { - $dists[$i] = 0; - } else { - $dists[$i] = -INF; - } - $preds[$i] = null; - } - - while (!empty($edges) and $counter < $limit) { - $counter++; - - /* @var e edge */ - $e = array_pop($edges); - - $f = $e->from; - $t = $e->to; - $dist = $e->weight + $dists[$f]; - - // If this edge improves a distance update the tables and the edges stack - if ($dist > $dists[$t]) { - $dists[$t] = $dist; - $preds[$t] = $f; - foreach ($this->graph[$t] as $newedge) { - $edges[] = $newedge; - } - } - } - - // A valid groupdistribution graph can't contain a negative edge - if ($counter == $limit) { - print_error('negative_cycle', 'ratingallocate'); - } - - // If there is no path to $to, return null - if (is_null($preds[$to])) { - return null; - } - - // Use the preds table to reconstruct the shortest path - $path = array(); - $p = $to; - while ($p != $from) { - $path[] = $p; - $p = $preds[$p]; - } - $path[] = $from; - - return $path; - } - - public function get_name() { - return "ford-fulkerson Koegel2014"; - } - -} diff --git a/tests/mod_ratingallocate_solver_test.php b/tests/mod_ratingallocate_solver_test.php index 131bfb12..2e2c4c08 100644 --- a/tests/mod_ratingallocate_solver_test.php +++ b/tests/mod_ratingallocate_solver_test.php @@ -28,7 +28,6 @@ global $CFG; require_once($CFG->dirroot . '/mod/ratingallocate/locallib.php'); -require_once($CFG->dirroot . '/mod/ratingallocate/solver/ford-fulkerson-koegel.php'); class edmonds_karp_test extends basic_testcase { @@ -72,9 +71,9 @@ private function perform_race($groupsnum, $ratersnum) { $usercount = $ratersnum; - $solvers = array('\raalgo_edmondskarp\algorithm_impl', 'solver_ford_fulkerson'); + $solvers = array('edmondskarp', 'fordfulkersonkoegel'); foreach ($solvers as $solver) { - $solver1 = new $solver; + $solver1 = \mod_ratingallocate\algorithm::get_instance($solver); $timestart = microtime(true); $distribution1 = $solver1->compute_distribution($groups, $ratings, $usercount); $result[$solver1->get_name()]['elapsed_sec'] = (microtime(true) - $timestart); @@ -187,7 +186,7 @@ public function test_edmondskarp() { $usercount = 5; - $solver = new \raalgo_edmondskarp\algorithm_impl(); + $solver = \mod_ratingallocate\algorithm::get_instance('edmondskarp'); $distribution = $solver->compute_distribution($choices, $ratings, $usercount); $expected = array(1 => array(2, 5), 2 => array(4, 1)); // echo "gesamtpunktzahl: " . $solver->compute_target_function($choices, $ratings, $distribution); @@ -196,7 +195,7 @@ public function test_edmondskarp() { $this->assertEquals($solver::compute_target_function($ratings, $distribution), 15); // test against Koegels solver - $solverkoe = new solver_ford_fulkerson(); + $solverkoe = \mod_ratingallocate\algorithm::get_instance('fordfulkersonkoegel'); $distributionkoe = $solverkoe->compute_distribution($choices, $ratings, $usercount); $this->assertEquals($solverkoe::compute_target_function($ratings, $distributionkoe), 15); $this->assertEquals($solverkoe::compute_target_function($ratings, $distributionkoe), $solver::compute_target_function($ratings, $distribution)); @@ -235,11 +234,11 @@ public function test_negweightcycle() { $usercount = 2; - $solver = new \raalgo_edmondskarp\algorithm_impl(); + $solver = \mod_ratingallocate\algorithm::get_instance('edmondskarp'); $distribution = $solver->compute_distribution($choices, $ratings, $usercount); $this->assertEquals($solver::compute_target_function($ratings, $distribution), 10); - $solverkoe = new solver_ford_fulkerson(); + $solverkoe = \mod_ratingallocate\algorithm::get_instance('fordfulkersonkoegel'); $distributionkoe = $solverkoe->compute_distribution($choices, $ratings, $usercount); $this->assertEquals($solverkoe::compute_target_function($ratings, $distributionkoe), 10); @@ -268,9 +267,9 @@ public function test_targetfunc() { $ratings[4]->choiceid = 2; $ratings[4]->rating = 4; - $this->assertEquals(distributor::compute_target_function($ratings, array(1 => array(1), 2 => array(2))), 9); - $this->assertEquals(distributor::compute_target_function($ratings, array(1 => array(1, 2))), 8); - $this->assertEquals(distributor::compute_target_function($ratings, array(1 => array(2), 2 => array(1))), 7); + $this->assertEquals(\mod_ratingallocate\algorithm::compute_target_function($ratings, array(1 => array(1), 2 => array(2))), 9); + $this->assertEquals(\mod_ratingallocate\algorithm::compute_target_function($ratings, array(1 => array(1, 2))), 8); + $this->assertEquals(\mod_ratingallocate\algorithm::compute_target_function($ratings, array(1 => array(2), 2 => array(1))), 7); } /** From 595a9a954651ad4d35f6287f0ae5b57c8bc840ba Mon Sep 17 00:00:00 2001 From: NinaHerrmann Date: Mon, 18 Mar 2019 16:56:20 +0100 Subject: [PATCH 20/73] remove reference to obsolete algo file --- view.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/view.php b/view.php index 50b28b4c..2b0af028 100644 --- a/view.php +++ b/view.php @@ -30,8 +30,6 @@ require_once(dirname(dirname(dirname(__FILE__))).'/config.php'); require_once(dirname(__FILE__).'/locallib.php'); -require_once(dirname(__FILE__).'/solver/ford-fulkerson-koegel.php'); - $id = optional_param('id', 0, PARAM_INT); // course_module ID, or $n = optional_param('m', 0, PARAM_INT); // ratingallocate instance ID - it should be named as the first character of the module From 2a6131ab385e1bb77038ba623f63b6cc52c5e2ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Dagef=C3=B6rde?= Date: Mon, 18 Mar 2019 17:05:21 +0100 Subject: [PATCH 21/73] fix PHPCS in new files --- algorithm/edmondskarp/version.php | 2 +- .../classes/algorithm_impl.php | 36 ++++++++++--------- algorithm/fordfulkersonkoegel/version.php | 2 +- 3 files changed, 21 insertions(+), 19 deletions(-) diff --git a/algorithm/edmondskarp/version.php b/algorithm/edmondskarp/version.php index 1f3c5a5d..100d7572 100644 --- a/algorithm/edmondskarp/version.php +++ b/algorithm/edmondskarp/version.php @@ -27,4 +27,4 @@ $plugin->version = 2019031800; // The current module version (Date: YYYYMMDDXX) $plugin->requires = 2017111300; // Requires this Moodle version -$plugin->component = 'raalgo_edmondskarp'; // To check on upgrade, that module sits in correct place +$plugin->component = 'raalgo_edmondskarp'; // To check on upgrade, that module sits in correct place. diff --git a/algorithm/fordfulkersonkoegel/classes/algorithm_impl.php b/algorithm/fordfulkersonkoegel/classes/algorithm_impl.php index c15acbac..9382d64a 100644 --- a/algorithm/fordfulkersonkoegel/classes/algorithm_impl.php +++ b/algorithm/fordfulkersonkoegel/classes/algorithm_impl.php @@ -28,6 +28,8 @@ class algorithm_impl extends \mod_ratingallocate\algorithm { /** @var $graph Flow-Graph built */ + protected $graph; + /** * Starts the distribution algorithm. * Uses the users' ratings and a minimum-cost maximum-flow algorithm @@ -45,25 +47,25 @@ public function compute_distribution($choicerecords, $ratings, $usercount) { } $groupcount = count($groupdata); - // Index of source and sink in the graph + // Index of source and sink in the graph. $source = 0; $sink = $groupcount + $usercount + 1; list($fromuserid, $touserid, $fromgroupid, $togroupid) = $this->setup_id_conversions($usercount, $ratings); $this->setup_graph($groupcount, $usercount, $fromuserid, $fromgroupid, $ratings, $groupdata, $source, $sink); - // Now that the datastructure is complete, we can start the algorithm - // This is an adaptation of the Ford-Fulkerson algorithm - // (http://en.wikipedia.org/wiki/Ford%E2%80%93Fulkerson_algorithm) + // Now that the datastructure is complete, we can start the algorithm. + // This is an adaptation of the Ford-Fulkerson algorithm. + // (http://en.wikipedia.org/wiki/Ford%E2%80%93Fulkerson_algorithm). for ($i = 1; $i <= $usercount; $i++) { - // Look for an augmenting path (a shortest path from the source to the sink) + // Look for an augmenting path (a shortest path from the source to the sink). $path = $this->find_shortest_path_bellmanf_koegel($source, $sink); // If there is no such path, it is impossible to fit any more users into groups. if (is_null($path)) { - // Stop the algorithm + // Stop the algorithm. continue; } - // Reverse the augmenting path, thereby distributing a user into a group + // Reverse the augmenting path, thereby distributing a user into a group. $this->augment_flow($path); } @@ -81,21 +83,21 @@ public function compute_distribution($choicerecords, $ratings, $usercount) { */ public function find_shortest_path_bellmanf_koegel($from, $to) { - // Table of distances known so far + // Table of distances known so far. $dists = array(); - // Table of predecessors (used to reconstruct the shortest path later) + // Table of predecessors (used to reconstruct the shortest path later). $preds = array(); - // Stack of the edges we need to test next + // Stack of the edges we need to test next. $edges = $this->graph[$from]; - // Number of nodes in the graph + // Number of nodes in the graph. $count = $this->graph['count']; // To prevent the algorithm from getting stuck in a loop with - // with negative weight, we stop it after $count ^ 3 iterations + // with negative weight, we stop it after $count ^ 3 iterations. $counter = 0; $limit = $count * $count * $count; - // Initialize dists and preds + // Initialize dists and preds. for ($i = 0; $i < $count; $i++) { if ($i == $from) { $dists[$i] = 0; @@ -115,7 +117,7 @@ public function find_shortest_path_bellmanf_koegel($from, $to) { $t = $e->to; $dist = $e->weight + $dists[$f]; - // If this edge improves a distance update the tables and the edges stack + // If this edge improves a distance update the tables and the edges stack. if ($dist > $dists[$t]) { $dists[$t] = $dist; $preds[$t] = $f; @@ -125,17 +127,17 @@ public function find_shortest_path_bellmanf_koegel($from, $to) { } } - // A valid groupdistribution graph can't contain a negative edge + // A valid groupdistribution graph can't contain a negative edge. if ($counter == $limit) { print_error('negative_cycle', 'ratingallocate'); } - // If there is no path to $to, return null + // If there is no path to $to, return null. if (is_null($preds[$to])) { return null; } - // Use the preds table to reconstruct the shortest path + // Use the preds table to reconstruct the shortest path. $path = array(); $p = $to; while ($p != $from) { diff --git a/algorithm/fordfulkersonkoegel/version.php b/algorithm/fordfulkersonkoegel/version.php index e0a91046..8b61261c 100644 --- a/algorithm/fordfulkersonkoegel/version.php +++ b/algorithm/fordfulkersonkoegel/version.php @@ -27,4 +27,4 @@ $plugin->version = 2019031800; // The current module version (Date: YYYYMMDDXX) $plugin->requires = 2017111300; // Requires this Moodle version -$plugin->component = 'raalgo_fordfulkersonkoegel'; // To check on upgrade, that module sits in correct place +$plugin->component = 'raalgo_fordfulkersonkoegel'; // To check on upgrade, that module sits in correct place. From 2e71639dcf7ef44b575a61b630666631e4e92901 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Dagef=C3=B6rde?= Date: Mon, 18 Mar 2019 17:22:25 +0100 Subject: [PATCH 22/73] Add some further style fixes --- classes/algorithm.php | 6 +++--- classes/plugininfo/raalgo.php | 7 +++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/classes/algorithm.php b/classes/algorithm.php index 77334746..dcbf9406 100644 --- a/classes/algorithm.php +++ b/classes/algorithm.php @@ -65,13 +65,13 @@ public function distribute_users(\ratingallocate $ratingallocate) { public static function compute_target_function($ratings, $distribution) { $functionvalue = 0; foreach ($distribution as $choiceid => $choice) { - // $choice ist jetzt ein array von userids + // In $choice ist jetzt ein array von userids. foreach ($choice as $userid) { - // jetzt das richtige Rating rausfinden + // Jetzt das richtige Rating rausfinden. foreach ($ratings as $rating) { if ($rating->userid == $userid && $rating->choiceid == $choiceid) { $functionvalue += $rating->rating; - continue; // aus der Such-Schleife raus und weitermachen + continue; // Aus der Such-Schleife raus und weitermachen. } } diff --git a/classes/plugininfo/raalgo.php b/classes/plugininfo/raalgo.php index bdca6b91..dac1f198 100644 --- a/classes/plugininfo/raalgo.php +++ b/classes/plugininfo/raalgo.php @@ -15,6 +15,13 @@ // along with Moodle. If not, see . namespace mod_ratingallocate\plugininfo; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Subplugin type raalgo (allocation algorithms). + * @package mod_ratingallocate\plugininfo + */ class raalgo extends \core\plugininfo\base { } \ No newline at end of file From e754a0d9f4111a8b604d136ec4c7166e8edd179e Mon Sep 17 00:00:00 2001 From: Justus Dieckmann Date: Mon, 18 Mar 2019 17:39:53 +0100 Subject: [PATCH 23/73] Fix behat tests by adding minsize and optional fields to choices --- tests/behat/allocation_status.feature | 38 +++++++------ tests/behat/defaultratings.feature | 36 ++++++------ tests/behat/mod_form.feature | 80 ++++++++++++++++----------- tests/behat/ratings.feature | 30 ++++++---- 4 files changed, 106 insertions(+), 78 deletions(-) diff --git a/tests/behat/allocation_status.feature b/tests/behat/allocation_status.feature index cf56064d..749e19de 100644 --- a/tests/behat/allocation_status.feature +++ b/tests/behat/allocation_status.feature @@ -6,20 +6,20 @@ Feature: Students should get status information according to their rating and th | fullname | shortname | category | groupmode | | Course 1 | C1 | 0 | 1 | And the following "users" exist: - | username | firstname | lastname | email | - | teacher1 | Theo | Teacher | teacher1@example.com | - | student1 | Steve | Student | student1@example.com | - | student2 | Sophie | Student | student2@example.com | - | student3 | Steffanie | Student | student3@example.com | + | username | firstname | lastname | email | + | teacher1 | Theo | Teacher | teacher1@example.com | + | student1 | Steve | Student | student1@example.com | + | student2 | Sophie | Student | student2@example.com | + | student3 | Steffanie | Student | student3@example.com | And the following "course enrolments" exist: - | user | course | role | - | teacher1 | C1 | editingteacher | - | student1 | C1 | student | - | student2 | C1 | student | - | student3 | C1 | student | + | user | course | role | + | teacher1 | C1 | editingteacher | + | student1 | C1 | student | + | student2 | C1 | student | + | student3 | C1 | student | And the following "activities" exist: - | activity | course | idnumber | name | accesstimestart | - | ratingallocate | C1 | ra1 | My Fair Allocation | ##yesterday## | + | activity | course | idnumber | name | accesstimestart | + | ratingallocate | C1 | ra1 | My Fair Allocation | ##yesterday## | And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I follow "My Fair Allocation" @@ -27,7 +27,9 @@ Feature: Students should get status information according to their rating and th And I add a new choice with the values: | title | My only choice | | explanation | Test | - | maxsize | 1 | + | maxsize | 1 | + | minsize | 0 | + | optional | 0 | And I log out And I log in as "student1" And I am on "Course 1" course homepage @@ -47,12 +49,12 @@ Feature: Students should get status information according to their rating and th And I follow "My Fair Allocation" And I navigate to "Edit settings" in current page administration And I set the following fields to these values: - | accesstimestart[day] | ##2 days ago##j## | + | accesstimestart[day] | ##2 days ago##j## | | accesstimestart[month] | ##2 days ago##n## | - | accesstimestart[year] | ##2 days ago##Y## | - | accesstimestop[day] | ##yesterday##j## | - | accesstimestop[month] | ##yesterday##n## | - | accesstimestop[year] | ##yesterday##Y## | + | accesstimestart[year] | ##2 days ago##Y## | + | accesstimestop[day] | ##yesterday##j## | + | accesstimestop[month] | ##yesterday##n## | + | accesstimestop[year] | ##yesterday##Y## | And I press "id_submitbutton" And I run the scheduled task "mod_ratingallocate\task\cron_task" And I am on "Course 1" course homepage diff --git a/tests/behat/defaultratings.feature b/tests/behat/defaultratings.feature index 5469e90e..d64e6410 100644 --- a/tests/behat/defaultratings.feature +++ b/tests/behat/defaultratings.feature @@ -7,16 +7,16 @@ Feature: When a student starts a rating the default values of all choices | fullname | shortname | category | groupmode | | Course 1 | C1 | 0 | 1 | And the following "users" exist: - | username | firstname | lastname | email | - | teacher1 | Teacher | 1 | teacher1@example.com | - | student1 | Student | 1 | student1@example.com | + | username | firstname | lastname | email | + | teacher1 | Teacher | 1 | teacher1@example.com | + | student1 | Student | 1 | student1@example.com | And the following "course enrolments" exist: - | user | course | role | - | teacher1 | C1 | editingteacher | - | student1 | C1 | student | + | user | course | role | + | teacher1 | C1 | editingteacher | + | student1 | C1 | student | And the following "activities" exist: - | activity | course | idnumber | name | - | ratingallocate | C1 | ra1 | My Fair Allocation | + | activity | course | idnumber | name | + | ratingallocate | C1 | ra1 | My Fair Allocation | And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I follow "My Fair Allocation" @@ -24,11 +24,15 @@ Feature: When a student starts a rating the default values of all choices And I add a new choice with the values: | title | My first choice | | explanation | Test 1 | - | maxsize | 2 | + | maxsize | 2 | + | minsize | 0 | + | optional | 0 | And I add a new choice with the values: | title | My second choice | - | explanation | Test 1 | - | maxsize | 2 | + | explanation | Test 1 | + | maxsize | 2 | + | minsize | 0 | + | optional | 0 | @javascript Scenario: The default rating is the max rating @@ -41,7 +45,7 @@ Feature: When a student starts a rating the default values of all choices And I follow "My Fair Allocation" And I press "Edit Rating" Then I should see the following rating form: - | My first choice | 4 | + | My first choice | 4 | | My second choice | 4 | @javascript @wip @@ -56,7 +60,7 @@ Feature: When a student starts a rating the default values of all choices And I follow "My Fair Allocation" And I press "Edit Rating" Then I should see the following rating form: - | My first choice | 3 | + | My first choice | 3 | | My second choice | 3 | @javascript @wip @@ -71,7 +75,7 @@ Feature: When a student starts a rating the default values of all choices And I follow "My Fair Allocation" And I press "Edit Rating" Then I should see the following rating form: - | My first choice | 0 | + | My first choice | 0 | | My second choice | 0 | @javascript @@ -85,10 +89,10 @@ Feature: When a student starts a rating the default values of all choices And I follow "My Fair Allocation" And I press "Edit Rating" And I set the rating form to the following values: - | My first choice | 2 | + | My first choice | 2 | | My second choice | 3 | And I press "Save changes" And I press "Edit Rating" Then I should see the following rating form: - | My first choice | 2 | + | My first choice | 2 | | My second choice | 3 | \ No newline at end of file diff --git a/tests/behat/mod_form.feature b/tests/behat/mod_form.feature index 20ade79d..00446cb3 100644 --- a/tests/behat/mod_form.feature +++ b/tests/behat/mod_form.feature @@ -7,13 +7,13 @@ Feature: Creating a new rating allocation, where new choices need to | fullname | shortname | category | groupmode | | Course 1 | C1 | 0 | 1 | And the following "users" exist: - | username | firstname | lastname | email | - | teacher1 | Teacher | 1 | teacher1@example.com | - | student1 | Student | 1 | student1@example.com | + | username | firstname | lastname | email | + | teacher1 | Teacher | 1 | teacher1@example.com | + | student1 | Student | 1 | student1@example.com | And the following "course enrolments" exist: - | user | course | role | - | teacher1 | C1 | editingteacher | - | student1 | C1 | student | + | user | course | role | + | teacher1 | C1 | editingteacher | + | student1 | C1 | student | And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I add a "Fair Allocation" to section "0" and I fill the form with: @@ -23,21 +23,29 @@ Feature: Creating a new rating allocation, where new choices need to And I add a new choice with the values: | title | My first choice | | explanation | Test 1 | - | maxsize | 2 | + | maxsize | 2 | + | minsize | 0 | + | optional | 0 | And I add a new choice with the values: | title | My second choice | | explanation | Test 2 | - | maxsize | 2 | + | maxsize | 2 | + | minsize | 0 | + | optional | 0 | And I add a new choice with the values: | title | My third choice | - | explanation | Test 3 | - | maxsize | 2 | + | explanation | Test 3 | + | maxsize | 2 | + | minsize | 0 | + | optional | 0 | Scenario: Create a new rating alloation and add an additonal new choice. Given I add a new choice with the values: - | title | My fourth choice | - | explanation | Test 4 | - | maxsize | 2 | + | title | My fourth choice | + | explanation | Test 4 | + | maxsize | 2 | + | minsize | 0 | + | optional | 0 | Then I should see the choice with the title "My first choice" And I should see the choice with the title "My second choice" And I should see the choice with the title "My third choice" @@ -45,9 +53,9 @@ Feature: Creating a new rating allocation, where new choices need to Scenario: Create a new rating alloation and add two additonal new choices using the add next button. Given I add new choices with the values: - | title | explanation | maxsize | - | My fourth choice | Test 4 | 2 | - | My fifth choice | Test 5 | 2 | + | title | explanation | maxsize | minsize | optional | + | My fourth choice | Test 4 | 2 | 0 | 0 | + | My fifth choice | Test 5 | 2 | 0 | 0 | Then I should see the choice with the title "My first choice" And I should see the choice with the title "My second choice" And I should see the choice with the title "My third choice" @@ -57,9 +65,9 @@ Feature: Creating a new rating allocation, where new choices need to @javascript Scenario: Create a new rating alloation and add two additonal new choices, but delete two old and one new. When I add new choices with the values: - | title | explanation | maxsize | - | My fourth choice | Test 4 | 2 | - | My fifth choice | Test 5 | 2 | + | title | explanation | maxsize | minsize | optional | + | My fourth choice | Test 4 | 2 | 0 | 0 | + | My fifth choice | Test 5 | 2 | 0 | 0 | And I delete the choice with the title "My first choice" And I delete the choice with the title "My second choice" And I delete the choice with the title "My fifth choice" @@ -73,9 +81,11 @@ Feature: Creating a new rating allocation, where new choices need to Scenario: Create a new rating alloation and add an additonal new active choice. When I add a new choice with the values: | title | My fourth choice | - | explanation | Test 4 | - | maxsize | 1337 | - | active | true | + | explanation | Test 4 | + | maxsize | 1337 | + | active | true | + | minsize | 0 | + | optional | 0 | And I should see the choice with the title "My fourth choice" And the choice with name "My fourth choice" should have explanation being equal to "Test 4" And the choice with name "My fourth choice" should have maxsize being equal to 1337 @@ -85,9 +95,11 @@ Feature: Creating a new rating allocation, where new choices need to Scenario: Create a new rating alloation and add an additonal new inactive choice. When I add a new choice with the values: | title | My fourth choice | - | explanation | Test 4 | - | maxsize | 1337 | + | explanation | Test 4 | + | maxsize | 1337 | | active | false | + | minsize | 0 | + | optional | 0 | And I should see the choice with the title "My fourth choice" And the choice with name "My fourth choice" should have explanation being equal to "Test 4" And the choice with name "My fourth choice" should have maxsize being equal to 1337 @@ -96,20 +108,24 @@ Feature: Creating a new rating allocation, where new choices need to @javascript Scenario: Create a new rating alloation and add an additonal new inactive choice. Change the the choice to active. When I add a new choice with the values: - | title | My fourth choice | - | explanation | This is my discription | - | maxsize | 1231243 | - | active | false | + | title | My fourth choice | + | explanation | This is my discription | + | maxsize | 1231243 | + | active | false | + | minsize | 0 | + | optional | 0 | Then I set the choice with the title "My fourth choice" to active And I should see "My fourth choice" And the choice with name "My fourth choice" should be active Scenario: Create a new rating alloation and add an additonal new active choice. Change the the choice to inactive. When I add a new choice with the values: - | title | My fourth choice | - | explanation | This is my discription | - | maxsize | 1231243 | - | active | true | + | title | My fourth choice | + | explanation | This is my discription | + | maxsize | 1231243 | + | active | true | + | minsize | 0 | + | optional | 0 | Then I set the choice with the title "My fourth choice" to inactive And I should see "My fourth choice" And the choice with name "My fourth choice" should not be active diff --git a/tests/behat/ratings.feature b/tests/behat/ratings.feature index 374da14a..9e453341 100644 --- a/tests/behat/ratings.feature +++ b/tests/behat/ratings.feature @@ -6,16 +6,16 @@ Feature: When a student rates a rating should be saved and it should be possible | fullname | shortname | category | groupmode | | Course 1 | C1 | 0 | 1 | And the following "users" exist: - | username | firstname | lastname | email | - | teacher1 | Teacher | 1 | teacher1@example.com | - | student1 | Student | 1 | student1@example.com | + | username | firstname | lastname | email | + | teacher1 | Teacher | 1 | teacher1@example.com | + | student1 | Student | 1 | student1@example.com | And the following "course enrolments" exist: - | user | course | role | - | teacher1 | C1 | editingteacher | - | student1 | C1 | student | + | user | course | role | + | teacher1 | C1 | editingteacher | + | student1 | C1 | student | And the following "activities" exist: - | activity | course | idnumber | name | - | ratingallocate | C1 | ra1 | My Fair Allocation | + | activity | course | idnumber | name | + | ratingallocate | C1 | ra1 | My Fair Allocation | And I log in as "teacher1" And I am on "Course 1" course homepage with editing mode on And I follow "My Fair Allocation" @@ -23,15 +23,21 @@ Feature: When a student rates a rating should be saved and it should be possible And I add a new choice with the values: | title | My first choice | | explanation | Test 1 | - | maxsize | 2 | + | maxsize | 2 | + | minsize | 0 | + | optional | 0 | And I add a new choice with the values: | title | My second choice | | explanation | Test 2 | - | maxsize | 2 | + | maxsize | 2 | + | minsize | 0 | + | optional | 0 | And I add a new choice with the values: | title | My third choice | - | explanation | Test 3 | - | maxsize | 2 | + | explanation | Test 3 | + | maxsize | 2 | + | minsize | 0 | + | optional | 0 | And I log out @javascript From 1c7674f35a38b26d42e4553de9ca1967a50f1bea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Dagef=C3=B6rde?= Date: Mon, 18 Mar 2019 17:43:35 +0100 Subject: [PATCH 24/73] Add tests for subplugin management --- classes/algorithm.php | 25 +++++++-- ...tingallocate_algorithm_subplugins_test.php | 52 +++++++++++++++++++ 2 files changed, 73 insertions(+), 4 deletions(-) create mode 100644 tests/mod_ratingallocate_algorithm_subplugins_test.php diff --git a/classes/algorithm.php b/classes/algorithm.php index dcbf9406..5abe690d 100644 --- a/classes/algorithm.php +++ b/classes/algorithm.php @@ -85,9 +85,26 @@ public static function compute_target_function($ratings, $distribution) { * @return algorithm Algorithm instance */ public static function get_instance(string $name) { - $subplugins = \core_plugin_manager::instance()->get_plugins_of_type('raalgo'); - // TODO Check whether the specified plugin is installed. - $classname = '\raalgo_'.$name.'\algorithm_impl'; - return new $classname(); + $possible = self::get_available_algorithms(); + if (array_key_exists($name, $possible)) { + $classname = '\raalgo_' . $name . '\algorithm_impl'; + return new $classname(); + } else { + throw new \coding_exception('Tried to instantiate algorithm that is not installed or available.'); + } } + + /** + * Get the list of available algorithms + * @return string[] of the form Name -> Displayname + */ + public static function get_available_algorithms() { + $algorithms = \core_plugin_manager::instance()->get_plugins_of_type('raalgo'); + $result = array(); + foreach($algorithms as $algo) { + $result[$algo->name] = $algo->displayname; + } + return $result; + } + } \ No newline at end of file diff --git a/tests/mod_ratingallocate_algorithm_subplugins_test.php b/tests/mod_ratingallocate_algorithm_subplugins_test.php new file mode 100644 index 00000000..77e76b08 --- /dev/null +++ b/tests/mod_ratingallocate_algorithm_subplugins_test.php @@ -0,0 +1,52 @@ +. + +/** + * Privacy provider tests. + * + * @package mod_ratingallocate + * @copyright 2018 Tamara Gunkel + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +use core_privacy\local\metadata\collection; +use core_privacy\local\request\deletion_criteria; +use mod_ratingallocate\privacy\provider; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Privacy provider tests class. + * + * @package mod_ratingallocate + * @copyright 2018 Tamara Gunkel + * @group mod_ratingallocate + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class mod_ratingallocate_algorithm_subplugins_testcase extends basic_testcase { + + public function test_default_algorithms_present() { + $algorithms = \mod_ratingallocate\algorithm::get_available_algorithms(); + $this->assertGreaterThan(2, count($algorithms)); + $this->assertArrayHasKey('edmondskarp', $algorithms); + $this->assertArrayHasKey('fordfulkersonkoegel', $algorithms); + } + + public function test_loads_assignment_subtype() { + $algorithm = \mod_ratingallocate\algorithm::get_instance('edmondskarp'); + $this->assertInstanceOf(\mod_ratingallocate\algorithm::class, $algorithm); + } +} From 0530be0cea56363393c7b6bd05b674722ce398f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Dagef=C3=B6rde?= Date: Mon, 18 Mar 2019 17:51:34 +0100 Subject: [PATCH 25/73] Use gte instead of gt --- tests/mod_ratingallocate_algorithm_subplugins_test.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/mod_ratingallocate_algorithm_subplugins_test.php b/tests/mod_ratingallocate_algorithm_subplugins_test.php index 77e76b08..9c1204b8 100644 --- a/tests/mod_ratingallocate_algorithm_subplugins_test.php +++ b/tests/mod_ratingallocate_algorithm_subplugins_test.php @@ -40,7 +40,7 @@ class mod_ratingallocate_algorithm_subplugins_testcase extends basic_testcase { public function test_default_algorithms_present() { $algorithms = \mod_ratingallocate\algorithm::get_available_algorithms(); - $this->assertGreaterThan(2, count($algorithms)); + $this->assertGreaterThanOrEqual(2, count($algorithms)); $this->assertArrayHasKey('edmondskarp', $algorithms); $this->assertArrayHasKey('fordfulkersonkoegel', $algorithms); } From 6ae734580f8847982e61edf89010676d5ee36aaf Mon Sep 17 00:00:00 2001 From: Justus Dieckmann Date: Tue, 19 Mar 2019 10:36:14 +0100 Subject: [PATCH 26/73] added optional support to behat steps --- tests/behat/allocation_status.feature | 1 - tests/behat/behat_mod_ratingallocate.php | 26 ++++++++++++++++++++++++ tests/behat/defaultratings.feature | 2 -- tests/behat/mod_form.feature | 8 -------- tests/behat/ratings.feature | 3 --- 5 files changed, 26 insertions(+), 14 deletions(-) diff --git a/tests/behat/allocation_status.feature b/tests/behat/allocation_status.feature index 749e19de..a5fb0a59 100644 --- a/tests/behat/allocation_status.feature +++ b/tests/behat/allocation_status.feature @@ -29,7 +29,6 @@ Feature: Students should get status information according to their rating and th | explanation | Test | | maxsize | 1 | | minsize | 0 | - | optional | 0 | And I log out And I log in as "student1" And I am on "Course 1" course homepage diff --git a/tests/behat/behat_mod_ratingallocate.php b/tests/behat/behat_mod_ratingallocate.php index 1ec650e3..5a8f3ebd 100644 --- a/tests/behat/behat_mod_ratingallocate.php +++ b/tests/behat/behat_mod_ratingallocate.php @@ -32,6 +32,12 @@ public function i_set_the_values_of_the_choice_to(TableNode $choicedata) { } else { $this->execute('behat_mod_ratingallocate::i_uncheck_the_active_checkbox'); } + } else if($locator === 'optional') { + if ($value === 'true') { + $this->execute('behat_mod_ratingallocate::i_check_the_optional_checkbox'); + } else { + $this->execute('behat_mod_ratingallocate::i_uncheck_the_optional_checkbox'); + } } else { $this->execute('behat_forms::i_set_the_field_to', array($locator, $value)); } @@ -190,6 +196,26 @@ public function i_uncheck_the_active_checkbox() { $checkbox->uncheck(); } + /** + * Checks the optional checkbox. + * + * @Given /^I check the optional checkbox$/ + */ + public function i_check_the_optional_checkbox() { + $checkbox = $this->find_field("id_optional"); + $checkbox->check(); + } + + /** + * Unchecks the optional checkbox. + * + * @Given /^I uncheck the optional checkbox$/ + */ + public function i_uncheck_the_optional_checkbox() { + $checkbox = $this->find_field("id_optional"); + $checkbox->uncheck(); + } + /** * The choice with id should be active. * diff --git a/tests/behat/defaultratings.feature b/tests/behat/defaultratings.feature index d64e6410..7b3677f9 100644 --- a/tests/behat/defaultratings.feature +++ b/tests/behat/defaultratings.feature @@ -26,13 +26,11 @@ Feature: When a student starts a rating the default values of all choices | explanation | Test 1 | | maxsize | 2 | | minsize | 0 | - | optional | 0 | And I add a new choice with the values: | title | My second choice | | explanation | Test 1 | | maxsize | 2 | | minsize | 0 | - | optional | 0 | @javascript Scenario: The default rating is the max rating diff --git a/tests/behat/mod_form.feature b/tests/behat/mod_form.feature index 00446cb3..b9684197 100644 --- a/tests/behat/mod_form.feature +++ b/tests/behat/mod_form.feature @@ -25,19 +25,16 @@ Feature: Creating a new rating allocation, where new choices need to | explanation | Test 1 | | maxsize | 2 | | minsize | 0 | - | optional | 0 | And I add a new choice with the values: | title | My second choice | | explanation | Test 2 | | maxsize | 2 | | minsize | 0 | - | optional | 0 | And I add a new choice with the values: | title | My third choice | | explanation | Test 3 | | maxsize | 2 | | minsize | 0 | - | optional | 0 | Scenario: Create a new rating alloation and add an additonal new choice. Given I add a new choice with the values: @@ -45,7 +42,6 @@ Feature: Creating a new rating allocation, where new choices need to | explanation | Test 4 | | maxsize | 2 | | minsize | 0 | - | optional | 0 | Then I should see the choice with the title "My first choice" And I should see the choice with the title "My second choice" And I should see the choice with the title "My third choice" @@ -85,7 +81,6 @@ Feature: Creating a new rating allocation, where new choices need to | maxsize | 1337 | | active | true | | minsize | 0 | - | optional | 0 | And I should see the choice with the title "My fourth choice" And the choice with name "My fourth choice" should have explanation being equal to "Test 4" And the choice with name "My fourth choice" should have maxsize being equal to 1337 @@ -99,7 +94,6 @@ Feature: Creating a new rating allocation, where new choices need to | maxsize | 1337 | | active | false | | minsize | 0 | - | optional | 0 | And I should see the choice with the title "My fourth choice" And the choice with name "My fourth choice" should have explanation being equal to "Test 4" And the choice with name "My fourth choice" should have maxsize being equal to 1337 @@ -113,7 +107,6 @@ Feature: Creating a new rating allocation, where new choices need to | maxsize | 1231243 | | active | false | | minsize | 0 | - | optional | 0 | Then I set the choice with the title "My fourth choice" to active And I should see "My fourth choice" And the choice with name "My fourth choice" should be active @@ -125,7 +118,6 @@ Feature: Creating a new rating allocation, where new choices need to | maxsize | 1231243 | | active | true | | minsize | 0 | - | optional | 0 | Then I set the choice with the title "My fourth choice" to inactive And I should see "My fourth choice" And the choice with name "My fourth choice" should not be active diff --git a/tests/behat/ratings.feature b/tests/behat/ratings.feature index 9e453341..59c89943 100644 --- a/tests/behat/ratings.feature +++ b/tests/behat/ratings.feature @@ -25,19 +25,16 @@ Feature: When a student rates a rating should be saved and it should be possible | explanation | Test 1 | | maxsize | 2 | | minsize | 0 | - | optional | 0 | And I add a new choice with the values: | title | My second choice | | explanation | Test 2 | | maxsize | 2 | | minsize | 0 | - | optional | 0 | And I add a new choice with the values: | title | My third choice | | explanation | Test 3 | | maxsize | 2 | | minsize | 0 | - | optional | 0 | And I log out @javascript From b44f1a1fef52443e27eff3f8c3dd162b2b4feb12 Mon Sep 17 00:00:00 2001 From: NinaHerrmann Date: Tue, 19 Mar 2019 10:44:40 +0100 Subject: [PATCH 27/73] added minsize checkbox --- mod_form.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mod_form.php b/mod_form.php index 07cb0738..18e0b249 100644 --- a/mod_form.php +++ b/mod_form.php @@ -85,6 +85,10 @@ public function definition() { // Adding the standard "intro" and "introformat" fields. $this->standard_intro_elements(); + // ------------------------------------------------------------------------------- + $elementname = 'minsize'; + $mform->addElement('advcheckbox', $elementname, get_string('minsize', self::MOD_NAME)); + // ------------------------------------------------------------------------------- $elementname = 'strategy'; // Define options for select. From ae36c39d86bd6baad253ff7408fae2ec3358811b Mon Sep 17 00:00:00 2001 From: NinaHerrmann Date: Tue, 19 Mar 2019 10:52:45 +0100 Subject: [PATCH 28/73] added optional checkbox --- mod_form.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/mod_form.php b/mod_form.php index 18e0b249..62f33724 100644 --- a/mod_form.php +++ b/mod_form.php @@ -86,8 +86,12 @@ public function definition() { $this->standard_intro_elements(); // ------------------------------------------------------------------------------- - $elementname = 'minsize'; - $mform->addElement('advcheckbox', $elementname, get_string('minsize', self::MOD_NAME)); + // TODO hilfe! + // TODO darstellung nebeneinander. + $elementname = 'generaloption_minsize'; + $mform->addElement('advcheckbox', $elementname, get_string('checkbox_' . $elementname, self::MOD_NAME)); + $elementname = 'generaloption_optional'; + $mform->addElement('advcheckbox', $elementname, get_string('checkbox_' . $elementname, self::MOD_NAME)); // ------------------------------------------------------------------------------- $elementname = 'strategy'; From a588984117680892388dfb794d0dce39a7f38bf8 Mon Sep 17 00:00:00 2001 From: Justus Dieckmann Date: Tue, 19 Mar 2019 11:00:15 +0100 Subject: [PATCH 29/73] testing checkbox for optional setting --- tests/behat/behat_mod_ratingallocate.php | 33 ++++++++++++++++++++++++ tests/behat/defaultratings.feature | 4 +-- tests/behat/mod_form.feature | 7 ++++- 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/tests/behat/behat_mod_ratingallocate.php b/tests/behat/behat_mod_ratingallocate.php index 5a8f3ebd..d26961bd 100644 --- a/tests/behat/behat_mod_ratingallocate.php +++ b/tests/behat/behat_mod_ratingallocate.php @@ -249,6 +249,39 @@ public function the_choice_should_not_be_active($title) { } } + /** + * The choice with id should be optional. + * + * @Then /^the choice with name "([^"]*)" should be optional$/ + * + * @throws ExpectationException + * @param string $title title of the choice + */ + public function the_choice_should_be_optional($title) { + $choice = $this->get_choice($title); + if (!$choice->optional) { + throw new ExpectationException('The choice "' . $title . + '" should be optional.', + $this->getSession()); + } + } + + /** + * The choice with id should not be optional. + * + * @Then /^the choice with name "([^"]*)" should not be optional$/ + * + * @throws ExpectationException + * @param string $title title of the choice + */ + public function the_choice_should_not_be_optional($title) { + $choice = $this->get_choice($title); + if ($choice->optional) { + throw new ExpectationException('The choice "' . $title. '" should not be optional', + $this->getSession()); + } + } + /** * * diff --git a/tests/behat/defaultratings.feature b/tests/behat/defaultratings.feature index 7b3677f9..fcf1e4e6 100644 --- a/tests/behat/defaultratings.feature +++ b/tests/behat/defaultratings.feature @@ -46,7 +46,7 @@ Feature: When a student starts a rating the default values of all choices | My first choice | 4 | | My second choice | 4 | - @javascript @wip + @javascript Scenario: The default rating should be changeable to a medium rating And I navigate to "Edit settings" in current page administration And I select "strategy_lickert" from the "strategy" singleselect @@ -61,7 +61,7 @@ Feature: When a student starts a rating the default values of all choices | My first choice | 3 | | My second choice | 3 | - @javascript @wip + @javascript Scenario: The default rating should be changeable to the lowest rating And I navigate to "Edit settings" in current page administration And I select "strategy_lickert" from the "strategy" singleselect diff --git a/tests/behat/mod_form.feature b/tests/behat/mod_form.feature index b9684197..5d3815dd 100644 --- a/tests/behat/mod_form.feature +++ b/tests/behat/mod_form.feature @@ -74,6 +74,7 @@ Feature: Creating a new rating allocation, where new choices need to And I should see the choice with the title "My fourth choice" And I should not see the choice with the title "My fifth choice" + @wip Scenario: Create a new rating alloation and add an additonal new active choice. When I add a new choice with the values: | title | My fourth choice | @@ -81,12 +82,14 @@ Feature: Creating a new rating allocation, where new choices need to | maxsize | 1337 | | active | true | | minsize | 0 | + | optional | true | And I should see the choice with the title "My fourth choice" And the choice with name "My fourth choice" should have explanation being equal to "Test 4" And the choice with name "My fourth choice" should have maxsize being equal to 1337 And the choice with name "My fourth choice" should be active + And the choice with name "My fourth choice" should be optional - @javascript + @javascript @wip Scenario: Create a new rating alloation and add an additonal new inactive choice. When I add a new choice with the values: | title | My fourth choice | @@ -94,10 +97,12 @@ Feature: Creating a new rating allocation, where new choices need to | maxsize | 1337 | | active | false | | minsize | 0 | + | optional | false | And I should see the choice with the title "My fourth choice" And the choice with name "My fourth choice" should have explanation being equal to "Test 4" And the choice with name "My fourth choice" should have maxsize being equal to 1337 And the choice with name "My fourth choice" should not be active + And the choice with name "My fourth choice" should not be optional @javascript Scenario: Create a new rating alloation and add an additonal new inactive choice. Change the the choice to active. From 8c0747fdc7006e9bebea9420670e9de37f4496fd Mon Sep 17 00:00:00 2001 From: Justus Dieckmann Date: Tue, 19 Mar 2019 11:04:53 +0100 Subject: [PATCH 30/73] not wip anymore --- tests/behat/mod_form.feature | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/behat/mod_form.feature b/tests/behat/mod_form.feature index 5d3815dd..acdc2c73 100644 --- a/tests/behat/mod_form.feature +++ b/tests/behat/mod_form.feature @@ -74,7 +74,6 @@ Feature: Creating a new rating allocation, where new choices need to And I should see the choice with the title "My fourth choice" And I should not see the choice with the title "My fifth choice" - @wip Scenario: Create a new rating alloation and add an additonal new active choice. When I add a new choice with the values: | title | My fourth choice | @@ -89,7 +88,7 @@ Feature: Creating a new rating allocation, where new choices need to And the choice with name "My fourth choice" should be active And the choice with name "My fourth choice" should be optional - @javascript @wip + @javascript Scenario: Create a new rating alloation and add an additonal new inactive choice. When I add a new choice with the values: | title | My fourth choice | From 1b4fa96a5d395d4fa6c3ad1f53c6bfe46ede04bd Mon Sep 17 00:00:00 2001 From: NinaHerrmann Date: Tue, 19 Mar 2019 11:08:39 +0100 Subject: [PATCH 31/73] edit db schema --- db/install.xml | 2 ++ db/upgrade.php | 23 +++++++++++++++++++++++ version.php | 2 +- 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/db/install.xml b/db/install.xml index b5a7b6f5..68050398 100644 --- a/db/install.xml +++ b/db/install.xml @@ -16,6 +16,8 @@ + + diff --git a/db/upgrade.php b/db/upgrade.php index 14875d7f..e9976c98 100644 --- a/db/upgrade.php +++ b/db/upgrade.php @@ -131,6 +131,29 @@ function xmldb_ratingallocate_upgrade($oldversion) { } } + + if ($oldversion < 2019031900) { + // Define field generaloption_minsize to be added to ratingallocate. + $table = new xmldb_table('ratingallocate'); + $field = new xmldb_field('generaloption_minsize', XMLDB_TYPE_INTEGER, '4', null, XMLDB_NOTNULL, null, '0', 'setting'); + + // Conditionally launch add field generaloption_minsize. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Define field generaloption_optional to be added to ratingallocate. + $table = new xmldb_table('ratingallocate'); + $field = new xmldb_field('generaloption_optional', XMLDB_TYPE_INTEGER, '4', null, XMLDB_NOTNULL, null, '0', 'generaloption_minsize'); + + // Conditionally launch add field generaloption_optional. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Ratingallocate savepoint reached. + upgrade_mod_savepoint(true, 2019031900, 'ratingallocate'); + } return true; } diff --git a/version.php b/version.php index 59c7cb50..54a5f8de 100644 --- a/version.php +++ b/version.php @@ -26,7 +26,7 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2019031800; // The current module version (Date: YYYYMMDDXX) +$plugin->version = 2019031900; // The current module version (Date: YYYYMMDDXX) $plugin->requires = 2017111300; // Requires this Moodle version $plugin->maturity = MATURITY_STABLE; $plugin->release = 'v3.6-r1'; From 5347faf68d1ae7251b83a8ee19a7e32b3db944f7 Mon Sep 17 00:00:00 2001 From: NinaHerrmann Date: Tue, 19 Mar 2019 11:08:55 +0100 Subject: [PATCH 32/73] Add language strings for general options --- lang/en/ratingallocate.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lang/en/ratingallocate.php b/lang/en/ratingallocate.php index 039047ce..2d57618d 100644 --- a/lang/en/ratingallocate.php +++ b/lang/en/ratingallocate.php @@ -206,6 +206,8 @@ $string['publishdate'] = 'Estimated publication date'; $string['runalgorithmbycron'] = 'Automatic allocation after rating period'; $string['runalgorithmbycron_help'] = 'Automatically runs the allocation algorithm after the rating period ended. However, the results have to be published manually.'; +$string['checkbox_generaloption_minsize'] = 'Choices requiring a minimum number of participants'; +$string['checkbox_generaloption_optional'] = 'Some choices are optional'; $string['select_strategy'] = 'Rating strategy'; $string['select_strategy_help'] = 'Choose a rating strategy: From 766e7fddb2eeb6bd547ba173d54b4dd0950d2979 Mon Sep 17 00:00:00 2001 From: NinaHerrmann Date: Tue, 19 Mar 2019 11:33:21 +0100 Subject: [PATCH 33/73] added abstract function for getting supported features in class algorithm --- classes/algorithm.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/classes/algorithm.php b/classes/algorithm.php index dcbf9406..4e2e47a3 100644 --- a/classes/algorithm.php +++ b/classes/algorithm.php @@ -23,6 +23,12 @@ abstract class algorithm { public abstract function get_name(); + + /** + * Expected return value is an array with min and opt as key and 1 or 0 as supported or not supported. + * @return array + */ + public abstract function get_supported_features(); protected abstract function compute_distribution($choicerecords, $ratings, $usercount); /** From 15c1702d332e6db26fbfed97ffdafe54db4dafa9 Mon Sep 17 00:00:00 2001 From: NinaHerrmann Date: Tue, 19 Mar 2019 11:36:49 +0100 Subject: [PATCH 34/73] implemented get supported features in subplugins --- algorithm/edmondskarp/classes/algorithm_impl.php | 8 ++++++++ algorithm/fordfulkersonkoegel/classes/algorithm_impl.php | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/algorithm/edmondskarp/classes/algorithm_impl.php b/algorithm/edmondskarp/classes/algorithm_impl.php index ecbb74b0..1b0fcf78 100644 --- a/algorithm/edmondskarp/classes/algorithm_impl.php +++ b/algorithm/edmondskarp/classes/algorithm_impl.php @@ -284,4 +284,12 @@ protected function augment_flow($path) { } } } + + /** + * Expected return value is an array with min and opt as key and 1 or 0 as supported or not supported. + * @return array + */ + public function get_supported_features() { + return ['min' => 0, 'opt' => 0]; + } } diff --git a/algorithm/fordfulkersonkoegel/classes/algorithm_impl.php b/algorithm/fordfulkersonkoegel/classes/algorithm_impl.php index 9382d64a..d1f56026 100644 --- a/algorithm/fordfulkersonkoegel/classes/algorithm_impl.php +++ b/algorithm/fordfulkersonkoegel/classes/algorithm_impl.php @@ -298,4 +298,12 @@ protected function augment_flow($path) { } } } + + /** + * Expected return value is an array with min and opt as key and 1 or 0 as supported or not supported. + * @return array + */ + public function get_supported_features() { + return ['min' => 0, 'opt' => 0]; + } } From f500122f7712dc858d6554ead7c18c17f37ebe9a Mon Sep 17 00:00:00 2001 From: NinaHerrmann Date: Tue, 19 Mar 2019 11:50:28 +0100 Subject: [PATCH 35/73] Bumped version number --- db/upgrade.php | 4 ++-- version.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/db/upgrade.php b/db/upgrade.php index e9976c98..80bc3971 100644 --- a/db/upgrade.php +++ b/db/upgrade.php @@ -132,7 +132,7 @@ function xmldb_ratingallocate_upgrade($oldversion) { } - if ($oldversion < 2019031900) { + if ($oldversion < 2019031901) { // Define field generaloption_minsize to be added to ratingallocate. $table = new xmldb_table('ratingallocate'); $field = new xmldb_field('generaloption_minsize', XMLDB_TYPE_INTEGER, '4', null, XMLDB_NOTNULL, null, '0', 'setting'); @@ -152,7 +152,7 @@ function xmldb_ratingallocate_upgrade($oldversion) { } // Ratingallocate savepoint reached. - upgrade_mod_savepoint(true, 2019031900, 'ratingallocate'); + upgrade_mod_savepoint(true, 2019031901, 'ratingallocate'); } return true; diff --git a/version.php b/version.php index 54a5f8de..484aabda 100644 --- a/version.php +++ b/version.php @@ -26,7 +26,7 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2019031900; // The current module version (Date: YYYYMMDDXX) +$plugin->version = 2019031901; // The current module version (Date: YYYYMMDDXX) $plugin->requires = 2017111300; // Requires this Moodle version $plugin->maturity = MATURITY_STABLE; $plugin->release = 'v3.6-r1'; From 698d21112b2654251968b7d3397595b563c00481 Mon Sep 17 00:00:00 2001 From: NinaHerrmann Date: Tue, 19 Mar 2019 10:44:40 +0100 Subject: [PATCH 36/73] added minsize checkbox --- mod_form.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mod_form.php b/mod_form.php index 07cb0738..18e0b249 100644 --- a/mod_form.php +++ b/mod_form.php @@ -85,6 +85,10 @@ public function definition() { // Adding the standard "intro" and "introformat" fields. $this->standard_intro_elements(); + // ------------------------------------------------------------------------------- + $elementname = 'minsize'; + $mform->addElement('advcheckbox', $elementname, get_string('minsize', self::MOD_NAME)); + // ------------------------------------------------------------------------------- $elementname = 'strategy'; // Define options for select. From 439c44e658f971045fedb1a953b66ccec4feae14 Mon Sep 17 00:00:00 2001 From: NinaHerrmann Date: Tue, 19 Mar 2019 10:52:45 +0100 Subject: [PATCH 37/73] added optional checkbox --- mod_form.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/mod_form.php b/mod_form.php index 18e0b249..62f33724 100644 --- a/mod_form.php +++ b/mod_form.php @@ -86,8 +86,12 @@ public function definition() { $this->standard_intro_elements(); // ------------------------------------------------------------------------------- - $elementname = 'minsize'; - $mform->addElement('advcheckbox', $elementname, get_string('minsize', self::MOD_NAME)); + // TODO hilfe! + // TODO darstellung nebeneinander. + $elementname = 'generaloption_minsize'; + $mform->addElement('advcheckbox', $elementname, get_string('checkbox_' . $elementname, self::MOD_NAME)); + $elementname = 'generaloption_optional'; + $mform->addElement('advcheckbox', $elementname, get_string('checkbox_' . $elementname, self::MOD_NAME)); // ------------------------------------------------------------------------------- $elementname = 'strategy'; From 80b954974dc795fe401cb003eff749c98a71a276 Mon Sep 17 00:00:00 2001 From: NinaHerrmann Date: Tue, 19 Mar 2019 12:10:51 +0100 Subject: [PATCH 38/73] merged projekttage --- db/install.xml | 2 ++ db/upgrade.php | 23 +++++++++++++++++++++-- version.php | 2 +- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/db/install.xml b/db/install.xml index e7a3795c..88ea4486 100644 --- a/db/install.xml +++ b/db/install.xml @@ -16,6 +16,8 @@ + + diff --git a/db/upgrade.php b/db/upgrade.php index b1638bb0..39517459 100644 --- a/db/upgrade.php +++ b/db/upgrade.php @@ -142,16 +142,35 @@ function xmldb_ratingallocate_upgrade($oldversion) { if (!$dbman->field_exists($table, $field)) { $dbman->add_field($table, $field); } - $field = new xmldb_field('optional', XMLDB_TYPE_INTEGER, '4', null, null, null, '0', 'active'); // Conditionally launch add field optional. if (!$dbman->field_exists($table, $field)) { $dbman->add_field($table, $field); } + upgrade_mod_savepoint(true, 2019031803, 'ratingallocate'); + } + + if ($oldversion < 2019031900) { + // Define field generaloption_minsize to be added to ratingallocate. + $table = new xmldb_table('ratingallocate'); + $field = new xmldb_field('generaloption_minsize', XMLDB_TYPE_INTEGER, '4', null, XMLDB_NOTNULL, null, '0', 'setting'); + + // Conditionally launch add field generaloption_minsize. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + // Define field generaloption_optional to be added to ratingallocate. + $table = new xmldb_table('ratingallocate'); + $field = new xmldb_field('generaloption_optional', XMLDB_TYPE_INTEGER, '4', null, XMLDB_NOTNULL, null, '0', 'generaloption_minsize'); + + // Conditionally launch add field generaloption_optional. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } // Ratingallocate savepoint reached. - upgrade_mod_savepoint(true, 2019031803, 'ratingallocate'); + upgrade_mod_savepoint(true, 2019031900, 'ratingallocate'); } return true; diff --git a/version.php b/version.php index e188c911..54a5f8de 100644 --- a/version.php +++ b/version.php @@ -26,7 +26,7 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2019031804; // The current module version (Date: YYYYMMDDXX) +$plugin->version = 2019031900; // The current module version (Date: YYYYMMDDXX) $plugin->requires = 2017111300; // Requires this Moodle version $plugin->maturity = MATURITY_STABLE; $plugin->release = 'v3.6-r1'; From b1df3bcb96925b80b16d808ba445e571c7e3f3ab Mon Sep 17 00:00:00 2001 From: NinaHerrmann Date: Tue, 19 Mar 2019 11:08:55 +0100 Subject: [PATCH 39/73] Add language strings for general options --- lang/en/ratingallocate.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lang/en/ratingallocate.php b/lang/en/ratingallocate.php index ce409f09..3fa2bd81 100644 --- a/lang/en/ratingallocate.php +++ b/lang/en/ratingallocate.php @@ -209,6 +209,8 @@ $string['publishdate'] = 'Estimated publication date'; $string['runalgorithmbycron'] = 'Automatic allocation after rating period'; $string['runalgorithmbycron_help'] = 'Automatically runs the allocation algorithm after the rating period ended. However, the results have to be published manually.'; +$string['checkbox_generaloption_minsize'] = 'Choices requiring a minimum number of participants'; +$string['checkbox_generaloption_optional'] = 'Some choices are optional'; $string['select_strategy'] = 'Rating strategy'; $string['select_strategy_help'] = 'Choose a rating strategy: From 70281a7ea97d83923f8f5a5b299b534274e3894a Mon Sep 17 00:00:00 2001 From: NinaHerrmann Date: Tue, 19 Mar 2019 11:50:28 +0100 Subject: [PATCH 40/73] Bumped version number --- db/upgrade.php | 4 ++-- version.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/db/upgrade.php b/db/upgrade.php index 39517459..08b0e3ca 100644 --- a/db/upgrade.php +++ b/db/upgrade.php @@ -151,7 +151,7 @@ function xmldb_ratingallocate_upgrade($oldversion) { upgrade_mod_savepoint(true, 2019031803, 'ratingallocate'); } - if ($oldversion < 2019031900) { + if ($oldversion < 2019031901) { // Define field generaloption_minsize to be added to ratingallocate. $table = new xmldb_table('ratingallocate'); $field = new xmldb_field('generaloption_minsize', XMLDB_TYPE_INTEGER, '4', null, XMLDB_NOTNULL, null, '0', 'setting'); @@ -170,7 +170,7 @@ function xmldb_ratingallocate_upgrade($oldversion) { } // Ratingallocate savepoint reached. - upgrade_mod_savepoint(true, 2019031900, 'ratingallocate'); + upgrade_mod_savepoint(true, 2019031901, 'ratingallocate'); } return true; diff --git a/version.php b/version.php index 54a5f8de..484aabda 100644 --- a/version.php +++ b/version.php @@ -26,7 +26,7 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2019031900; // The current module version (Date: YYYYMMDDXX) +$plugin->version = 2019031901; // The current module version (Date: YYYYMMDDXX) $plugin->requires = 2017111300; // Requires this Moodle version $plugin->maturity = MATURITY_STABLE; $plugin->release = 'v3.6-r1'; From 566e4040277b536cc818703895a0a524e4665e68 Mon Sep 17 00:00:00 2001 From: NinaHerrmann Date: Tue, 19 Mar 2019 14:23:23 +0100 Subject: [PATCH 41/73] Form: add algorithm options --- mod_form.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/mod_form.php b/mod_form.php index 62f33724..6b81e962 100644 --- a/mod_form.php +++ b/mod_form.php @@ -38,6 +38,7 @@ class mod_ratingallocate_mod_form extends moodleform_mod { const CHOICE_PLACEHOLDER_IDENTIFIER = 'placeholder_for_choices'; const STRATEGY_OPTIONS = 'strategyopt'; const STRATEGY_OPTIONS_PLACEHOLDER = 'placeholder_strategyopt'; + const ALGORITHM_OPTIONS_PLACEHOLDER = 'placeholder_algorithmopt'; private $newchoicecounter = 0; private $msgerrorrequired; @@ -140,6 +141,16 @@ public function definition() { $mform->addElement('static', self::STRATEGY_OPTIONS_PLACEHOLDER.'[' . $strategy . ']', '', ''); } + $headerid = 'algorithm_fieldset'; + $mform->addElement('header', $headerid, get_string('algorithmoptions', ratingallocate_MOD_NAME)); + $mform->setExpanded($headerid); + + foreach(\mod_ratingallocate\algorithm::get_available_algorithms() as $key => $value){ + $features = \mod_ratingallocate\algorithm::get_instance($key)->get_supported_features(); + $mform->addElement('radio', 'yesno', '', $value, 1, array()); + $mform->disabledIf("yesno[$value]" , 'generaloption_minsize', 'eq', NULL); + } + // Add standard elements, common to all modules. $this->standard_coursemodule_elements(); From d6db13aa68f5864944806c58a17abbdfb49e06d9 Mon Sep 17 00:00:00 2001 From: NinaHerrmann Date: Tue, 19 Mar 2019 14:23:59 +0100 Subject: [PATCH 42/73] Algorithms: return supported features statically --- algorithm/edmondskarp/classes/algorithm_impl.php | 2 +- algorithm/fordfulkersonkoegel/classes/algorithm_impl.php | 2 +- classes/algorithm.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/algorithm/edmondskarp/classes/algorithm_impl.php b/algorithm/edmondskarp/classes/algorithm_impl.php index 1b0fcf78..00207d21 100644 --- a/algorithm/edmondskarp/classes/algorithm_impl.php +++ b/algorithm/edmondskarp/classes/algorithm_impl.php @@ -289,7 +289,7 @@ protected function augment_flow($path) { * Expected return value is an array with min and opt as key and 1 or 0 as supported or not supported. * @return array */ - public function get_supported_features() { + public static function get_supported_features() { return ['min' => 0, 'opt' => 0]; } } diff --git a/algorithm/fordfulkersonkoegel/classes/algorithm_impl.php b/algorithm/fordfulkersonkoegel/classes/algorithm_impl.php index d1f56026..611a1c05 100644 --- a/algorithm/fordfulkersonkoegel/classes/algorithm_impl.php +++ b/algorithm/fordfulkersonkoegel/classes/algorithm_impl.php @@ -303,7 +303,7 @@ protected function augment_flow($path) { * Expected return value is an array with min and opt as key and 1 or 0 as supported or not supported. * @return array */ - public function get_supported_features() { + public static function get_supported_features() { return ['min' => 0, 'opt' => 0]; } } diff --git a/classes/algorithm.php b/classes/algorithm.php index 73e1603d..d7d46c81 100644 --- a/classes/algorithm.php +++ b/classes/algorithm.php @@ -28,7 +28,7 @@ public abstract function get_name(); * Expected return value is an array with min and opt as key and 1 or 0 as supported or not supported. * @return array */ - public abstract function get_supported_features(); + public static abstract function get_supported_features(); protected abstract function compute_distribution($choicerecords, $ratings, $usercount); /** From b18991b01098d113a5bef2f736915117e137bb9f Mon Sep 17 00:00:00 2001 From: NinaHerrmann Date: Tue, 19 Mar 2019 14:33:12 +0100 Subject: [PATCH 43/73] Form: add language string for section --- lang/en/ratingallocate.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lang/en/ratingallocate.php b/lang/en/ratingallocate.php index 3fa2bd81..dddb3b49 100644 --- a/lang/en/ratingallocate.php +++ b/lang/en/ratingallocate.php @@ -223,6 +223,8 @@ $string['strategy_not_specified'] = 'You have to select a strategy.'; $string['strategyspecificoptions'] = 'Strategy specific options'; +$string['algorithmoptions'] = 'Algorithm selection'; + $string['err_required'] = 'You need to provide a value for this field.'; $string['err_minimum'] = 'The minimum value for this field is {$a}.'; $string['err_maximum'] = 'The maximum value for this field is {$a}.'; From 33e13884b16e3be0ffae04f534aaeb580b18fb09 Mon Sep 17 00:00:00 2001 From: NinaHerrmann Date: Tue, 19 Mar 2019 14:35:08 +0100 Subject: [PATCH 44/73] Form: set algorithm as required --- mod_form.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mod_form.php b/mod_form.php index 6b81e962..68686a17 100644 --- a/mod_form.php +++ b/mod_form.php @@ -147,9 +147,10 @@ public function definition() { foreach(\mod_ratingallocate\algorithm::get_available_algorithms() as $key => $value){ $features = \mod_ratingallocate\algorithm::get_instance($key)->get_supported_features(); - $mform->addElement('radio', 'yesno', '', $value, 1, array()); - $mform->disabledIf("yesno[$value]" , 'generaloption_minsize', 'eq', NULL); + $mform->addElement('radio', 'algorithm', '', $value, 1, array()); + $mform->disabledIf("algorithm[$value]" , 'generaloption_minsize', 'eq', 1); } + $mform->addRule('algorithm', get_string('err_required', 'form') , 'required', null, 'server'); // Add standard elements, common to all modules. $this->standard_coursemodule_elements(); From bc8472e39699d6a4b5597a062d22a869762d200e Mon Sep 17 00:00:00 2001 From: NinaHerrmann Date: Tue, 19 Mar 2019 14:40:34 +0100 Subject: [PATCH 45/73] Algorithm: changed interface for supported features --- algorithm/edmondskarp/classes/algorithm_impl.php | 6 +++--- algorithm/fordfulkersonkoegel/classes/algorithm_impl.php | 6 +++--- classes/algorithm.php | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/algorithm/edmondskarp/classes/algorithm_impl.php b/algorithm/edmondskarp/classes/algorithm_impl.php index 00207d21..972ee669 100644 --- a/algorithm/edmondskarp/classes/algorithm_impl.php +++ b/algorithm/edmondskarp/classes/algorithm_impl.php @@ -286,10 +286,10 @@ protected function augment_flow($path) { } /** - * Expected return value is an array with min and opt as key and 1 or 0 as supported or not supported. - * @return array + * Supports neither min size nor optional. + * @return bool[] */ public static function get_supported_features() { - return ['min' => 0, 'opt' => 0]; + return ['min' => false, 'opt' => false]; } } diff --git a/algorithm/fordfulkersonkoegel/classes/algorithm_impl.php b/algorithm/fordfulkersonkoegel/classes/algorithm_impl.php index 611a1c05..50c6ccae 100644 --- a/algorithm/fordfulkersonkoegel/classes/algorithm_impl.php +++ b/algorithm/fordfulkersonkoegel/classes/algorithm_impl.php @@ -300,10 +300,10 @@ protected function augment_flow($path) { } /** - * Expected return value is an array with min and opt as key and 1 or 0 as supported or not supported. - * @return array + * Supports neither min size nor optional. + * @return bool[] */ public static function get_supported_features() { - return ['min' => 0, 'opt' => 0]; + return ['min' => false, 'opt' => false]; } } diff --git a/classes/algorithm.php b/classes/algorithm.php index d7d46c81..84f86c9b 100644 --- a/classes/algorithm.php +++ b/classes/algorithm.php @@ -25,8 +25,8 @@ abstract class algorithm { public abstract function get_name(); /** - * Expected return value is an array with min and opt as key and 1 or 0 as supported or not supported. - * @return array + * Expected return value is an array with min and opt as key and true or false as supported or not supported. + * @return bool[] */ public static abstract function get_supported_features(); protected abstract function compute_distribution($choicerecords, $ratings, $usercount); From 0c81adaca0accb1a093027531f621232e4768bf0 Mon Sep 17 00:00:00 2001 From: NinaHerrmann Date: Tue, 19 Mar 2019 14:41:04 +0100 Subject: [PATCH 46/73] Form: disable depending on supported features --- mod_form.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mod_form.php b/mod_form.php index 68686a17..e53e19c3 100644 --- a/mod_form.php +++ b/mod_form.php @@ -148,7 +148,9 @@ public function definition() { foreach(\mod_ratingallocate\algorithm::get_available_algorithms() as $key => $value){ $features = \mod_ratingallocate\algorithm::get_instance($key)->get_supported_features(); $mform->addElement('radio', 'algorithm', '', $value, 1, array()); - $mform->disabledIf("algorithm[$value]" , 'generaloption_minsize', 'eq', 1); + if (!$features['min']) { + $mform->disabledIf("algorithm[$value]" , 'generaloption_minsize', 'eq', 1); + } } $mform->addRule('algorithm', get_string('err_required', 'form') , 'required', null, 'server'); From 9ec259f561f76cf664b1188073502f8a9d3e22d2 Mon Sep 17 00:00:00 2001 From: NinaHerrmann Date: Tue, 19 Mar 2019 15:05:18 +0100 Subject: [PATCH 47/73] Form: send algorithm name and disable does not work. --- mod_form.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mod_form.php b/mod_form.php index e53e19c3..3d38b3e3 100644 --- a/mod_form.php +++ b/mod_form.php @@ -147,9 +147,10 @@ public function definition() { foreach(\mod_ratingallocate\algorithm::get_available_algorithms() as $key => $value){ $features = \mod_ratingallocate\algorithm::get_instance($key)->get_supported_features(); - $mform->addElement('radio', 'algorithm', '', $value, 1, array()); + $mform->addElement('radio', 'algorithm', '', $value, $key, array()); if (!$features['min']) { - $mform->disabledIf("algorithm[$value]" , 'generaloption_minsize', 'eq', 1); + // TODO: does not work for individual radiobuttons. Needs JS check. + $mform->disabledIf("algorithm_$key" , 'generaloption_minsize', 'checked'); } } $mform->addRule('algorithm', get_string('err_required', 'form') , 'required', null, 'server'); From a2e5cf6c49262372b3f2f2e23efcac4980387874 Mon Sep 17 00:00:00 2001 From: NinaHerrmann Date: Tue, 19 Mar 2019 15:41:14 +0100 Subject: [PATCH 48/73] algorithms: verify that supported_features return correct structure --- tests/mod_ratingallocate_algorithm_subplugins_test.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/mod_ratingallocate_algorithm_subplugins_test.php b/tests/mod_ratingallocate_algorithm_subplugins_test.php index 9c1204b8..8a444104 100644 --- a/tests/mod_ratingallocate_algorithm_subplugins_test.php +++ b/tests/mod_ratingallocate_algorithm_subplugins_test.php @@ -49,4 +49,12 @@ public function test_loads_assignment_subtype() { $algorithm = \mod_ratingallocate\algorithm::get_instance('edmondskarp'); $this->assertInstanceOf(\mod_ratingallocate\algorithm::class, $algorithm); } + + public function test_algorithm_supported_features() { + $algorithm = \mod_ratingallocate\algorithm::get_instance('edmondskarp'); + $supports = $algorithm->get_supported_features(); + $this->assertContainsOnlyInstancesOf('bool', $supports); + $this->assertArrayHasKey('min', $supports); + $this->assertArrayHasKey('opt', $supports); + } } From 0a4a566bb1093d4748427b75fb2ab9f1de8a8dd3 Mon Sep 17 00:00:00 2001 From: NinaHerrmann Date: Tue, 19 Mar 2019 15:51:20 +0100 Subject: [PATCH 49/73] form: use local constant instead of global one --- mod_form.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mod_form.php b/mod_form.php index 3d38b3e3..1295cf30 100644 --- a/mod_form.php +++ b/mod_form.php @@ -124,7 +124,7 @@ public function definition() { $mform->setDefault($elementname, 1); $headerid = 'strategy_fieldset'; - $mform->addElement('header', $headerid, get_string('strategyspecificoptions', ratingallocate_MOD_NAME)); + $mform->addElement('header', $headerid, get_string('strategyspecificoptions', self::MOD_NAME)); $mform->setExpanded($headerid); foreach (\strategymanager::get_strategies() as $strategy) { @@ -142,7 +142,7 @@ public function definition() { } $headerid = 'algorithm_fieldset'; - $mform->addElement('header', $headerid, get_string('algorithmoptions', ratingallocate_MOD_NAME)); + $mform->addElement('header', $headerid, get_string('algorithmoptions', self::MOD_NAME)); $mform->setExpanded($headerid); foreach(\mod_ratingallocate\algorithm::get_available_algorithms() as $key => $value){ From 8e76b9e345992d03348845f9aa8178462af1b193 Mon Sep 17 00:00:00 2001 From: NinaHerrmann Date: Tue, 19 Mar 2019 16:05:40 +0100 Subject: [PATCH 50/73] form: validate choice of algorithm --- lang/en/ratingallocate.php | 3 +++ mod_form.php | 21 +++++++++++++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/lang/en/ratingallocate.php b/lang/en/ratingallocate.php index dddb3b49..750981dc 100644 --- a/lang/en/ratingallocate.php +++ b/lang/en/ratingallocate.php @@ -224,6 +224,9 @@ $string['strategyspecificoptions'] = 'Strategy specific options'; $string['algorithmoptions'] = 'Algorithm selection'; +$string['algorithm_does_not_support_minsize'] = 'The selected algorithm does not work with the "minimum number of participants" option.'; +$string['algorithm_does_not_support_optional'] = 'The selected algorithm does not work with the "optional choices" option.'; +$string['algorithm_does_not_exist'] = 'The selected algorithm does not exist.'; $string['err_required'] = 'You need to provide a value for this field.'; $string['err_minimum'] = 'The minimum value for this field is {$a}.'; diff --git a/mod_form.php b/mod_form.php index 1295cf30..38337f74 100644 --- a/mod_form.php +++ b/mod_form.php @@ -148,10 +148,6 @@ public function definition() { foreach(\mod_ratingallocate\algorithm::get_available_algorithms() as $key => $value){ $features = \mod_ratingallocate\algorithm::get_instance($key)->get_supported_features(); $mform->addElement('radio', 'algorithm', '', $value, $key, array()); - if (!$features['min']) { - // TODO: does not work for individual radiobuttons. Needs JS check. - $mform->disabledIf("algorithm_$key" , 'generaloption_minsize', 'checked'); - } } $mform->addRule('algorithm', get_string('err_required', 'form') , 'required', null, 'server'); @@ -270,6 +266,23 @@ public function validation($data, $files) { } } } + + // User has to select an algorithm that exists. + if (!empty($data['algorithm'])) { + try { + $algorithm = \mod_ratingallocate\algorithm::get_instance($data['algorithm']); + $features = $algorithm->get_supported_features(); + // User has to select an algorithm that complies to the selected features. + if ($data['generaloption_minsize'] && !$features['min']) { + $errors['algorithm'] = get_string('algorithm_does_not_support_minsize', self::MOD_NAME); + } + if ($data['generaloption_optional'] && !$features['opt']) { + $errors['algorithm'] = get_string('algorithm_does_not_support_optional', self::MOD_NAME); + } + } catch (coding_exception $e) { + $errors['algorithm'] = get_string('algorithm_does_not_exist', self::MOD_NAME); + } + } return $errors; } /** From 3d63020fb4e4c4ffef2356fe1ebbdbe1f9a21598 Mon Sep 17 00:00:00 2001 From: NinaHerrmann Date: Tue, 19 Mar 2019 16:12:48 +0100 Subject: [PATCH 51/73] db: add field for persisting selected algorithm default, esp. for migrations: edmondskarp --- db/install.xml | 1 + db/upgrade.php | 16 ++++++++++++++++ version.php | 2 +- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/db/install.xml b/db/install.xml index 88ea4486..47d5ac1f 100644 --- a/db/install.xml +++ b/db/install.xml @@ -19,6 +19,7 @@ + diff --git a/db/upgrade.php b/db/upgrade.php index 17117731..50ee97b7 100644 --- a/db/upgrade.php +++ b/db/upgrade.php @@ -173,5 +173,21 @@ function xmldb_ratingallocate_upgrade($oldversion) { // Ratingallocate savepoint reached. upgrade_mod_savepoint(true, 2019031901, 'ratingallocate'); } + + if ($oldversion < 2019031916) { + + // Define field algorithm to be added to ratingallocate. + $table = new xmldb_table('ratingallocate'); + $field = new xmldb_field('algorithm', XMLDB_TYPE_CHAR, '50', null, XMLDB_NOTNULL, null, 'edmondskarp', 'strategy'); + + // Conditionally launch add field algorithm. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Ratingallocate savepoint reached. + upgrade_mod_savepoint(true, 2019031916, 'ratingallocate'); + } + return true; } diff --git a/version.php b/version.php index 484aabda..dd2a6cbe 100644 --- a/version.php +++ b/version.php @@ -26,7 +26,7 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2019031901; // The current module version (Date: YYYYMMDDXX) +$plugin->version = 2019031916; // The current module version (Date: YYYYMMDDXX) $plugin->requires = 2017111300; // Requires this Moodle version $plugin->maturity = MATURITY_STABLE; $plugin->release = 'v3.6-r1'; From c20d86328ca4c86e7a6be1f8848409e127ea8754 Mon Sep 17 00:00:00 2001 From: NinaHerrmann Date: Tue, 19 Mar 2019 16:20:16 +0100 Subject: [PATCH 52/73] tests: add new instance config fields --- tests/mod_generator_test.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/mod_generator_test.php b/tests/mod_generator_test.php index 514e5a73..2fe8ad72 100644 --- a/tests/mod_generator_test.php +++ b/tests/mod_generator_test.php @@ -62,7 +62,10 @@ public function test_create_instance() { 'accesstimestart' => reset($records)->{'accesstimestart'}, 'accesstimestop' => reset($records)->{'accesstimestop'}, 'setting' => '{"strategy_yesno":{"maxcrossout":"1"}}', + 'generaloption_minsize' => 0, + 'generaloption_minsize' => 0, 'strategy' => 'strategy_yesno', + 'algorithm' => 'edmondskarp', 'publishdate' => reset($records)->{'publishdate'}, 'published' => '0', 'notificationsend' => '0', From 8b37b97d6e012eba284a5b9b0d3577801a8d6b76 Mon Sep 17 00:00:00 2001 From: NinaHerrmann Date: Tue, 19 Mar 2019 16:37:24 +0100 Subject: [PATCH 53/73] test: containsOnlyInstancesOf does not work well --- tests/mod_ratingallocate_algorithm_subplugins_test.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/mod_ratingallocate_algorithm_subplugins_test.php b/tests/mod_ratingallocate_algorithm_subplugins_test.php index 8a444104..8c76625e 100644 --- a/tests/mod_ratingallocate_algorithm_subplugins_test.php +++ b/tests/mod_ratingallocate_algorithm_subplugins_test.php @@ -53,7 +53,6 @@ public function test_loads_assignment_subtype() { public function test_algorithm_supported_features() { $algorithm = \mod_ratingallocate\algorithm::get_instance('edmondskarp'); $supports = $algorithm->get_supported_features(); - $this->assertContainsOnlyInstancesOf('bool', $supports); $this->assertArrayHasKey('min', $supports); $this->assertArrayHasKey('opt', $supports); } From 6afc49b7ea1e881ce4f3ec70eaf0a9815adfea5e Mon Sep 17 00:00:00 2001 From: NinaHerrmann Date: Tue, 19 Mar 2019 16:40:39 +0100 Subject: [PATCH 54/73] form: set first algorithm as default --- mod_form.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mod_form.php b/mod_form.php index 38337f74..af8c9b49 100644 --- a/mod_form.php +++ b/mod_form.php @@ -145,11 +145,13 @@ public function definition() { $mform->addElement('header', $headerid, get_string('algorithmoptions', self::MOD_NAME)); $mform->setExpanded($headerid); - foreach(\mod_ratingallocate\algorithm::get_available_algorithms() as $key => $value){ + $algorithms = \mod_ratingallocate\algorithm::get_available_algorithms(); + foreach($algorithms as $key => $value){ $features = \mod_ratingallocate\algorithm::get_instance($key)->get_supported_features(); $mform->addElement('radio', 'algorithm', '', $value, $key, array()); } $mform->addRule('algorithm', get_string('err_required', 'form') , 'required', null, 'server'); + $mform->setDefault('algorithm', array_keys($algorithms)[0]); // Add standard elements, common to all modules. $this->standard_coursemodule_elements(); From d55b2ffca143a4fa8baddca7959dfa8128bc6409 Mon Sep 17 00:00:00 2001 From: NinaHerrmann Date: Tue, 19 Mar 2019 16:44:03 +0100 Subject: [PATCH 55/73] fix code style --- mod_form.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mod_form.php b/mod_form.php index af8c9b49..85f0e699 100644 --- a/mod_form.php +++ b/mod_form.php @@ -146,7 +146,7 @@ public function definition() { $mform->setExpanded($headerid); $algorithms = \mod_ratingallocate\algorithm::get_available_algorithms(); - foreach($algorithms as $key => $value){ + foreach ($algorithms as $key => $value) { $features = \mod_ratingallocate\algorithm::get_instance($key)->get_supported_features(); $mform->addElement('radio', 'algorithm', '', $value, $key, array()); } @@ -163,7 +163,7 @@ public function definition() { /** * Add an settings element to the form. It is enabled only if the strategy it belongs to is selected. * @param string $stratfieldid id of the element to be added - * @param array $value array with the element type and its caption + * @param array $value array with the element type and its caption * (usually returned by the strategys get settingsfields methods). * @param string $strategyid id of the strategy it belongs to. * @param $mform MoodleQuickForm form object the settings field should be added to. From 44e05d3df1a14311e050433a3ac632c915bb5e0a Mon Sep 17 00:00:00 2001 From: NinaHerrmann Date: Tue, 19 Mar 2019 16:44:47 +0100 Subject: [PATCH 56/73] test: fix copy-paste problem in tests of new fields --- tests/mod_generator_test.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/mod_generator_test.php b/tests/mod_generator_test.php index 2fe8ad72..093bd19e 100644 --- a/tests/mod_generator_test.php +++ b/tests/mod_generator_test.php @@ -63,7 +63,7 @@ public function test_create_instance() { 'accesstimestop' => reset($records)->{'accesstimestop'}, 'setting' => '{"strategy_yesno":{"maxcrossout":"1"}}', 'generaloption_minsize' => 0, - 'generaloption_minsize' => 0, + 'generaloption_optional' => 0, 'strategy' => 'strategy_yesno', 'algorithm' => 'edmondskarp', 'publishdate' => reset($records)->{'publishdate'}, From 5a69529e78f53a9bc8456ed6f2affdbf1fd4e02c Mon Sep 17 00:00:00 2001 From: Justus Dieckmann Date: Tue, 19 Mar 2019 11:51:30 +0100 Subject: [PATCH 57/73] Add table for logging --- db/install.xml | 15 +++++++++++++++ db/upgrade.php | 28 ++++++++++++++++++++++++++++ version.php | 2 +- 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/db/install.xml b/db/install.xml index 47d5ac1f..dced74b5 100644 --- a/db/install.xml +++ b/db/install.xml @@ -75,5 +75,20 @@ + + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/db/upgrade.php b/db/upgrade.php index 50ee97b7..a7935970 100644 --- a/db/upgrade.php +++ b/db/upgrade.php @@ -189,5 +189,33 @@ function xmldb_ratingallocate_upgrade($oldversion) { upgrade_mod_savepoint(true, 2019031916, 'ratingallocate'); } + if ($oldversion < 2019031901) { + + // Define table ratingallocate_execution_log to be created. + $table = new xmldb_table('ratingallocate_execution_log'); + + // Adding fields to table ratingallocate_execution_log. + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('ratingallocateid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + $table->add_field('algorithm', XMLDB_TYPE_CHAR, '50', null, XMLDB_NOTNULL, null, null); + $table->add_field('message', XMLDB_TYPE_TEXT, null, null, XMLDB_NOTNULL, null, null); + $table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + $table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + $table->add_field('usermodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + + // Adding keys to table ratingallocate_execution_log. + $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']); + $table->add_key('ratingallocateid', XMLDB_KEY_FOREIGN, ['ratingallocateid'], 'ratingallocate', ['id']); + + // Conditionally launch create table for ratingallocate_execution_log. + if (!$dbman->table_exists($table)) { + $dbman->create_table($table); + } + + // Ratingallocate savepoint reached. + upgrade_mod_savepoint(true, 2019031901, 'ratingallocate'); + } + + return true; } diff --git a/version.php b/version.php index dd2a6cbe..86b37aae 100644 --- a/version.php +++ b/version.php @@ -30,4 +30,4 @@ $plugin->requires = 2017111300; // Requires this Moodle version $plugin->maturity = MATURITY_STABLE; $plugin->release = 'v3.6-r1'; -$plugin->component = 'mod_ratingallocate'; // To check on upgrade, that module sits in correct place +$plugin->component = 'mod_ratingallocate'; // To check on upgrade, that module sits in correct place \ No newline at end of file From 056aa179ce2f40f47d4ce196081e89920a64cdfa Mon Sep 17 00:00:00 2001 From: Justus Dieckmann Date: Tue, 19 Mar 2019 12:04:48 +0100 Subject: [PATCH 58/73] Add persistent for execution_log --- classes/execution_log.php | 60 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 classes/execution_log.php diff --git a/classes/execution_log.php b/classes/execution_log.php new file mode 100644 index 00000000..112a4ded --- /dev/null +++ b/classes/execution_log.php @@ -0,0 +1,60 @@ +. + +/** + * Class for loading/storing execution_log messages from the DB. + * + * @package mod_ratingallocate + * @copyright 2019 WWU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace mod_ratingallocate; + +defined('MOODLE_INTERNAL') || die(); + +use core\persistent; +use lang_string; + +/** + * Class for loading/storing execution_log messages from the DB. + * + * @package mod_ratingallocate + * @copyright 2019 WWU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class execution_log extends persistent { + + const TABLE = 'ratingallocate_execution_log'; + + /** + * Return the definition of the properties of this model. + * + * @return array + */ + protected static function define_properties() { + return array( + 'ratingallocateid' => array( + 'type' => PARAM_INT + ), + 'algorithm' => array( + 'type' => PARAM_ALPHANUM, + ), + 'message' => array( + 'type' => PARAM_TEXT, + ) + ); + } +} From c164e84aa49f605ef90bb71ffdc812894de4c636 Mon Sep 17 00:00:00 2001 From: Justus Dieckmann Date: Tue, 19 Mar 2019 12:25:21 +0100 Subject: [PATCH 59/73] skeleton for appending log and subplugin name abstract function --- algorithm/edmondskarp/classes/algorithm_impl.php | 4 ++++ .../fordfulkersonkoegel/classes/algorithm_impl.php | 4 ++++ classes/algorithm.php | 12 ++++++++++++ 3 files changed, 20 insertions(+) diff --git a/algorithm/edmondskarp/classes/algorithm_impl.php b/algorithm/edmondskarp/classes/algorithm_impl.php index 972ee669..9152adc2 100644 --- a/algorithm/edmondskarp/classes/algorithm_impl.php +++ b/algorithm/edmondskarp/classes/algorithm_impl.php @@ -34,6 +34,10 @@ public function get_name() { return 'edmonds_karp'; } + public function get_subplugin_name() { + return 'edmondskarp'; + } + public function compute_distribution($choicerecords, $ratings, $usercount) { $choicedata = array(); foreach ($choicerecords as $record) { diff --git a/algorithm/fordfulkersonkoegel/classes/algorithm_impl.php b/algorithm/fordfulkersonkoegel/classes/algorithm_impl.php index 50c6ccae..df5518cd 100644 --- a/algorithm/fordfulkersonkoegel/classes/algorithm_impl.php +++ b/algorithm/fordfulkersonkoegel/classes/algorithm_impl.php @@ -30,6 +30,10 @@ class algorithm_impl extends \mod_ratingallocate\algorithm { /** @var $graph Flow-Graph built */ protected $graph; + public function get_subplugin_name() { + return 'fordfulkersonkoegel'; + } + /** * Starts the distribution algorithm. * Uses the users' ratings and a minimum-cost maximum-flow algorithm diff --git a/classes/algorithm.php b/classes/algorithm.php index 84f86c9b..9c5a9577 100644 --- a/classes/algorithm.php +++ b/classes/algorithm.php @@ -22,6 +22,16 @@ abstract class algorithm { + /** + * Get name of the subplugin, without the raalgo_ prefix. + * @return string + */ + public abstract function get_subplugin_name(); + + /** + * @deprecated + * @return string + */ public abstract function get_name(); /** @@ -86,6 +96,8 @@ public static function compute_target_function($ratings, $distribution) { return $functionvalue; } + protected abstract function append_to_log(string $message); + /** * @param string $name Subplugin name without 'raalgo_'-prefix. * @return algorithm Algorithm instance From 0c7dddb5e07008eeff8a6b566b7f4e39163f129d Mon Sep 17 00:00:00 2001 From: Justus Dieckmann Date: Tue, 19 Mar 2019 13:37:16 +0100 Subject: [PATCH 60/73] started append to log function --- classes/algorithm.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/classes/algorithm.php b/classes/algorithm.php index 9c5a9577..df85f31c 100644 --- a/classes/algorithm.php +++ b/classes/algorithm.php @@ -96,7 +96,18 @@ public static function compute_target_function($ratings, $distribution) { return $functionvalue; } - protected abstract function append_to_log(string $message); + /** Inserts a message to the execution_log + * @param string $message + */ + protected function append_to_log(string $message) { + $log = new execution_log(); + + $log->set('message', $message); + $log->set('algorithm', $this->get_subplugin_name()); + $log->set('ratingallocateid', ??); + $log->save(); + + } /** * @param string $name Subplugin name without 'raalgo_'-prefix. From 2ef628429b34915220290ac6302d01ae20a2f277 Mon Sep 17 00:00:00 2001 From: Justus Dieckmann Date: Tue, 19 Mar 2019 13:54:37 +0100 Subject: [PATCH 61/73] Passing and saving ratingallocate to algorithm in constructor method --- classes/algorithm.php | 28 ++++++++++++++++++---------- locallib.php | 4 ++-- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/classes/algorithm.php b/classes/algorithm.php index df85f31c..aa2ca72a 100644 --- a/classes/algorithm.php +++ b/classes/algorithm.php @@ -22,6 +22,13 @@ abstract class algorithm { + /** @var \ratingallocate ratingallocate */ + private $ratingallocate; + + public function __construct(\ratingallocate $ratingallocate) { + $this->ratingallocate = $ratingallocate; + } + /** * Get name of the subplugin, without the raalgo_ prefix. * @return string @@ -45,27 +52,27 @@ protected abstract function compute_distribution($choicerecords, $ratings, $user * Entry-point for the \ratingallocate object to call a solver * @param \ratingallocate $ratingallocate */ - public function distribute_users(\ratingallocate $ratingallocate) { + public function distribute_users() { // Load data from database. - $choicerecords = $ratingallocate->get_rateable_choices(); - $ratings = $ratingallocate->get_ratings_for_rateable_choices(); + $choicerecords = $this->ratingallocate->get_rateable_choices(); + $ratings = $this->ratingallocate->get_ratings_for_rateable_choices(); // Randomize the order of the enrties to prevent advantages for early entry. shuffle($ratings); - $usercount = count($ratingallocate->get_raters_in_course()); + $usercount = count($this->ratingallocate->get_raters_in_course()); $distributions = $this->compute_distribution($choicerecords, $ratings, $usercount); // Perform all allocation manipulation / inserts in one transaction. - $transaction = $ratingallocate->db->start_delegated_transaction(); + $transaction = $this->ratingallocate->db->start_delegated_transaction(); - $ratingallocate->clear_all_allocations(); + $this->ratingallocate->clear_all_allocations(); foreach ($distributions as $choiceid => $users) { foreach ($users as $userid) { - $ratingallocate->add_allocation($choiceid, $userid); + $this->ratingallocate->add_allocation($choiceid, $userid); } } $transaction->allow_commit(); @@ -104,20 +111,21 @@ protected function append_to_log(string $message) { $log->set('message', $message); $log->set('algorithm', $this->get_subplugin_name()); - $log->set('ratingallocateid', ??); + $log->set('ratingallocateid', $this->ratingallocate); $log->save(); } /** * @param string $name Subplugin name without 'raalgo_'-prefix. + * @param \ratingallocate $ratingallocate the current ratingallocateinstance. * @return algorithm Algorithm instance */ - public static function get_instance(string $name) { + public static function get_instance(string $name, \ratingallocate $ratingallocate) { $possible = self::get_available_algorithms(); if (array_key_exists($name, $possible)) { $classname = '\raalgo_' . $name . '\algorithm_impl'; - return new $classname(); + return new $classname($ratingallocate); } else { throw new \coding_exception('Tried to instantiate algorithm that is not installed or available.'); } diff --git a/locallib.php b/locallib.php index f90f0ddb..b35d75df 100644 --- a/locallib.php +++ b/locallib.php @@ -878,10 +878,10 @@ public function distrubute_choices() { $this->origdbrecord->algorithmstarttime = time(); $this->db->update_record(this_db\ratingallocate::TABLE, $this->origdbrecord); - $distributor = \mod_ratingallocate\algorithm::get_instance('edmondskarp'); + $distributor = \mod_ratingallocate\algorithm::get_instance('edmondskarp', $this); // $distributor = new solver_ford_fulkerson(); $timestart = microtime(true); - $distributor->distribute_users($this); + $distributor->distribute_users(); $timeneeded = (microtime(true) - $timestart); // echo memory_get_peak_usage(); From 7e86d0feafaf37bb79840276aaeb876a69513e1c Mon Sep 17 00:00:00 2001 From: Justus Dieckmann Date: Tue, 19 Mar 2019 14:52:59 +0100 Subject: [PATCH 62/73] generated ratingallocate dummy instance for tests --- ...tingallocate_algorithm_subplugins_test.php | 11 ++++++-- tests/mod_ratingallocate_solver_test.php | 28 +++++++++++++++---- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/tests/mod_ratingallocate_algorithm_subplugins_test.php b/tests/mod_ratingallocate_algorithm_subplugins_test.php index 8c76625e..61293e47 100644 --- a/tests/mod_ratingallocate_algorithm_subplugins_test.php +++ b/tests/mod_ratingallocate_algorithm_subplugins_test.php @@ -36,7 +36,7 @@ * @group mod_ratingallocate * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class mod_ratingallocate_algorithm_subplugins_testcase extends basic_testcase { +class mod_ratingallocate_algorithm_subplugins_testcase extends advanced_testcase { public function test_default_algorithms_present() { $algorithms = \mod_ratingallocate\algorithm::get_available_algorithms(); @@ -46,7 +46,14 @@ public function test_default_algorithms_present() { } public function test_loads_assignment_subtype() { - $algorithm = \mod_ratingallocate\algorithm::get_instance('edmondskarp'); + $this->resetAfterTest(); + // Create minnimal dummy data. + $course = $this->getDataGenerator()->create_course(); + $data = mod_ratingallocate_generator::get_default_values(); + $data['course'] = $course; + $dbrec = mod_ratingallocate_generator::create_instance_with_choices($this, $data); + $ratingallocate = mod_ratingallocate_generator::get_ratingallocate($dbrec); + $algorithm = \mod_ratingallocate\algorithm::get_instance('edmondskarp', $ratingallocate); $this->assertInstanceOf(\mod_ratingallocate\algorithm::class, $algorithm); } diff --git a/tests/mod_ratingallocate_solver_test.php b/tests/mod_ratingallocate_solver_test.php index 2e2c4c08..03edaba4 100644 --- a/tests/mod_ratingallocate_solver_test.php +++ b/tests/mod_ratingallocate_solver_test.php @@ -29,7 +29,7 @@ global $CFG; require_once($CFG->dirroot . '/mod/ratingallocate/locallib.php'); -class edmonds_karp_test extends basic_testcase { +class edmonds_karp_test extends advanced_testcase { private function perform_race($groupsnum, $ratersnum) { $groupsmaxsizemin = floor($ratersnum / $groupsnum); @@ -73,7 +73,7 @@ private function perform_race($groupsnum, $ratersnum) { $solvers = array('edmondskarp', 'fordfulkersonkoegel'); foreach ($solvers as $solver) { - $solver1 = \mod_ratingallocate\algorithm::get_instance($solver); + $solver1 = \mod_ratingallocate\algorithm::get_instance($solver, null); $timestart = microtime(true); $distribution1 = $solver1->compute_distribution($groups, $ratings, $usercount); $result[$solver1->get_name()]['elapsed_sec'] = (microtime(true) - $timestart); @@ -130,6 +130,7 @@ public function teston_random() { } public function test_edmondskarp() { + $this->resetAfterTest(); $choices = array(); $choices[1] = new stdClass(); $choices[1]->maxsize = 2; @@ -186,7 +187,14 @@ public function test_edmondskarp() { $usercount = 5; - $solver = \mod_ratingallocate\algorithm::get_instance('edmondskarp'); + // Create minnimal dummy data. + $course = $this->getDataGenerator()->create_course(); + $data = mod_ratingallocate_generator::get_default_values(); + $data['course'] = $course; + $dbrec = mod_ratingallocate_generator::create_instance_with_choices($this, $data); + $ratingallocate = mod_ratingallocate_generator::get_ratingallocate($dbrec); + + $solver = \mod_ratingallocate\algorithm::get_instance('edmondskarp', $ratingallocate); $distribution = $solver->compute_distribution($choices, $ratings, $usercount); $expected = array(1 => array(2, 5), 2 => array(4, 1)); // echo "gesamtpunktzahl: " . $solver->compute_target_function($choices, $ratings, $distribution); @@ -195,13 +203,14 @@ public function test_edmondskarp() { $this->assertEquals($solver::compute_target_function($ratings, $distribution), 15); // test against Koegels solver - $solverkoe = \mod_ratingallocate\algorithm::get_instance('fordfulkersonkoegel'); + $solverkoe = \mod_ratingallocate\algorithm::get_instance('fordfulkersonkoegel', $ratingallocate); $distributionkoe = $solverkoe->compute_distribution($choices, $ratings, $usercount); $this->assertEquals($solverkoe::compute_target_function($ratings, $distributionkoe), 15); $this->assertEquals($solverkoe::compute_target_function($ratings, $distributionkoe), $solver::compute_target_function($ratings, $distribution)); } public function test_negweightcycle() { + $this->resetAfterTest(); // experimental $choices = array(); $choices[1] = new stdClass(); @@ -234,11 +243,18 @@ public function test_negweightcycle() { $usercount = 2; - $solver = \mod_ratingallocate\algorithm::get_instance('edmondskarp'); + // Create minnimal dummy data. + $course = $this->getDataGenerator()->create_course(); + $data = mod_ratingallocate_generator::get_default_values(); + $data['course'] = $course; + $dbrec = mod_ratingallocate_generator::create_instance_with_choices($this, $data); + $ratingallocate = mod_ratingallocate_generator::get_ratingallocate($dbrec); + + $solver = \mod_ratingallocate\algorithm::get_instance('edmondskarp', $ratingallocate); $distribution = $solver->compute_distribution($choices, $ratings, $usercount); $this->assertEquals($solver::compute_target_function($ratings, $distribution), 10); - $solverkoe = \mod_ratingallocate\algorithm::get_instance('fordfulkersonkoegel'); + $solverkoe = \mod_ratingallocate\algorithm::get_instance('fordfulkersonkoegel', $ratingallocate); $distributionkoe = $solverkoe->compute_distribution($choices, $ratings, $usercount); $this->assertEquals($solverkoe::compute_target_function($ratings, $distributionkoe), 10); From 473be27db5721d63fdd32cba9eba3bb251a076ff Mon Sep 17 00:00:00 2001 From: Justus Dieckmann Date: Tue, 19 Mar 2019 14:59:43 +0100 Subject: [PATCH 63/73] Added getter for ratingallocateid --- classes/algorithm.php | 2 +- locallib.php | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/classes/algorithm.php b/classes/algorithm.php index aa2ca72a..af8c0ce5 100644 --- a/classes/algorithm.php +++ b/classes/algorithm.php @@ -111,7 +111,7 @@ protected function append_to_log(string $message) { $log->set('message', $message); $log->set('algorithm', $this->get_subplugin_name()); - $log->set('ratingallocateid', $this->ratingallocate); + $log->set('ratingallocateid', $this->ratingallocate->get_id()); $log->save(); } diff --git a/locallib.php b/locallib.php index b35d75df..243cd56b 100644 --- a/locallib.php +++ b/locallib.php @@ -1480,6 +1480,14 @@ public function get_context() { return $this->context; } + + /** + * @return int id + */ + public function get_id() { + return $this->ratingallocateid; + } + /** * @return bool true, if all strategy settings are ok. */ From 77951327f485fc71ff4839d40b8dee0be04ebc02 Mon Sep 17 00:00:00 2001 From: Justus Dieckmann Date: Tue, 19 Mar 2019 15:55:41 +0100 Subject: [PATCH 64/73] Test: created initial data for logging --- tests/mod_ratingallocate_log_test.php | 61 +++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 tests/mod_ratingallocate_log_test.php diff --git a/tests/mod_ratingallocate_log_test.php b/tests/mod_ratingallocate_log_test.php new file mode 100644 index 00000000..449d88ed --- /dev/null +++ b/tests/mod_ratingallocate_log_test.php @@ -0,0 +1,61 @@ +. + +/** + * Ratingallocate log tests. + * + * @package mod_ratingallocate + * @copyright 2019 R. Tschudi, N Herrmann + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +use core_privacy\local\request\deletion_criteria; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Ratingallocate log tests. + * + * @package mod_ratingallocate + * @copyright 2019 R. Tschudi, N Herrmann + * @group mod_ratingallocate + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class mod_ratingallocate_log_test extends advanced_testcase { + + /** + * Test the creation of logs. + */ + public function test_algorithm_log_create() { + global $DB; + $this->resetAfterTest(true); + + // Create minnimal dummy data. + $course = $this->getDataGenerator()->create_course(); + $data = mod_ratingallocate_generator::get_default_values(); + $data['course'] = $course; + $dbrec = mod_ratingallocate_generator::create_instance_with_choices($this, $data); + $ratingallocate = mod_ratingallocate_generator::get_ratingallocate($dbrec); + + $algorithm = new \raalgo_edmondskarp\algorithm_impl($ratingallocate); + $logs = array('Test Message 0', 'Test Message 1', 'Test Message 2'); + foreach ($logs as $log) { + $algorithm->append_to_log($log); + } + $entries = $DB->get_records('ratingallocate_execution_log'); + var_dump($entries); + } +} \ No newline at end of file From d5fd0b8ad8fb2222de228a55e155a1f0b0028ef8 Mon Sep 17 00:00:00 2001 From: Justus Dieckmann Date: Tue, 19 Mar 2019 16:20:36 +0100 Subject: [PATCH 65/73] Test: added testclass for algorithm --- classes/algorithm_testable.php | 54 +++++++++++++++++++++++++++ tests/mod_ratingallocate_log_test.php | 2 +- 2 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 classes/algorithm_testable.php diff --git a/classes/algorithm_testable.php b/classes/algorithm_testable.php new file mode 100644 index 00000000..78b71053 --- /dev/null +++ b/classes/algorithm_testable.php @@ -0,0 +1,54 @@ +. + +namespace mod_ratingallocate; + +defined('MOODLE_INTERNAL') || die(); + +require_once(__DIR__ . '/../locallib.php'); + +class algorithm_testable extends algorithm { + + /** + * Inserts a message to the execution_log + * @param string $message + */ + public function append_to_log(string $message) { + parent::append_to_log($message); + } + + /** + * Get name of the subplugin, without the raalgo_ prefix. + * + * @return string + */ + public function get_subplugin_name() { + return 'algorithm_testable'; + } + + /** + * @deprecated + * @return string + */ + public function get_name() { + return 'Algorithm Testable'; + } + + protected function compute_distribution($choicerecords, $ratings, $usercount) { + return null; + } + +} \ No newline at end of file diff --git a/tests/mod_ratingallocate_log_test.php b/tests/mod_ratingallocate_log_test.php index 449d88ed..1ddd1c1f 100644 --- a/tests/mod_ratingallocate_log_test.php +++ b/tests/mod_ratingallocate_log_test.php @@ -50,7 +50,7 @@ public function test_algorithm_log_create() { $dbrec = mod_ratingallocate_generator::create_instance_with_choices($this, $data); $ratingallocate = mod_ratingallocate_generator::get_ratingallocate($dbrec); - $algorithm = new \raalgo_edmondskarp\algorithm_impl($ratingallocate); + $algorithm = new \mod_ratingallocate\algorithm_testable($ratingallocate); $logs = array('Test Message 0', 'Test Message 1', 'Test Message 2'); foreach ($logs as $log) { $algorithm->append_to_log($log); From 38a33340eccaa2a86b028c09fcee1da3d90ec3ba Mon Sep 17 00:00:00 2001 From: Justus Dieckmann Date: Tue, 19 Mar 2019 16:52:03 +0100 Subject: [PATCH 66/73] Test: finished testcase creation for append_to_log --- classes/algorithm_testable.php | 2 +- tests/mod_ratingallocate_log_test.php | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/classes/algorithm_testable.php b/classes/algorithm_testable.php index 78b71053..52dbdde0 100644 --- a/classes/algorithm_testable.php +++ b/classes/algorithm_testable.php @@ -36,7 +36,7 @@ public function append_to_log(string $message) { * @return string */ public function get_subplugin_name() { - return 'algorithm_testable'; + return 'algorithmtestable'; } /** diff --git a/tests/mod_ratingallocate_log_test.php b/tests/mod_ratingallocate_log_test.php index 1ddd1c1f..d7af76c5 100644 --- a/tests/mod_ratingallocate_log_test.php +++ b/tests/mod_ratingallocate_log_test.php @@ -51,11 +51,19 @@ public function test_algorithm_log_create() { $ratingallocate = mod_ratingallocate_generator::get_ratingallocate($dbrec); $algorithm = new \mod_ratingallocate\algorithm_testable($ratingallocate); - $logs = array('Test Message 0', 'Test Message 1', 'Test Message 2'); + $logs = array('Test Message 0', 'Test Message 1'); foreach ($logs as $log) { $algorithm->append_to_log($log); } $entries = $DB->get_records('ratingallocate_execution_log'); - var_dump($entries); + $first = array_shift($entries); + $second = array_shift($entries); + + $this->assertEquals($first->message, 'Test Message 0'); + $this->assertEquals($second->message, 'Test Message 1'); + $this->assertEquals($first->ratingallocateid, $ratingallocate->get_id()); + $this->assertEquals($second->ratingallocateid, $ratingallocate->get_id()); + $this->assertEquals($first->algorithm, $algorithm->get_subplugin_name()); + $this->assertEquals($second->algorithm, $algorithm->get_subplugin_name()); } } \ No newline at end of file From 434ef91fac2df64b899267ecaef4f939b326879e Mon Sep 17 00:00:00 2001 From: Justus Dieckmann Date: Tue, 19 Mar 2019 16:52:32 +0100 Subject: [PATCH 67/73] CodeStyle fixes --- classes/algorithm.php | 3 ++- tests/mod_ratingallocate_solver_test.php | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/classes/algorithm.php b/classes/algorithm.php index af8c0ce5..a856a4d3 100644 --- a/classes/algorithm.php +++ b/classes/algorithm.php @@ -103,7 +103,8 @@ public static function compute_target_function($ratings, $distribution) { return $functionvalue; } - /** Inserts a message to the execution_log + /** + * Inserts a message to the execution_log * @param string $message */ protected function append_to_log(string $message) { diff --git a/tests/mod_ratingallocate_solver_test.php b/tests/mod_ratingallocate_solver_test.php index 03edaba4..9004eccc 100644 --- a/tests/mod_ratingallocate_solver_test.php +++ b/tests/mod_ratingallocate_solver_test.php @@ -187,7 +187,7 @@ public function test_edmondskarp() { $usercount = 5; - // Create minnimal dummy data. + // Create minimal dummy data. $course = $this->getDataGenerator()->create_course(); $data = mod_ratingallocate_generator::get_default_values(); $data['course'] = $course; From fba4249f21cee3ed50b3fa56f2a4d46dc770670c Mon Sep 17 00:00:00 2001 From: Justus Dieckmann Date: Tue, 19 Mar 2019 17:06:45 +0100 Subject: [PATCH 68/73] Added cutom error message for persistent class --- classes/execution_log.php | 5 ++++- lang/en/ratingallocate.php | 7 ++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/classes/execution_log.php b/classes/execution_log.php index 112a4ded..15eab423 100644 --- a/classes/execution_log.php +++ b/classes/execution_log.php @@ -47,13 +47,16 @@ class execution_log extends persistent { protected static function define_properties() { return array( 'ratingallocateid' => array( - 'type' => PARAM_INT + 'type' => PARAM_INT, + 'message' => new lang_string('error_persistent_ratingallocateid', 'mod_ratingallocate'), ), 'algorithm' => array( 'type' => PARAM_ALPHANUM, + 'message' => new lang_string('error_persistent_algoname', 'mod_ratingallocate'), ), 'message' => array( 'type' => PARAM_TEXT, + 'message' => new lang_string('error_persistent_message', 'mod_ratingallocate'), ) ); } diff --git a/lang/en/ratingallocate.php b/lang/en/ratingallocate.php index 750981dc..8c8e4317 100644 --- a/lang/en/ratingallocate.php +++ b/lang/en/ratingallocate.php @@ -369,4 +369,9 @@ $string['privacy:metadata:preference:flextable_manual_filter'] = 'Stores the filters that are applied to the manual allocations table.'; $string['filtertabledesc'] = 'Describes the filters that are applied to the allocation table.'; -$string['filtermanualtabledesc'] = 'Describes the filters that are applied to the table of the manual allocation form.'; \ No newline at end of file +$string['filtermanualtabledesc'] = 'Describes the filters that are applied to the table of the manual allocation form.'; + +// Persistent class errors. +$string['error_persistent_ratingallocateid'] = 'The id is invalid.'; +$string['error_persistent_algoname'] = 'The algorithm name is invalid.'; +$string['error_persistent_message'] = 'The message is invalid.'; \ No newline at end of file From 28d67595613afd3684c48158bf0472f0e6980642 Mon Sep 17 00:00:00 2001 From: Justus Dieckmann Date: Tue, 19 Mar 2019 18:27:20 +0100 Subject: [PATCH 69/73] Reordered savepoints in upgrade.php --- db/upgrade.php | 35 ++++++++++++++--------------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/db/upgrade.php b/db/upgrade.php index a7935970..9e4a3074 100644 --- a/db/upgrade.php +++ b/db/upgrade.php @@ -170,27 +170,6 @@ function xmldb_ratingallocate_upgrade($oldversion) { $dbman->add_field($table, $field); } - // Ratingallocate savepoint reached. - upgrade_mod_savepoint(true, 2019031901, 'ratingallocate'); - } - - if ($oldversion < 2019031916) { - - // Define field algorithm to be added to ratingallocate. - $table = new xmldb_table('ratingallocate'); - $field = new xmldb_field('algorithm', XMLDB_TYPE_CHAR, '50', null, XMLDB_NOTNULL, null, 'edmondskarp', 'strategy'); - - // Conditionally launch add field algorithm. - if (!$dbman->field_exists($table, $field)) { - $dbman->add_field($table, $field); - } - - // Ratingallocate savepoint reached. - upgrade_mod_savepoint(true, 2019031916, 'ratingallocate'); - } - - if ($oldversion < 2019031901) { - // Define table ratingallocate_execution_log to be created. $table = new xmldb_table('ratingallocate_execution_log'); @@ -216,6 +195,20 @@ function xmldb_ratingallocate_upgrade($oldversion) { upgrade_mod_savepoint(true, 2019031901, 'ratingallocate'); } + if ($oldversion < 2019031916) { + + // Define field algorithm to be added to ratingallocate. + $table = new xmldb_table('ratingallocate'); + $field = new xmldb_field('algorithm', XMLDB_TYPE_CHAR, '50', null, XMLDB_NOTNULL, null, 'edmondskarp', 'strategy'); + + // Conditionally launch add field algorithm. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Ratingallocate savepoint reached. + upgrade_mod_savepoint(true, 2019031916, 'ratingallocate'); + } return true; } From 30107d7e72215b9d548767092677fcc1f829f4d0 Mon Sep 17 00:00:00 2001 From: Justus Dieckmann Date: Tue, 19 Mar 2019 18:36:53 +0100 Subject: [PATCH 70/73] implemented get_supported_features() in algorithm_testable --- classes/algorithm_testable.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/classes/algorithm_testable.php b/classes/algorithm_testable.php index 52dbdde0..cc5086fb 100644 --- a/classes/algorithm_testable.php +++ b/classes/algorithm_testable.php @@ -51,4 +51,15 @@ protected function compute_distribution($choicerecords, $ratings, $usercount) { return null; } + /** + * Expected return value is an array with min and opt as key and true or false as supported or not supported. + * + * @return bool[] + */ + public static function get_supported_features() { + return array( + 'min' => false, + 'opt' => false + ); + } } \ No newline at end of file From ba9d4a590ba0cb14ab4d8757dddacd7e4592efaa Mon Sep 17 00:00:00 2001 From: Justus Dieckmann Date: Tue, 19 Mar 2019 19:05:33 +0100 Subject: [PATCH 71/73] bugfix missing parameter for get_instance() --- classes/algorithm.php | 6 +++--- mod_form.php | 4 ++-- tests/mod_ratingallocate_algorithm_subplugins_test.php | 9 ++++++++- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/classes/algorithm.php b/classes/algorithm.php index a856a4d3..08b437df 100644 --- a/classes/algorithm.php +++ b/classes/algorithm.php @@ -25,7 +25,7 @@ abstract class algorithm { /** @var \ratingallocate ratingallocate */ private $ratingallocate; - public function __construct(\ratingallocate $ratingallocate) { + public function __construct($ratingallocate) { $this->ratingallocate = $ratingallocate; } @@ -119,10 +119,10 @@ protected function append_to_log(string $message) { /** * @param string $name Subplugin name without 'raalgo_'-prefix. - * @param \ratingallocate $ratingallocate the current ratingallocateinstance. + * @param \ratingallocate|null $ratingallocate the current ratingallocateinstance. * @return algorithm Algorithm instance */ - public static function get_instance(string $name, \ratingallocate $ratingallocate) { + public static function get_instance(string $name, $ratingallocate) { $possible = self::get_available_algorithms(); if (array_key_exists($name, $possible)) { $classname = '\raalgo_' . $name . '\algorithm_impl'; diff --git a/mod_form.php b/mod_form.php index 85f0e699..6d095b7c 100644 --- a/mod_form.php +++ b/mod_form.php @@ -147,7 +147,7 @@ public function definition() { $algorithms = \mod_ratingallocate\algorithm::get_available_algorithms(); foreach ($algorithms as $key => $value) { - $features = \mod_ratingallocate\algorithm::get_instance($key)->get_supported_features(); + $features = \mod_ratingallocate\algorithm::get_instance($key, null)->get_supported_features(); $mform->addElement('radio', 'algorithm', '', $value, $key, array()); } $mform->addRule('algorithm', get_string('err_required', 'form') , 'required', null, 'server'); @@ -272,7 +272,7 @@ public function validation($data, $files) { // User has to select an algorithm that exists. if (!empty($data['algorithm'])) { try { - $algorithm = \mod_ratingallocate\algorithm::get_instance($data['algorithm']); + $algorithm = \mod_ratingallocate\algorithm::get_instance($data['algorithm'], null); $features = $algorithm->get_supported_features(); // User has to select an algorithm that complies to the selected features. if ($data['generaloption_minsize'] && !$features['min']) { diff --git a/tests/mod_ratingallocate_algorithm_subplugins_test.php b/tests/mod_ratingallocate_algorithm_subplugins_test.php index 61293e47..f871c7d6 100644 --- a/tests/mod_ratingallocate_algorithm_subplugins_test.php +++ b/tests/mod_ratingallocate_algorithm_subplugins_test.php @@ -58,7 +58,14 @@ public function test_loads_assignment_subtype() { } public function test_algorithm_supported_features() { - $algorithm = \mod_ratingallocate\algorithm::get_instance('edmondskarp'); + $this->resetAfterTest(); + // Create minnimal dummy data. + $course = $this->getDataGenerator()->create_course(); + $data = mod_ratingallocate_generator::get_default_values(); + $data['course'] = $course; + $dbrec = mod_ratingallocate_generator::create_instance_with_choices($this, $data); + $ratingallocate = mod_ratingallocate_generator::get_ratingallocate($dbrec); + $algorithm = \mod_ratingallocate\algorithm::get_instance('edmondskarp', $ratingallocate); $supports = $algorithm->get_supported_features(); $this->assertArrayHasKey('min', $supports); $this->assertArrayHasKey('opt', $supports); From 37150e2a151b0539977557ed670d5330fd015657 Mon Sep 17 00:00:00 2001 From: Justus Dieckmann Date: Wed, 20 Mar 2019 02:59:24 +0100 Subject: [PATCH 72/73] remove generating ratingallocate instance because not necessary --- ...tingallocate_algorithm_subplugins_test.php | 20 +++----------- tests/mod_ratingallocate_solver_test.php | 26 ++++--------------- version.php | 2 +- 3 files changed, 9 insertions(+), 39 deletions(-) diff --git a/tests/mod_ratingallocate_algorithm_subplugins_test.php b/tests/mod_ratingallocate_algorithm_subplugins_test.php index f871c7d6..02322c58 100644 --- a/tests/mod_ratingallocate_algorithm_subplugins_test.php +++ b/tests/mod_ratingallocate_algorithm_subplugins_test.php @@ -36,7 +36,7 @@ * @group mod_ratingallocate * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class mod_ratingallocate_algorithm_subplugins_testcase extends advanced_testcase { +class mod_ratingallocate_algorithm_subplugins_testcase extends basic_testcase { public function test_default_algorithms_present() { $algorithms = \mod_ratingallocate\algorithm::get_available_algorithms(); @@ -46,26 +46,12 @@ public function test_default_algorithms_present() { } public function test_loads_assignment_subtype() { - $this->resetAfterTest(); - // Create minnimal dummy data. - $course = $this->getDataGenerator()->create_course(); - $data = mod_ratingallocate_generator::get_default_values(); - $data['course'] = $course; - $dbrec = mod_ratingallocate_generator::create_instance_with_choices($this, $data); - $ratingallocate = mod_ratingallocate_generator::get_ratingallocate($dbrec); - $algorithm = \mod_ratingallocate\algorithm::get_instance('edmondskarp', $ratingallocate); + $algorithm = \mod_ratingallocate\algorithm::get_instance('edmondskarp', null); $this->assertInstanceOf(\mod_ratingallocate\algorithm::class, $algorithm); } public function test_algorithm_supported_features() { - $this->resetAfterTest(); - // Create minnimal dummy data. - $course = $this->getDataGenerator()->create_course(); - $data = mod_ratingallocate_generator::get_default_values(); - $data['course'] = $course; - $dbrec = mod_ratingallocate_generator::create_instance_with_choices($this, $data); - $ratingallocate = mod_ratingallocate_generator::get_ratingallocate($dbrec); - $algorithm = \mod_ratingallocate\algorithm::get_instance('edmondskarp', $ratingallocate); + $algorithm = \mod_ratingallocate\algorithm::get_instance('edmondskarp', null); $supports = $algorithm->get_supported_features(); $this->assertArrayHasKey('min', $supports); $this->assertArrayHasKey('opt', $supports); diff --git a/tests/mod_ratingallocate_solver_test.php b/tests/mod_ratingallocate_solver_test.php index 9004eccc..9816ab3b 100644 --- a/tests/mod_ratingallocate_solver_test.php +++ b/tests/mod_ratingallocate_solver_test.php @@ -29,7 +29,7 @@ global $CFG; require_once($CFG->dirroot . '/mod/ratingallocate/locallib.php'); -class edmonds_karp_test extends advanced_testcase { +class edmonds_karp_test extends basic_testcase { private function perform_race($groupsnum, $ratersnum) { $groupsmaxsizemin = floor($ratersnum / $groupsnum); @@ -130,7 +130,6 @@ public function teston_random() { } public function test_edmondskarp() { - $this->resetAfterTest(); $choices = array(); $choices[1] = new stdClass(); $choices[1]->maxsize = 2; @@ -187,14 +186,7 @@ public function test_edmondskarp() { $usercount = 5; - // Create minimal dummy data. - $course = $this->getDataGenerator()->create_course(); - $data = mod_ratingallocate_generator::get_default_values(); - $data['course'] = $course; - $dbrec = mod_ratingallocate_generator::create_instance_with_choices($this, $data); - $ratingallocate = mod_ratingallocate_generator::get_ratingallocate($dbrec); - - $solver = \mod_ratingallocate\algorithm::get_instance('edmondskarp', $ratingallocate); + $solver = \mod_ratingallocate\algorithm::get_instance('edmondskarp', null); $distribution = $solver->compute_distribution($choices, $ratings, $usercount); $expected = array(1 => array(2, 5), 2 => array(4, 1)); // echo "gesamtpunktzahl: " . $solver->compute_target_function($choices, $ratings, $distribution); @@ -203,14 +195,13 @@ public function test_edmondskarp() { $this->assertEquals($solver::compute_target_function($ratings, $distribution), 15); // test against Koegels solver - $solverkoe = \mod_ratingallocate\algorithm::get_instance('fordfulkersonkoegel', $ratingallocate); + $solverkoe = \mod_ratingallocate\algorithm::get_instance('fordfulkersonkoegel', null); $distributionkoe = $solverkoe->compute_distribution($choices, $ratings, $usercount); $this->assertEquals($solverkoe::compute_target_function($ratings, $distributionkoe), 15); $this->assertEquals($solverkoe::compute_target_function($ratings, $distributionkoe), $solver::compute_target_function($ratings, $distribution)); } public function test_negweightcycle() { - $this->resetAfterTest(); // experimental $choices = array(); $choices[1] = new stdClass(); @@ -243,18 +234,11 @@ public function test_negweightcycle() { $usercount = 2; - // Create minnimal dummy data. - $course = $this->getDataGenerator()->create_course(); - $data = mod_ratingallocate_generator::get_default_values(); - $data['course'] = $course; - $dbrec = mod_ratingallocate_generator::create_instance_with_choices($this, $data); - $ratingallocate = mod_ratingallocate_generator::get_ratingallocate($dbrec); - - $solver = \mod_ratingallocate\algorithm::get_instance('edmondskarp', $ratingallocate); + $solver = \mod_ratingallocate\algorithm::get_instance('edmondskarp', null); $distribution = $solver->compute_distribution($choices, $ratings, $usercount); $this->assertEquals($solver::compute_target_function($ratings, $distribution), 10); - $solverkoe = \mod_ratingallocate\algorithm::get_instance('fordfulkersonkoegel', $ratingallocate); + $solverkoe = \mod_ratingallocate\algorithm::get_instance('fordfulkersonkoegel', null); $distributionkoe = $solverkoe->compute_distribution($choices, $ratings, $usercount); $this->assertEquals($solverkoe::compute_target_function($ratings, $distributionkoe), 10); diff --git a/version.php b/version.php index 86b37aae..dd2a6cbe 100644 --- a/version.php +++ b/version.php @@ -30,4 +30,4 @@ $plugin->requires = 2017111300; // Requires this Moodle version $plugin->maturity = MATURITY_STABLE; $plugin->release = 'v3.6-r1'; -$plugin->component = 'mod_ratingallocate'; // To check on upgrade, that module sits in correct place \ No newline at end of file +$plugin->component = 'mod_ratingallocate'; // To check on upgrade, that module sits in correct place From 379255e0979cebb37dac0c24e3a192eb7382459e Mon Sep 17 00:00:00 2001 From: Justus Dieckmann Date: Wed, 20 Mar 2019 16:50:47 +0100 Subject: [PATCH 73/73] Include Moodle36 in .travis.yml --- .travis.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index c1a676c1..0a7b1335 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,9 +28,11 @@ env: matrix: - DB=pgsql MOODLE_BRANCH=MOODLE_34_STABLE - DB=pgsql MOODLE_BRANCH=MOODLE_35_STABLE + - DB=pgsql MOODLE_BRANCH=MOODLE_36_STABLE - DB=pgsql MOODLE_BRANCH=master - DB=mysqli MOODLE_BRANCH=MOODLE_34_STABLE - DB=mysqli MOODLE_BRANCH=MOODLE_35_STABLE + - DB=mysqli MOODLE_BRANCH=MOODLE_36_STABLE - DB=mysqli MOODLE_BRANCH=master before_install: @@ -51,7 +53,7 @@ jobs: # packages: # - oracle-java8-installer # - oracle-java8-set-default - env: DB=mysqli MOODLE_BRANCH=MOODLE_35_STABLE + env: DB=mysqli MOODLE_BRANCH=MOODLE_36_STABLE install: - moodle-plugin-ci install --no-init script: @@ -67,7 +69,7 @@ jobs: # Smaller build matrix for development builds - stage: develop php: 7.2 - env: DB=mysqli MOODLE_BRANCH=MOODLE_35_STABLE + env: DB=mysqli MOODLE_BRANCH=MOODLE_36_STABLE install: - moodle-plugin-ci install script: