ÿØÿà JFIF    ÿÛ „  ( %!1"%)+...383-7(-.+  -%%--------+------------/----------------------------ÿÀ  ¶" ÿÄ     ÿÄ @    !1AQaq‘¡"±ÁÑð2’BRráS‚ñ3b¢CT²Ò #cÿÄ    ÿÄ '   1!AQ2a"‘±#ÿÚ   ? ò(/p‘—–¢·,M|¯G»R£üešT. ôŸEoCFñ‡ 4ÔkåóxR|µÁ¨ÓDà!Ç´µ˜‡p ÍQ‹§!þž^õWsŒy  åÁ§$á«%ºq˜™JyyË0y«^Ϗ -3ÃÏËÈ£Æ\P’ä„s&•‹Üs üô'àWt¹pñCp$bÚF`·7.^)¾!)ÒÙ˜–ÅÏš^h¾ø³·ìe±ŒaeÖ—Í,e=B}Jéߦ$$½—šÙÜCC¯¶è8β–Åkèwx LÉngT±æ¹£Ócœ¿Í/©Éÿ ‰ÚCÒ­mq;Tt¶–}0"zÖPcd1š­¤tˆ„ÐÓâqú[™öUCŠ2޿د­É(¸ßúçšmW,Ò ºf‹Ån™ÄLêÁ8üÁkÁ€L¥ÍuJ1œôrÂSŒÓöt!7EUíïb)AËi¬â-ÙHk8òÁiXYwÅ)O^"ÇØriÛô{»wRUKÙÏÚlÓ¬€‚’שT ] ªb—ÊîKC^×M“xùäñz¬zJ× {4[¹M'IÆeH1LCWÕ]œûº WDæà°OÅ‚¹c²ÞuA–rL`HšpCez‡V’— Ž‚Ë„–ñ­'µR0ÕÖ=À:±G»C\nÆÉ5*¢ †3šhQ4ýÒi$Õ1"Ü]¢p-q3zcQ$ªö¨¥æf›‘»´ÝÚHá„e²JÇ–iÊ:·à¬Øc5Åk»Laª*wi [0Ówk ¦a¦0Õ²ÅÅ‚T¸’³q:Æ<¹ú Ò˜Æ}2VE•Ѐp¡Ô3Úº …h³^LJºçì¥Ánó|˜ÖˆÎˆ+« ¦dø‘Mº–d\ªDðÛ„Ì×LÝ——Ð9Ç|•FèçVm™¨€™3õS–9Äå¢<̉Ìm¢±`·º˜&Z²ž Ë®D«¶»6™‰©­HÈ Àá«h­i³04æ’hxœi3ï¨(ÓL·†‹¶ËpŒ ƒÜÒ? :õdsÀ –3æÓ·Üjâ¬9—]8n¼&]I F£¼…ì–—Œ0v°LǐÅfÛ2TÖ’aµ˜N@ÖBSÏ^à´4-±°b×nÈÈœÆD=KdŒø…ÄÈK,›ÖR§fjü$^ÔÎ!„6^ s:Üun\ôkCÞò÷<—Lúl Í›G¹ÀPá¼Nc¡šÒ…ÙWͳœ±vÊáЄ×9‹P‰cEØ‘™›µŸP<×g1 º©Ø¬wiÁ^l5× I/';Ë+´Z‡ÓÄFÕp[Èð’ HÄ ó 9¬Dl4“é±Ï”V=^Hð;žLädÉa‚°Ë,#/Ž~²BÔÛ QãñQtIeórVB,ÏÂf‹4#Μc‹É3ZIÖ Œ¢¥·üxa‡Ãçè„jŠ[5 Ä!U?,3žÎ×…ðdÓ÷háŠA‰ìÄ4÷ÃÜZÃE{‰®+W\ZÍE[‰»µk»MÝ­`¢©†˜ÃV‹-f¢¡†¢a«eŠ%«Y¨©q%jâd,Ôq`) ¤¦…šˆ ¦E>0Ùb²t–Ÿ  ÷jsςҒKÈcø(é›;k(’#ôŒQ ™C> õ³M9óºÆµ¤çâ;uÀ)5>RÔ¸fÓ~Ȧ—’-$fe«"FâUˆÊwµPÏu w+"åÉó¨6s™Û9(À¨ù©hE°½†n`m~“@©›p!Vkhµ Á1šbEqqÊ@ºèl›2 –Yã%‘‚hÛ{a¶‡ÅÞM²¬çL34®Çl]®‹ŽØŒm'RÜe35æöˆ­Á­ cP9šeÁköo´L³Ì=p8]”‡B¶,šº|ɏdzXˆØk*ÇÚ{#ñ‰pã(€¶‡9ý'šÜ³½¯kƒ†Â’ëSOƒ•Å®H6+a¢µˆ‹Y¨b˜b+XˆÖ!°u!¢"µˆˆn2ˆ Å0Ä`Å Ô®c(ÓÜGº•Ä»Œ âWî%q.áÐÄ÷î%umÍ£+ÜQ,Vn¦-GphU¸¢X­&,Gt ·K¢ÅÅ·¥RÄÊÉbKn N­R2g$VµÕó Qr#–ÓZUÏð0ÎtÙÀœz ëöo“Îk¯n„T°"SÞ+³/…€½"ižãO%Í%&Δâ‘ËØ´IˆÓ°Ë‰ÞM3.™yçÁzit›`4C`k¢¼á!álÄï sÂ[×!¤l¾æo"ƒIÔ'@ªu)3›ô®“@Z à¾] üœ'‡4¸PÞkÁ-qÄH‰ÓÍBd´ÿ —¹°ÄA&ÊUC„öz‚‚RæŠtf¬>˜ÑidšEçÑL÷áý¡V±h§­c¿¥¨ƒ@g¼…Ýö^ÞËc.ž„æ“I[ ª+·zÐÒÚ,½Ð»¶€C›7jk$๖€Ž¾ÅÙpp¶ÍþéΤ ½ØÎŒq ñpæžÅ¥#Øâ49ÃÂK^MYV“,¶Éz_òæ:…;Õ«¦I3âJ£¤û5$7Èxün\â]Êe¯(Ÿ&Ž‹¶2<6Ćf× æ¢ƱqWı[(ÙÂŽç‚T7¨@ÈÉÍß‚ïÙ"&3TYlGށ†)†#5ŠA©^T2ÆÁ†)©†©†¤yGXÁ†©"†)©¼ÅV WS†#©]R–r‹q5ÕbâWþBh¯u+ªÅÄ×ü„nÑ^êkªÁj‰jeœWˆÕÕ`µDµ2Ì+Ä µDµµ5Ô{¤Þ0RF’H÷AÛ<ÊÁ§ ƈæ0Ñ£ê4 öÚµ[¬/1²€â<-¬šö²r×0o×w ´xÐÒË„ g01¬„åEM蛇Ã\6µE›‘fÙO k÷CºnÓ1`iîÒ² áÂ!ÑLÄÿ ³¬\õ*:M=Ç»c‹2[‰ØsÕLò\ŒK L^™üÄN\¶#» Ç\……ßïIœÉ$¸ÎñÍ®ÔHÇ Ûí/´>Li”„†g^$ 7NÍÑÑ2M2ªNg=k½Ð}Ÿl6‡ —JWˆ”ÞéT·+µ¦üÂFè¢Vs6ýÆ4»2+2IÝ9ô9®ÇGÙÇ1+Ä8;*Õ§ž½D*îÑWÚçá3Ma¤·ˆ¥t}š²Ü…Q+Æüµ8HíS”üxGéÁˆ¦Ël„éÈ 5 µÄƒ6äÜ(kIš¯S†fÖ&¸®×v|Äyˆ*禌Éù@7-®Ä[]ÎÖ¿ê`º…=:…žJVnݳvðÖKE¼‚ »mà ÷f®:Ë ™–4rð¡4` ܹçž~‹C=œ…¾Ïj|VDtK.ƒ"9 Ï ­–éXá³6WÒïIL-Ї Ç db/install.php000064400000002162152267045410007317 0ustar00. /** * This file replaces the legacy STATEMENTS section in db/install.xml, * lib.php/modulename_install() post installation hook and partially defaults.php * * @package workshopform_numerrors * @copyright 2010 David Mudrak * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Post installation procedure */ function xmldb_workshopform_numerrors_install() { global $CFG, $DB; } db/upgrade.php000064400000003271152267045410007302 0ustar00. /** * Keeps track of upgrades to the workshop numerrors grading strategy * * @package workshopform_numerrors * @copyright 2010 David Mudrak * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Performs upgrade of the database structure and data * * @param int $oldversion the version we are upgrading from * @return bool true */ function xmldb_workshopform_numerrors_upgrade($oldversion) { // Automatically generated Moodle v4.2.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.3.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.4.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.5.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v5.0.0 release upgrade line. // Put any upgrade step following this. return true; } db/install.xml000064400000005724152267045410007337 0ustar00
lang/en/workshopform_numerrors.php000064400000004225152267045410013465 0ustar00. /** * Strings for component 'workshopform_numerrors', language 'en', branch 'MOODLE_20_STABLE' * * @package workshopform_numerrors * @copyright 2009 David Mudrak * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['addmoredimensions'] = 'Blanks for {$a} more assertions'; $string['configgrade0'] = 'The default word describing the negative assessment of an assertion.'; $string['configgrade1'] = 'The default word describing the positive assessment of an assertion.'; $string['dimensioncommentfor'] = 'Comment for {$a}'; $string['dimensiondescription'] = 'Description'; $string['dimensiongrade'] = 'Grade'; $string['dimensionnumber'] = 'Assertion {$a}'; $string['dimensionweight'] = 'Weight'; $string['grademapping'] = 'Grade mapping table'; $string['grade0'] = 'Word for the error'; $string['grade0default'] = 'No'; $string['grade1'] = 'Word for the success'; $string['grade1default'] = 'Yes'; $string['maperror'] = 'Weighted number of errors is less than or equals'; $string['mapgrade'] = 'Grade for submission'; $string['pluginname'] = 'Number of errors'; $string['privacy:metadata'] = 'The Number of errors plugin only stores the details of the assessment form. Actual personal data of how the form has been filled are stored by the Workshop module itself and are attached to exported assessments.'; $string['privacy:export:metadata:map'] = 'If the weighted number of errors reaches {$a->nonegative} then the grade is {$a->grade} percents.'; backup/moodle2/restore_workshopform_numerrors_subplugin.class.php000064400000014533152267045410021712 0ustar00. /** * @package workshopform_numerrors * @copyright 2010 onwards David Mudrak * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); /** * restore subplugin class that provides the necessary information * needed to restore one workshopform_numerrors subplugin. */ class restore_workshopform_numerrors_subplugin extends restore_subplugin { //////////////////////////////////////////////////////////////////////////// // mappings of XML paths to the processable methods //////////////////////////////////////////////////////////////////////////// /** * Returns the paths to be handled by the subplugin at workshop level */ protected function define_workshop_subplugin_structure() { $paths = array(); $elename = $this->get_namefor('map'); $elepath = $this->get_pathfor('/workshopform_numerrors_map'); // we used get_recommended_name() so this works $paths[] = new restore_path_element($elename, $elepath); $elename = $this->get_namefor('dimension'); $elepath = $this->get_pathfor('/workshopform_numerrors_dimension'); // we used get_recommended_name() so this works $paths[] = new restore_path_element($elename, $elepath); return $paths; // And we return the interesting paths } /** * Returns the paths to be handled by the subplugin at referenceassessment level */ protected function define_referenceassessment_subplugin_structure() { $paths = array(); $elename = $this->get_namefor('referencegrade'); $elepath = $this->get_pathfor('/workshopform_numerrors_referencegrade'); // we used get_recommended_name() so this works $paths[] = new restore_path_element($elename, $elepath); return $paths; // And we return the interesting paths } /** * Returns the paths to be handled by the subplugin at exampleassessment level */ protected function define_exampleassessment_subplugin_structure() { $paths = array(); $elename = $this->get_namefor('examplegrade'); $elepath = $this->get_pathfor('/workshopform_numerrors_examplegrade'); // we used get_recommended_name() so this works $paths[] = new restore_path_element($elename, $elepath); return $paths; // And we return the interesting paths } /** * Returns the paths to be handled by the subplugin at assessment level */ protected function define_assessment_subplugin_structure() { $paths = array(); $elename = $this->get_namefor('grade'); $elepath = $this->get_pathfor('/workshopform_numerrors_grade'); // we used get_recommended_name() so this works $paths[] = new restore_path_element($elename, $elepath); return $paths; // And we return the interesting paths } //////////////////////////////////////////////////////////////////////////// // defined path elements are dispatched to the following methods //////////////////////////////////////////////////////////////////////////// /** * Processes the workshopform_numerrors_map element */ public function process_workshopform_numerrors_map($data) { global $DB; $data = (object)$data; $data->workshopid = $this->get_new_parentid('workshop'); $DB->insert_record('workshopform_numerrors_map', $data); } /** * Processes the workshopform_numerrors_dimension element */ public function process_workshopform_numerrors_dimension($data) { global $DB; $data = (object)$data; $oldid = $data->id; $data->workshopid = $this->get_new_parentid('workshop'); $newitemid = $DB->insert_record('workshopform_numerrors', $data); $this->set_mapping($this->get_namefor('dimension'), $oldid, $newitemid, true); // Process files for this workshopform_numerrors->id only $this->add_related_files('workshopform_numerrors', 'description', $this->get_namefor('dimension'), null, $oldid); } /** * Processes the workshopform_numerrors_referencegrade element */ public function process_workshopform_numerrors_referencegrade($data) { $this->process_dimension_grades_structure('workshop_referenceassessment', $data); } /** * Processes the workshopform_numerrors_examplegrade element */ public function process_workshopform_numerrors_examplegrade($data) { $this->process_dimension_grades_structure('workshop_exampleassessment', $data); } /** * Processes the workshopform_numerrors_grade element */ public function process_workshopform_numerrors_grade($data) { $this->process_dimension_grades_structure('workshop_assessment', $data); } //////////////////////////////////////////////////////////////////////////// // internal private methods //////////////////////////////////////////////////////////////////////////// /** * Process the dimension grades linked with the given type of assessment * * Populates the workshop_grades table with new records mapped to the restored * instances of assessments. * * @param mixed $elementname the name of the assessment element * @param array $data parsed xml data */ private function process_dimension_grades_structure($elementname, $data) { global $DB; $data = (object)$data; $oldid = $data->id; $data->assessmentid = $this->get_new_parentid($elementname); $data->strategy = 'numerrors'; $data->dimensionid = $this->get_mappingid($this->get_namefor('dimension'), $data->dimensionid); $DB->insert_record('workshop_grades', $data); } } backup/moodle2/backup_workshopform_numerrors_subplugin.class.php000064400000010557152267045410021476 0ustar00. /** * @package workshopform_numerrors * @copyright 2010 onwards David Mudrak * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); /** * Provides the information to backup numerrors grading strategy information */ class backup_workshopform_numerrors_subplugin extends backup_subplugin { /** * Returns the assessment form definition to attach to 'workshop' XML element */ protected function define_workshop_subplugin_structure() { // XML nodes declaration $subplugin = $this->get_subplugin_element(); // virtual optigroup element $subpluginwrapper = new backup_nested_element($this->get_recommended_name()); $subpluginmap = new backup_nested_element('workshopform_numerrors_map', array('id'), array( 'nonegative', 'grade')); $subplugindimension = new backup_nested_element('workshopform_numerrors_dimension', array('id'), array( 'sort', 'description', 'descriptionformat', 'grade0', 'grade1', 'weight')); // connect XML elements into the tree $subplugin->add_child($subpluginwrapper); $subpluginwrapper->add_child($subpluginmap); $subpluginwrapper->add_child($subplugindimension); // set source to populate the data $subpluginmap->set_source_table('workshopform_numerrors_map', array('workshopid' => backup::VAR_ACTIVITYID)); $subplugindimension->set_source_table('workshopform_numerrors', array('workshopid' => backup::VAR_ACTIVITYID)); // file annotations $subplugindimension->annotate_files('workshopform_numerrors', 'description', 'id'); return $subplugin; } /** * Returns the dimension grades to attach to 'referenceassessment' XML element */ protected function define_referenceassessment_subplugin_structure() { return $this->dimension_grades_structure('workshopform_numerrors_referencegrade'); } /** * Returns the dimension grades to attach to 'exampleassessment' XML element */ protected function define_exampleassessment_subplugin_structure() { return $this->dimension_grades_structure('workshopform_numerrors_examplegrade'); } /** * Returns the dimension grades to attach to 'assessment' XML element */ protected function define_assessment_subplugin_structure() { return $this->dimension_grades_structure('workshopform_numerrors_grade'); } //////////////////////////////////////////////////////////////////////////// // internal private methods //////////////////////////////////////////////////////////////////////////// /** * Returns the structure of dimension grades * * @param string forst parameter of {@link backup_nested_element} constructor */ private function dimension_grades_structure($elementname) { // create XML elements $subplugin = $this->get_subplugin_element(); // virtual optigroup element $subpluginwrapper = new backup_nested_element($this->get_recommended_name()); $subplugingrade = new backup_nested_element($elementname, array('id'), array( 'dimensionid', 'grade', 'peercomment', 'peercommentformat')); // connect XML elements into the tree $subplugin->add_child($subpluginwrapper); $subpluginwrapper->add_child($subplugingrade); // set source to populate the data $subplugingrade->set_source_sql( "SELECT id, dimensionid, grade, peercomment, peercommentformat FROM {workshop_grades} WHERE strategy = 'numerrors' AND assessmentid = ?", array(backup::VAR_PARENTID)); return $subplugin; } } backup/moodle1/lib.php000064400000011245152267045410010641 0ustar00. /** * Provides support for the conversion of moodle1 backup to the moodle2 format * * @package workshopform_numerrors * @copyright 2011 David Mudrak * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); require_once($CFG->libdir.'/gradelib.php'); // grade_floatval() called here /** * Conversion handler for the numerrors grading strategy data */ class moodle1_workshopform_numerrors_handler extends moodle1_workshopform_handler { /** @var array */ protected $mappings = array(); /** @var array */ protected $dimensions = array(); /** * New workshop instance is being processed */ public function on_elements_start() { $this->mappings = array(); $this->dimensions = array(); } /** * Converts into and stores it for later writing * * @param array $data legacy element data * @param array $raw raw element data * * @return array to be written to workshop.xml */ public function process_legacy_element(array $data, array $raw) { $workshop = $this->parenthandler->get_current_workshop(); $mapping = array(); $mapping['id'] = $data['id']; $mapping['nonegative'] = $data['elementno']; if ($workshop['grade'] == 0 or $data['maxscore'] == 0) { $mapping['grade'] = 0; } else { $mapping['grade'] = grade_floatval($data['maxscore'] / $workshop['grade'] * 100); } $this->mappings[] = $mapping; $converted = null; if (trim($data['description']) and $data['description'] <> '@@ GRADE_MAPPING_ELEMENT @@') { // prepare a fake record and re-use the upgrade logic $fakerecord = (object)$data; $converted = (array)workshopform_numerrors_upgrade_element($fakerecord, 12345678); unset($converted['workshopid']); $converted['id'] = $data['id']; $this->dimensions[] = $converted; } return $converted; } /** * Writes gathered mappings and dimensions */ public function on_elements_end() { foreach ($this->mappings as $mapping) { $this->write_xml('workshopform_numerrors_map', $mapping, array('/workshopform_numerrors_map/id')); } foreach ($this->dimensions as $dimension) { $this->write_xml('workshopform_numerrors_dimension', $dimension, array('/workshopform_numerrors_dimension/id')); } } } /** * Transforms a given record from workshop_elements_old into an object to be saved into workshopform_numerrors * * @param stdClass $old legacy record from workshop_elements_old * @param int $newworkshopid id of the new workshop instance that replaced the previous one * @return stdclass to be saved in workshopform_numerrors */ function workshopform_numerrors_upgrade_element(stdclass $old, $newworkshopid) { $new = new stdclass(); $new->workshopid = $newworkshopid; $new->sort = $old->elementno; $new->description = $old->description; $new->descriptionformat = FORMAT_HTML; $new->grade0 = get_string('grade0default', 'workshopform_numerrors'); $new->grade1 = get_string('grade1default', 'workshopform_numerrors'); // calculate new weight of the element. Negative weights are not supported any more and // are replaced with weight = 0. Legacy workshop did not store the raw weight but the index // in the array of weights (see $WORKSHOP_EWEIGHTS in workshop 1.x) // workshop 2.0 uses integer weights only (0-16) so all previous weights are multiplied by 4. switch ($old->weight) { case 8: $new->weight = 1; break; case 9: $new->weight = 2; break; case 10: $new->weight = 3; break; case 11: $new->weight = 4; break; case 12: $new->weight = 6; break; case 13: $new->weight = 8; break; case 14: $new->weight = 16; break; default: $new->weight = 0; } return $new; } edit_form.php000064400000012235152267045410007236 0ustar00. /** * This file defines an mform to edit "Number of errors" grading strategy forms. * * @package workshopform_numerrors * @copyright 2009 David Mudrak * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); require_once(__DIR__ . '/../../lib.php'); // module library require_once(__DIR__ . '/../edit_form.php'); // parent class definition /** * Class for editing "Number of errors" grading strategy forms. * * @uses moodleform */ class workshop_edit_numerrors_strategy_form extends workshop_edit_strategy_form { /** * Define the elements to be displayed at the form * * Called by the parent::definition() * * @return void */ protected function definition_inner(&$mform) { $plugindefaults = get_config('workshopform_numerrors'); $nodimensions = $this->_customdata['nodimensions']; // number of currently filled dimensions $norepeats = $this->_customdata['norepeats']; // number of dimensions to display $descriptionopts = $this->_customdata['descriptionopts']; // wysiwyg fields options $current = $this->_customdata['current']; // current data to be set $mform->addElement('hidden', 'norepeats', $norepeats); $mform->setType('norepeats', PARAM_INT); // value not to be overridden by submitted value $mform->setConstants(array('norepeats' => $norepeats)); for ($i = 0; $i < $norepeats; $i++) { $mform->addElement('header', 'dimension'.$i, get_string('dimensionnumber', 'workshopform_numerrors', $i+1)); $mform->addElement('hidden', 'dimensionid__idx_'.$i); // the id in workshop_forms $mform->setType('dimensionid__idx_'.$i, PARAM_INT); $mform->addElement('editor', 'description__idx_'.$i.'_editor', get_string('dimensiondescription', 'workshopform_numerrors'), '', $descriptionopts); $mform->addElement('text', 'grade0__idx_'.$i, get_string('grade0', 'workshopform_numerrors'), array('size'=>'15')); $mform->setDefault('grade0__idx_'.$i, $plugindefaults->grade0); $mform->setType('grade0__idx_'.$i, PARAM_TEXT); $mform->addElement('text', 'grade1__idx_'.$i, get_string('grade1', 'workshopform_numerrors'), array('size'=>'15')); $mform->setDefault('grade1__idx_'.$i, $plugindefaults->grade1); $mform->setType('grade1__idx_'.$i, PARAM_TEXT); $mform->addElement('select', 'weight__idx_'.$i, get_string('dimensionweight', 'workshopform_numerrors'), workshop::available_dimension_weights_list()); $mform->setDefault('weight__idx_'.$i, 1); } $mform->addElement('header', 'mappingheader', get_string('grademapping', 'workshopform_numerrors')); $mform->addElement('static', 'mappinginfo', get_string('maperror', 'workshopform_numerrors'), get_string('mapgrade', 'workshopform_numerrors')); // get the total weight of all items == maximum weighted number of errors $totalweight = 0; for ($i = 0; $i < $norepeats; $i++) { if (!empty($current->{'weight__idx_'.$i})) { $totalweight += $current->{'weight__idx_'.$i}; } } $totalweight = max($totalweight, $nodimensions); $percents = array(); $percents[''] = ''; for ($i = 100; $i >= 0; $i--) { $percents[$i] = get_string('percents', 'moodle', $i); } $mform->addElement('static', 'mappingzero', 0, get_string('percents', 'moodle', 100)); for ($i = 1; $i <= $totalweight; $i++) { $selects = array(); $selects[] = $mform->createElement('select', 'map__idx_'.$i, $i, $percents); $selects[] = $mform->createElement('static', 'mapdefault__idx_'.$i, '', get_string('percents', 'moodle', floor(100 - $i * 100 / $totalweight))); $mform->addGroup($selects, 'grademapping'.$i, $i, array(' '), false); $mform->setDefault('map__idx_'.$i, ''); } $mform->registerNoSubmitButton('noadddims'); $mform->addElement('submit', 'noadddims', get_string('addmoredimensions', 'workshopform_numerrors', workshop_numerrors_strategy::ADDDIMS)); $mform->closeHeaderBefore('noadddims'); $this->set_data($current); } } lib.php000064400000061230152267045410006033 0ustar00. /** * This file defines a class with "Number of errors" grading strategy logic * * @package workshopform_numerrors * @copyright 2009 David Mudrak * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); require_once(__DIR__ . '/../lib.php'); // Interface definition. require_once($CFG->libdir . '/gradelib.php'); // To handle float vs decimal issues. /** * Server workshop files * * @category files * @param stdClass $course course object * @param stdClass $cm course module object * @param stdClass $context context object * @param string $filearea file area * @param array $args extra arguments * @param bool $forcedownload whether or not force download * @param array $options additional options affecting the file serving * @return bool */ function workshopform_numerrors_pluginfile($course, $cm, $context, $filearea, array $args, $forcedownload, array $options=array()) { global $DB; if ($context->contextlevel != CONTEXT_MODULE) { return false; } require_login($course, true, $cm); if ($filearea !== 'description') { return false; } $itemid = (int)array_shift($args); // the id of the assessment form dimension if (!$workshop = $DB->get_record('workshop', array('id' => $cm->instance))) { send_file_not_found(); } if (!$dimension = $DB->get_record('workshopform_numerrors', array('id' => $itemid ,'workshopid' => $workshop->id))) { send_file_not_found(); } // TODO now make sure the user is allowed to see the file // (media embedded into the dimension description) $fs = get_file_storage(); $relativepath = implode('/', $args); $fullpath = "/$context->id/workshopform_numerrors/$filearea/$itemid/$relativepath"; if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) { return false; } // finally send the file send_stored_file($file, 0, 0, $forcedownload, $options); } /** * "Number of errors" grading strategy logic. */ class workshop_numerrors_strategy implements workshop_strategy { /** @var default number of dimensions to show */ const MINDIMS = 3; /** @var number of dimensions to add */ const ADDDIMS = 2; /** @var workshop the parent workshop instance */ protected $workshop; /** @var array definition of the assessment form fields */ protected $dimensions = null; /** @var array mapping of the number of errors to a grade */ protected $mappings = null; /** @var array options for dimension description fields */ protected $descriptionopts; /** * Constructor * * @param workshop $workshop The workshop instance record * @return void */ public function __construct(workshop $workshop) { $this->workshop = $workshop; $this->dimensions = $this->load_fields(); $this->mappings = $this->load_mappings(); $this->descriptionopts = array('trusttext' => true, 'subdirs' => false, 'maxfiles' => -1); } /** * Factory method returning an instance of an assessment form editor class * * @param $actionurl URL of form handler, defaults to auto detect the current url */ public function get_edit_strategy_form($actionurl=null) { global $CFG; // needed because the included files use it global $PAGE; require_once(__DIR__ . '/edit_form.php'); $fields = $this->prepare_form_fields($this->dimensions, $this->mappings); $nodimensions = count($this->dimensions); $norepeatsdefault = max($nodimensions + self::ADDDIMS, self::MINDIMS); $norepeats = optional_param('norepeats', $norepeatsdefault, PARAM_INT); // number of dimensions $noadddims = optional_param('noadddims', '', PARAM_ALPHA); // shall we add more? if ($noadddims) { $norepeats += self::ADDDIMS; } // Append editor context to editor options, giving preference to existing context. $this->descriptionopts = array_merge(array('context' => $PAGE->context), $this->descriptionopts); // prepare the embeded files for ($i = 0; $i < $nodimensions; $i++) { // prepare all editor elements $fields = file_prepare_standard_editor($fields, 'description__idx_'.$i, $this->descriptionopts, $PAGE->context, 'workshopform_numerrors', 'description', $fields->{'dimensionid__idx_'.$i}); } $customdata = array(); $customdata['workshop'] = $this->workshop; $customdata['strategy'] = $this; $customdata['norepeats'] = $norepeats; $customdata['nodimensions'] = $nodimensions; $customdata['descriptionopts'] = $this->descriptionopts; $customdata['current'] = $fields; $attributes = array('class' => 'editstrategyform'); return new workshop_edit_numerrors_strategy_form($actionurl, $customdata, 'post', '', $attributes); } /** * Save the assessment dimensions into database * * Saves data into the main strategy form table. If the record->id is null or zero, * new record is created. If the record->id is not empty, the existing record is updated. Records with * empty 'description' field are removed from database. * The passed data object are the raw data returned by the get_data(). * * @uses $DB * @param stdClass $data Raw data returned by the dimension editor form * @return void */ public function save_edit_strategy_form(stdclass $data) { global $DB, $PAGE; $workshopid = $data->workshopid; $norepeats = $data->norepeats; $data = $this->prepare_database_fields($data); $records = $data->numerrors; // data to be saved into {workshopform_numerrors} $mappings = $data->mappings; // data to be saved into {workshopform_numerrors_map} $todelete = array(); // dimension ids to be deleted $maxnonegative = 0; // maximum number of (weighted) negative responses for ($i=0; $i < $norepeats; $i++) { $record = $records[$i]; if (0 == strlen(trim($record->description_editor['text']))) { if (!empty($record->id)) { // existing dimension record with empty description - to be deleted $todelete[] = $record->id; } continue; } if (empty($record->id)) { // new field $record->id = $DB->insert_record('workshopform_numerrors', $record); } else { // exiting field $DB->update_record('workshopform_numerrors', $record); } $maxnonegative += $record->weight; // re-save with correct path to embeded media files $record = file_postupdate_standard_editor($record, 'description', $this->descriptionopts, $PAGE->context, 'workshopform_numerrors', 'description', $record->id); $DB->update_record('workshopform_numerrors', $record); } $this->delete_dimensions($todelete); // re-save the mappings $todelete = array(); foreach ($data->mappings as $nonegative => $grade) { if (is_null($grade)) { // no grade set for this number of negative responses $todelete[] = $nonegative; continue; } if (isset($this->mappings[$nonegative])) { $DB->set_field('workshopform_numerrors_map', 'grade', $grade, array('workshopid' => $this->workshop->id, 'nonegative' => $nonegative)); } else { $DB->insert_record('workshopform_numerrors_map', (object)array('workshopid' => $this->workshop->id, 'nonegative' => $nonegative, 'grade' => $grade)); } } // clear mappings that are not valid any more if (!empty($todelete)) { list($insql, $params) = $DB->get_in_or_equal($todelete, SQL_PARAMS_NAMED); $insql = "nonegative $insql OR "; } else { $insql = ''; } $sql = "DELETE FROM {workshopform_numerrors_map} WHERE (($insql nonegative > :maxnonegative) AND (workshopid = :workshopid))"; $params['maxnonegative'] = $maxnonegative; $params['workshopid'] = $this->workshop->id; $DB->execute($sql, $params); } /** * Factory method returning an instance of an assessment form * * @param moodle_url $actionurl URL of form handler, defaults to auto detect the current url * @param string $mode Mode to open the form in: preview/assessment * @param stdClass $assessment * @param bool $editable * @param array $options */ public function get_assessment_form(?moodle_url $actionurl=null, $mode='preview', ?stdclass $assessment=null, $editable=true, $options=array()) { global $CFG; // needed because the included files use it global $PAGE; global $DB; require_once(__DIR__ . '/assessment_form.php'); $fields = $this->prepare_form_fields($this->dimensions, $this->mappings); $nodimensions = count($this->dimensions); // rewrite URLs to the embeded files for ($i = 0; $i < $nodimensions; $i++) { $fields->{'description__idx_'.$i} = file_rewrite_pluginfile_urls($fields->{'description__idx_'.$i}, 'pluginfile.php', $PAGE->context->id, 'workshopform_numerrors', 'description', $fields->{'dimensionid__idx_'.$i}); } if ('assessment' === $mode and !empty($assessment)) { // load the previously saved assessment data $grades = $this->get_current_assessment_data($assessment); $current = new stdclass(); for ($i = 0; $i < $nodimensions; $i++) { $dimid = $fields->{'dimensionid__idx_'.$i}; if (isset($grades[$dimid])) { $current->{'gradeid__idx_'.$i} = $grades[$dimid]->id; $current->{'grade__idx_'.$i} = ($grades[$dimid]->grade == 0 ? -1 : 1); $current->{'peercomment__idx_'.$i} = $grades[$dimid]->peercomment; } } } // set up the required custom data common for all strategies $customdata['workshop'] = $this->workshop; $customdata['strategy'] = $this; $customdata['mode'] = $mode; $customdata['options'] = $options; // set up strategy-specific custom data $customdata['nodims'] = $nodimensions; $customdata['fields'] = $fields; $customdata['current'] = isset($current) ? $current : null; $attributes = array('class' => 'assessmentform numerrors'); return new workshop_numerrors_assessment_form($actionurl, $customdata, 'post', '', $attributes, $editable); } /** * Saves the filled assessment * * This method processes data submitted using the form returned by {@link get_assessment_form()} * * @param stdClass $assessment Assessment being filled * @param stdClass $data Raw data as returned by the assessment form * @return float|null Raw grade (from 0.00000 to 100.00000) for submission as suggested by the peer */ public function save_assessment(stdclass $assessment, stdclass $data) { global $DB; if (!isset($data->nodims)) { throw new coding_exception('You did not send me the number of assessment dimensions to process'); } for ($i = 0; $i < $data->nodims; $i++) { $grade = new stdclass(); $grade->id = $data->{'gradeid__idx_' . $i}; $grade->assessmentid = $assessment->id; $grade->strategy = 'numerrors'; $grade->dimensionid = $data->{'dimensionid__idx_' . $i}; $grade->grade = ($data->{'grade__idx_' . $i} <= 0 ? 0 : 1); $grade->peercomment = $data->{'peercomment__idx_' . $i}; $grade->peercommentformat = FORMAT_HTML; if (empty($grade->id)) { // new grade $grade->id = $DB->insert_record('workshop_grades', $grade); } else { // updated grade $DB->update_record('workshop_grades', $grade); } } return $this->update_peer_grade($assessment); } /** * Has the assessment form been defined and is ready to be used by the reviewers? * * @return boolean */ public function form_ready() { if (count($this->dimensions) > 0) { return true; } return false; } /** * @see parent::get_assessments_recordset() */ public function get_assessments_recordset($restrict=null) { global $DB; $sql = 'SELECT s.id AS submissionid, a.id AS assessmentid, a.weight AS assessmentweight, a.reviewerid, a.gradinggrade, g.dimensionid, g.grade FROM {workshop_submissions} s JOIN {workshop_assessments} a ON (a.submissionid = s.id) JOIN {workshop_grades} g ON (g.assessmentid = a.id AND g.strategy = :strategy) WHERE s.example=0 AND s.workshopid=:workshopid'; // to be cont. $params = array('workshopid' => $this->workshop->id, 'strategy' => $this->workshop->strategy); if (is_null($restrict)) { // update all users - no more conditions } elseif (!empty($restrict)) { list($usql, $uparams) = $DB->get_in_or_equal($restrict, SQL_PARAMS_NAMED); $sql .= " AND a.reviewerid $usql"; $params = array_merge($params, $uparams); } else { throw new coding_exception('Empty value is not a valid parameter here'); } $sql .= ' ORDER BY s.id'; // this is important for bulk processing return $DB->get_recordset_sql($sql, $params); } /** * @see parent::get_dimensions_info() */ public function get_dimensions_info() { global $DB; $params = array('workshopid' => $this->workshop->id); $dimrecords = $DB->get_records('workshopform_numerrors', array('workshopid' => $this->workshop->id), 'sort', 'id,weight'); foreach ($dimrecords as $dimid => $dimrecord) { $dimrecords[$dimid]->min = 0; $dimrecords[$dimid]->max = 1; } return $dimrecords; } /** * Is a given scale used by the instance of workshop? * * This grading strategy does not use scales. * * @param int $scaleid id of the scale to check * @param int|null $workshopid id of workshop instance to check, checks all in case of null * @return bool */ public static function scale_used($scaleid, $workshopid=null) { return false; } /** * Delete all data related to a given workshop module instance * * @see workshop_delete_instance() * @param int $workshopid id of the workshop module instance being deleted * @return void */ public static function delete_instance($workshopid) { global $DB; $DB->delete_records('workshopform_numerrors', array('workshopid' => $workshopid)); $DB->delete_records('workshopform_numerrors_map', array('workshopid' => $workshopid)); } //////////////////////////////////////////////////////////////////////////////// // Internal methods // //////////////////////////////////////////////////////////////////////////////// /** * Loads the fields of the assessment form currently used in this workshop * * @return array definition of assessment dimensions */ protected function load_fields() { global $DB; $sql = 'SELECT * FROM {workshopform_numerrors} WHERE workshopid = :workshopid ORDER BY sort'; $params = array('workshopid' => $this->workshop->id); return $DB->get_records_sql($sql, $params); } /** * Loads the mappings of the number of errors to the grade * * @return array of records */ protected function load_mappings() { global $DB; return $DB->get_records('workshopform_numerrors_map', array('workshopid' => $this->workshop->id), 'nonegative', 'nonegative,grade'); // we can use nonegative as key here as it must be unique within workshop } /** * Prepares the database data to be used by the mform * * @param array $dims Array of raw dimension records as returned by {@link load_fields()} * @param array $maps Array of raw mapping records as returned by {@link load_mappings()} * @return array Array of fields data to be used by the mform set_data */ protected function prepare_form_fields(array $dims, array $maps) { $formdata = new stdclass(); $key = 0; foreach ($dims as $dimension) { $formdata->{'dimensionid__idx_' . $key} = $dimension->id; $formdata->{'description__idx_' . $key} = $dimension->description; $formdata->{'description__idx_' . $key.'format'} = $dimension->descriptionformat; $formdata->{'grade0__idx_' . $key} = $dimension->grade0; $formdata->{'grade1__idx_' . $key} = $dimension->grade1; $formdata->{'weight__idx_' . $key} = $dimension->weight; $key++; } foreach ($maps as $nonegative => $map) { $formdata->{'map__idx_' . $nonegative} = $map->grade; } return $formdata; } /** * Deletes dimensions and removes embedded media from its descriptions * * todo we may check that there are no assessments done using these dimensions and probably remove them * * @param array $ids list to delete * @return void */ protected function delete_dimensions(array $ids) { global $DB, $PAGE; $fs = get_file_storage(); foreach ($ids as $id) { $fs->delete_area_files($PAGE->context->id, 'workshopform_numerrors', 'description', $id); } $DB->delete_records_list('workshopform_numerrors', 'id', $ids); } /** * Prepares data returned by {@link workshop_edit_numerrors_strategy_form} so they can be saved into database * * It automatically adds some columns into every record. The sorting is * done by the order of the returned array and starts with 1. * Called internally from {@link save_edit_strategy_form()} only. Could be private but * keeping protected for unit testing purposes. * * @param stdClass $raw Raw data returned by mform * @return array Array of objects to be inserted/updated in DB */ protected function prepare_database_fields(stdclass $raw) { global $PAGE; $cook = new stdclass(); // to be returned $cook->numerrors = array(); // to be stored in {workshopform_numerrors} $cook->mappings = array(); // to be stored in {workshopform_numerrors_map} for ($i = 0; $i < $raw->norepeats; $i++) { $cook->numerrors[$i] = new stdclass(); $cook->numerrors[$i]->id = $raw->{'dimensionid__idx_'.$i}; $cook->numerrors[$i]->workshopid = $this->workshop->id; $cook->numerrors[$i]->sort = $i + 1; $cook->numerrors[$i]->description_editor = $raw->{'description__idx_'.$i.'_editor'}; $cook->numerrors[$i]->grade0 = $raw->{'grade0__idx_'.$i}; $cook->numerrors[$i]->grade1 = $raw->{'grade1__idx_'.$i}; $cook->numerrors[$i]->weight = $raw->{'weight__idx_'.$i}; } $i = 1; while (isset($raw->{'map__idx_'.$i})) { if (is_numeric($raw->{'map__idx_'.$i})) { $cook->mappings[$i] = $raw->{'map__idx_'.$i}; // should be a value from 0 to 100 } else { $cook->mappings[$i] = null; // the user did not set anything } $i++; } return $cook; } /** * Returns the list of current grades filled by the reviewer * * @param stdClass $assessment Assessment record * @return array of filtered records from the table workshop_grades */ protected function get_current_assessment_data(stdclass $assessment) { global $DB; if (empty($this->dimensions)) { return array(); } list($dimsql, $dimparams) = $DB->get_in_or_equal(array_keys($this->dimensions), SQL_PARAMS_NAMED); // beware! the caller may rely on the returned array is indexed by dimensionid $sql = "SELECT dimensionid, wg.* FROM {workshop_grades} wg WHERE assessmentid = :assessmentid AND strategy= :strategy AND dimensionid $dimsql"; $params = array('assessmentid' => $assessment->id, 'strategy' => 'numerrors'); $params = array_merge($params, $dimparams); return $DB->get_records_sql($sql, $params); } /** * Aggregates the assessment form data and sets the grade for the submission given by the peer * * @param stdClass $assessment Assessment record * @return float|null Raw grade (0.00000 to 100.00000) for submission as suggested by the peer */ protected function update_peer_grade(stdclass $assessment) { $grades = $this->get_current_assessment_data($assessment); $suggested = $this->calculate_peer_grade($grades); if (!is_null($suggested)) { $this->workshop->set_peer_grade($assessment->id, $suggested); } return $suggested; } /** * Calculates the aggregated grade given by the reviewer * * @param array $grades Grade records as returned by {@link get_current_assessment_data} * @return float|null Raw grade (0.00000 to 100.00000) for submission as suggested by the peer */ protected function calculate_peer_grade(array $grades) { if (empty($grades)) { return null; } $sumerrors = 0; // sum of the weighted errors (i.e. the negative responses) foreach ($grades as $grade) { if (grade_floats_different($grade->grade, 1.00000)) { // negative reviewer's response $sumerrors += $this->dimensions[$grade->dimensionid]->weight; } } return $this->errors_to_grade($sumerrors); } /** * Returns a grade 0.00000 to 100.00000 for the given number of errors * * This is where we use the mapping table defined by the teacher. If a grade for the given * number of errors (negative assertions) is not defined, the most recently defined one is used. * Example of the defined mapping: * Number of errors | Grade * 0 | 100% (always) * 1 | - (not defined) * 2 | 80% * 3 | 60% * 4 | - * 5 | 30% * 6 | 0% * With this mapping, one error is mapped to 100% grade and 4 errors is mapped to 60%. * * @param mixed $numerrors Number of errors * @return float Raw grade (0.00000 to 100.00000) for the given number of negative assertions */ protected function errors_to_grade($numerrors) { $grade = 100.00000; for ($i = 1; $i <= $numerrors; $i++) { if (isset($this->mappings[$i])) { $grade = $this->mappings[$i]->grade; } } if ($grade > 100.00000) { $grade = 100.00000; } if ($grade < 0.00000) { $grade = 0.00000; } return grade_floatval($grade); } } version.php000064400000002304152267045410006747 0ustar00. /** * Defines the version of workshop Number of errors grading strategy subplugin * * This code fragment is called by moodle_needs_upgrading() and * /admin/index.php * * @package workshopform_numerrors * @copyright 2009 David Mudrak * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2025041400; $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'workshopform_numerrors'; assessment_form.php000064400000007007152267045410010477 0ustar00. /** * This file defines an mform to assess a submission by numerrors grading strategy * * @package workshopform_numerrors * @copyright 2009 David Mudrak * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); require_once(__DIR__ . '/../assessment_form.php'); // Parent class definition. /** * Class representing a form for assessing submissions by numerrors grading strategy * * @uses moodleform */ class workshop_numerrors_assessment_form extends workshop_assessment_form { /** * Define the elements to be displayed at the form * * Called by the parent::definition() * * @return void */ protected function definition_inner(&$mform) { $fields = $this->_customdata['fields']; $current = $this->_customdata['current']; $nodims = $this->_customdata['nodims']; // number of assessment dimensions $mform->addElement('hidden', 'nodims', $nodims); $mform->setType('nodims', PARAM_INT); for ($i = 0; $i < $nodims; $i++) { // dimension header $dimtitle = get_string('dimensionnumber', 'workshopform_numerrors', $i+1); $mform->addElement('header', "dimensionhdr__idx_$i", $dimtitle); // dimension id $mform->addElement('hidden', 'dimensionid__idx_'.$i, $fields->{'dimensionid__idx_'.$i}); $mform->setType('dimensionid__idx_'.$i, PARAM_INT); // grade id $mform->addElement('hidden', 'gradeid__idx_'.$i); // value set by set_data() later $mform->setType('gradeid__idx_'.$i, PARAM_INT); // dimension description $desc = '
'."\n"; $desc .= format_text($fields->{'description__idx_'.$i}, $fields->{'description__idx_'.$i.'format'}); $desc .= "\n
"; $mform->addElement('html', $desc); // evaluation of the assertion $label = get_string('dimensiongrade', 'workshopform_numerrors'); $mform->addGroup(array( $mform->createElement('radio', 'grade__idx_' . $i, '', $fields->{'grade0__idx_'.$i}, -1), $mform->createElement('radio', 'grade__idx_' . $i, '', $fields->{'grade1__idx_'.$i}, 1), ), 'group_grade__idx_' . $i, get_string('yourassessmentfor', 'workshop', $dimtitle), '
', false); $mform->addRule('group_grade__idx_' . $i, get_string('required'), 'required'); // comment $label = get_string('dimensioncommentfor', 'workshopform_numerrors', $dimtitle); $mform->addElement('textarea', 'peercomment__idx_' . $i, $label, array('cols' => 60, 'rows' => 5)); } $this->set_data($current); } } classes/privacy/provider.php000064400000011102152267045410012222 0ustar00. /** * Provides the class {@link workshopform_numerrors\privacy\provider} * * @package workshopform_numerrors * @category privacy * @copyright 2018 David Mudrák * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace workshopform_numerrors\privacy; use core_privacy\local\request\writer; defined('MOODLE_INTERNAL') || die(); /** * Privacy API implementation for the Number of errors strategy. * * @copyright 2018 David Mudrák * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\null_provider, \mod_workshop\privacy\workshopform_provider { /** * Explain that this plugin stores no personal data. * * @return string */ public static function get_reason(): string { return 'privacy:metadata'; } /** * Return details of the filled assessment form. * * @param stdClass $user User we are exporting data for * @param context $context The workshop activity context * @param array $subcontext Subcontext within the context to export to * @param int $assessmentid ID of the assessment */ public static function export_assessment_form(\stdClass $user, \context $context, array $subcontext, int $assessmentid) { global $DB; if ($context->contextlevel != CONTEXT_MODULE) { throw new \coding_exception('Unexpected context provided'); } $sql = "SELECT dim.id, dim.workshopid, dim.description, dim.descriptionformat, dim.grade0, dim.grade1, dim.weight, wg.grade, wg.peercomment, wg.peercommentformat FROM {course_modules} cm JOIN {context} ctx ON ctx.contextlevel = :contextlevel AND ctx.instanceid = cm.id JOIN {workshop} w ON cm.instance = w.id JOIN {workshopform_numerrors} dim ON dim.workshopid = w.id LEFT JOIN {workshop_grades} wg ON wg.strategy = :strategy AND wg.dimensionid = dim.id AND wg.assessmentid = :assessmentid WHERE ctx.id = :contextid ORDER BY dim.sort"; $params = [ 'strategy' => 'numerrors', 'contextlevel' => CONTEXT_MODULE, 'contextid' => $context->id, 'assessmentid' => $assessmentid, ]; $writer = \core_privacy\local\request\writer::with_context($context); $data = []; $workshopid = null; $hasdata = false; $dimensionids = []; foreach ($DB->get_records_sql($sql, $params) as $record) { if ($record->grade !== null) { $hasdata = true; } $record->description = $writer->rewrite_pluginfile_urls($subcontext, 'workshopform_numerrors', 'description', $record->id, $record->description); $workshopid = $record->workshopid; $dimensionids[] = $record->id; unset($record->id); unset($record->workshopid); $data[] = $record; } if ($hasdata) { $writer->export_data($subcontext, (object)['assertions' => $data]); foreach ($dimensionids as $dimensionid) { $writer->export_area_files($subcontext, 'workshopform_numerrors', 'description', $dimensionid); } foreach ($DB->get_records('workshopform_numerrors_map', ['workshopid' => $workshopid], 'nonegative', 'id, nonegative, grade') as $mapping) { $writer->export_metadata($subcontext, 'map_'.$mapping->nonegative.'_errors', $mapping->grade, get_string('privacy:export:metadata:map', 'workshopform_numerrors', [ 'nonegative' => $mapping->nonegative, 'grade' => $mapping->grade ]) ); } } } } tests/privacy/provider_test.php000064400000012642152267045410013000 0ustar00. /** * Provides the {@see workshopform_numerrors\privacy\provider_test} class. * * @package workshopform_numerrors * @category test * @copyright 2018 David Mudrák * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace workshopform_numerrors\privacy; defined('MOODLE_INTERNAL') || die(); global $CFG; use core_privacy\local\request\writer; use core_privacy\tests\provider_testcase; /** * Unit tests for the privacy API implementation. * * @copyright 2018 David Mudrák * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class provider_test extends provider_testcase { /** @var \testing_data_generator data generator. */ protected $generator; /** @var \mod_workshop_generator workshop generator. */ protected $workshopgenerator; /** @var \stdClass course data. */ protected $course1; /** @var \stdClass student data. */ protected $student1; /** @var \stdClass student data. */ protected $student2; /** @var \stdClass first workshop in course1 */ protected $workshop11; /** @var int ID of the submission in workshop11 by student1 */ protected $submission111; /** @var int ID of the assessment of submission111 by student2 */ protected $assessment1112; /** @var bool|int true or new id */ protected $dim1; /** @var bool|int true or new id */ protected $dim2; /** * Test {@link workshopform_numerrors\privacy\provider::export_assessment_form()} implementation. */ public function test_export_assessment_form(): void { global $DB; $this->resetAfterTest(); $this->setAdminUser(); $this->generator = $this->getDataGenerator(); $this->workshopgenerator = $this->generator->get_plugin_generator('mod_workshop'); $this->course1 = $this->generator->create_course(); $this->workshop11 = $this->generator->create_module('workshop', [ 'course' => $this->course1, 'name' => 'Workshop11', ]); $DB->set_field('workshop', 'phase', 50, ['id' => $this->workshop11->id]); $this->dim1 = $DB->insert_record('workshopform_numerrors', [ 'workshopid' => $this->workshop11->id, 'sort' => 1, 'description' => 'Assertion 1 description', 'descriptionformat' => FORMAT_MARKDOWN, 'descriptiontrust' => 0, 'grade0' => 'No', 'grade1' => 'Yes', 'weight' => 1, ]); $this->dim2 = $DB->insert_record('workshopform_numerrors', [ 'workshopid' => $this->workshop11->id, 'sort' => 2, 'description' => 'Assertion 2 description', 'descriptionformat' => FORMAT_MARKDOWN, 'descriptiontrust' => 0, 'grade0' => 'Missing', 'grade1' => 'Present', 'weight' => 1, ]); $this->student1 = $this->generator->create_user(); $this->student2 = $this->generator->create_user(); $this->submission111 = $this->workshopgenerator->create_submission($this->workshop11->id, $this->student1->id); $this->assessment1112 = $this->workshopgenerator->create_assessment($this->submission111, $this->student2->id, [ 'grade' => 92, ]); $DB->insert_record('workshop_grades', [ 'assessmentid' => $this->assessment1112, 'strategy' => 'numerrors', 'dimensionid' => $this->dim1, 'grade' => 1, 'peercomment' => 'Awesome', 'peercommentformat' => FORMAT_PLAIN, ]); $DB->insert_record('workshop_grades', [ 'assessmentid' => $this->assessment1112, 'strategy' => 'numerrors', 'dimensionid' => $this->dim2, 'grade' => 0, 'peercomment' => 'Missing', 'peercommentformat' => FORMAT_PLAIN, ]); $contextlist = new \core_privacy\local\request\approved_contextlist($this->student2, 'mod_workshop', [ \context_module::instance($this->workshop11->cmid)->id, ]); \mod_workshop\privacy\provider::export_user_data($contextlist); $writer = writer::with_context(\context_module::instance($this->workshop11->cmid)); $form = $writer->get_data([ get_string('myassessments', 'mod_workshop'), $this->assessment1112, get_string('assessmentform', 'mod_workshop'), get_string('pluginname', 'workshopform_numerrors'), ]); $this->assertEquals('Assertion 1 description', $form->assertions[0]->description); $this->assertEquals(0, $form->assertions[1]->grade); $this->assertEquals('Missing', $form->assertions[1]->peercomment); } } tests/lib_test.php000064400000024322152267045410010235 0ustar00. /** * Unit tests for Number of errors grading logic * * @package workshopform_numerrors * @category test * @copyright 2009 David Mudrak * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace workshopform_numerrors; use workshop; use workshop_numerrors_strategy; defined('MOODLE_INTERNAL') || die(); // Include the code to test global $CFG; require_once($CFG->dirroot . '/mod/workshop/locallib.php'); require_once($CFG->dirroot . '/mod/workshop/form/numerrors/lib.php'); /** * Unit tests for Number of errors grading lib.php */ final class lib_test extends \advanced_testcase { /** workshop instance emulation */ protected $workshop; /** instance of the strategy logic class being tested */ protected $strategy; /** * Setup testing environment */ protected function setUp(): void { parent::setUp(); $this->resetAfterTest(); $this->setAdminUser(); $course = $this->getDataGenerator()->create_course(); $workshop = $this->getDataGenerator()->create_module('workshop', array('strategy' => 'numerrors', 'course' => $course)); $cm = get_fast_modinfo($course)->instances['workshop'][$workshop->id]; $this->workshop = new workshop($workshop, $cm, $course); $this->strategy = new testable_workshop_numerrors_strategy($this->workshop); } protected function tearDown(): void { $this->workshop = null; $this->strategy = null; parent::tearDown(); } public function test_calculate_peer_grade_null_grade(): void { // fixture set-up $this->strategy->dimensions = array(); $this->strategy->mappings = array(); $grades = array(); // exercise SUT $suggested = $this->strategy->calculate_peer_grade($grades); // validate $this->assertNull($suggested); } public function test_calculate_peer_grade_no_error(): void { // fixture set-up $this->strategy->dimensions = array(); $this->strategy->dimensions[108] = (object)array('weight' => '1'); $this->strategy->dimensions[109] = (object)array('weight' => '1'); $this->strategy->dimensions[111] = (object)array('weight' => '1'); $this->strategy->mappings = array(); $grades = array(); $grades[] = (object)array('dimensionid' => 108, 'grade' => '1.00000'); $grades[] = (object)array('dimensionid' => 111, 'grade' => '1.00000'); $grades[] = (object)array('dimensionid' => 109, 'grade' => '1.00000'); // exercise SUT $suggested = $this->strategy->calculate_peer_grade($grades); // validate $this->assertEquals($suggested, 100.00000); } public function test_calculate_peer_grade_one_error(): void { // fixture set-up $this->strategy->dimensions = array(); $this->strategy->dimensions[108] = (object)array('weight' => '1'); $this->strategy->dimensions[109] = (object)array('weight' => '1'); $this->strategy->dimensions[111] = (object)array('weight' => '1'); $this->strategy->mappings = array( 1 => (object)array('grade' => '80.00000'), 2 => (object)array('grade' => '60.00000'), ); $grades = array(); $grades[] = (object)array('dimensionid' => 108, 'grade' => '1.00000'); $grades[] = (object)array('dimensionid' => 111, 'grade' => '0.00000'); $grades[] = (object)array('dimensionid' => 109, 'grade' => '1.00000'); // exercise SUT $suggested = $this->strategy->calculate_peer_grade($grades); // validate $this->assertEquals($suggested, 80.00000); } public function test_calculate_peer_grade_three_errors_same_weight_a(): void { // fixture set-up $this->strategy->dimensions = array(); $this->strategy->dimensions[108] = (object)array('weight' => '1.00000'); $this->strategy->dimensions[109] = (object)array('weight' => '1.00000'); $this->strategy->dimensions[111] = (object)array('weight' => '1.00000'); $this->strategy->mappings = array( 1 => (object)array('grade' => '80.00000'), 2 => (object)array('grade' => '60.00000'), 3 => (object)array('grade' => '10.00000'), ); $grades = array(); $grades[] = (object)array('dimensionid' => 108, 'grade' => '0.00000'); $grades[] = (object)array('dimensionid' => 111, 'grade' => '0.00000'); $grades[] = (object)array('dimensionid' => 109, 'grade' => '0.00000'); // exercise SUT $suggested = $this->strategy->calculate_peer_grade($grades); // validate $this->assertEquals($suggested, 10.00000); } public function test_calculate_peer_grade_three_errors_same_weight_b(): void { // fixture set-up $this->strategy->dimensions = array(); $this->strategy->dimensions[108] = (object)array('weight' => '1.00000'); $this->strategy->dimensions[109] = (object)array('weight' => '1.00000'); $this->strategy->dimensions[111] = (object)array('weight' => '1.00000'); $this->strategy->mappings = array( 1 => (object)array('grade' => '80.00000'), 2 => (object)array('grade' => '60.00000'), 3 => (object)array('grade' => '0.00000'), ); $grades = array(); $grades[] = (object)array('dimensionid' => 108, 'grade' => '0.00000'); $grades[] = (object)array('dimensionid' => 111, 'grade' => '0.00000'); $grades[] = (object)array('dimensionid' => 109, 'grade' => '0.00000'); // exercise SUT $suggested = $this->strategy->calculate_peer_grade($grades); // validate $this->assertEquals($suggested, 0.00000); } public function test_calculate_peer_grade_one_error_weighted(): void { // fixture set-up $this->strategy->dimensions = array(); $this->strategy->dimensions[108] = (object)array('weight' => '1'); $this->strategy->dimensions[109] = (object)array('weight' => '2'); $this->strategy->dimensions[111] = (object)array('weight' => '0'); $this->strategy->mappings = array( 1 => (object)array('grade' => '66.00000'), 2 => (object)array('grade' => '33.00000'), 3 => (object)array('grade' => '0.00000'), ); $grades = array(); $grades[] = (object)array('dimensionid' => 108, 'grade' => '1.00000'); $grades[] = (object)array('dimensionid' => 111, 'grade' => '1.00000'); $grades[] = (object)array('dimensionid' => 109, 'grade' => '0.00000'); // exercise SUT $suggested = $this->strategy->calculate_peer_grade($grades); // validate $this->assertEquals($suggested, 33.00000); } public function test_calculate_peer_grade_zero_weight(): void { // fixture set-up $this->strategy->dimensions = array(); $this->strategy->dimensions[108] = (object)array('weight' => '1'); $this->strategy->dimensions[109] = (object)array('weight' => '2'); $this->strategy->dimensions[111] = (object)array('weight' => '0'); $this->strategy->mappings = array( 1 => (object)array('grade' => '66.00000'), 2 => (object)array('grade' => '33.00000'), 3 => (object)array('grade' => '0.00000'), ); $grades = array(); $grades[] = (object)array('dimensionid' => 108, 'grade' => '1.00000'); $grades[] = (object)array('dimensionid' => 111, 'grade' => '0.00000'); $grades[] = (object)array('dimensionid' => 109, 'grade' => '1.00000'); // exercise SUT $suggested = $this->strategy->calculate_peer_grade($grades); // validate $this->assertEquals($suggested, 100.00000); } public function test_calculate_peer_grade_sum_weight(): void { // fixture set-up $this->strategy->dimensions = array(); $this->strategy->dimensions[108] = (object)array('weight' => '1'); $this->strategy->dimensions[109] = (object)array('weight' => '2'); $this->strategy->dimensions[111] = (object)array('weight' => '3'); $this->strategy->mappings = array( 1 => (object)array('grade' => '90.00000'), 2 => (object)array('grade' => '80.00000'), 3 => (object)array('grade' => '70.00000'), 4 => (object)array('grade' => '60.00000'), 5 => (object)array('grade' => '30.00000'), 6 => (object)array('grade' => '5.00000'), 7 => (object)array('grade' => '0.00000'), ); $grades = array(); $grades[] = (object)array('dimensionid' => 108, 'grade' => '0.00000'); $grades[] = (object)array('dimensionid' => 111, 'grade' => '0.00000'); $grades[] = (object)array('dimensionid' => 109, 'grade' => '0.00000'); // exercise SUT $suggested = $this->strategy->calculate_peer_grade($grades); // validate $this->assertEquals($suggested, 5.00000); } } /** * Test subclass that makes all the protected methods we want to test public */ class testable_workshop_numerrors_strategy extends workshop_numerrors_strategy { /** allows to set dimensions manually */ public $dimensions = array(); /** allow to set mappings manually */ public $mappings = array(); /** * This is where the calculation of suggested grade for submission is done */ public function calculate_peer_grade(array $grades) { return parent::calculate_peer_grade($grades); } } settings.php000064400000003222152267045410007122 0ustar00. /** * The configuration variables for "Number of errors" grading strategy * * The values defined here are often used as defaults for all module instances. * * @package workshopform_numerrors * @copyright 2009 David Mudrak * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $settings->add(new admin_setting_configtext('workshopform_numerrors/grade0', get_string('grade0', 'workshopform_numerrors'), get_string('configgrade0', 'workshopform_numerrors'), get_string('grade0default', 'workshopform_numerrors'), $paramtype=PARAM_TEXT, $size=15)); $settings->add(new admin_setting_configtext('workshopform_numerrors/grade1', get_string('grade1', 'workshopform_numerrors'), get_string('configgrade1', 'workshopform_numerrors'), get_string('grade1default', 'workshopform_numerrors'), $paramtype=PARAM_TEXT, $size=15));