ÿØÿà 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-Ї Ç PK\q*xxdb/install.phpnu[. /** * This file replaces the legacy STATEMENTS section in db/install.xml, * lib.php/modulename_install() post installation hook and partially defaults.php * * @package workshopform_accumulative * @copyright 2009 David Mudrak * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Post installation procedure */ function xmldb_workshopform_accumulative_install() { global $CFG, $DB; } PK\_4db/upgrade.phpnu[. /** * Keeps track of upgrades to the workshop accumulative grading strategy * * @package workshopform_accumulative * @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_accumulative_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; } PK\6 Idb/install.xmlnu[
PK\[\  %lang/en/workshopform_accumulative.phpnu[. /** * Strings for component 'workshopform_accumulative', language 'en', branch 'MOODLE_20_STABLE' * * @package workshopform_accumulative * @copyright 2009 David Mudrak * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['absent'] = 'Absent'; $string['addmoredimensions'] = 'Blanks for {$a} more aspects'; $string['correct'] = 'Correct'; $string['dimensioncommentfor'] = 'Comment for {$a}'; $string['dimensiondescription'] = 'Description'; $string['dimensiongradefor'] = 'Grade for {$a}'; $string['dimensionmaxgrade'] = 'Best possible grade / Scale to use'; $string['dimensionnumber'] = 'Aspect {$a}'; $string['dimensionweight'] = 'Weight'; $string['excellent'] = 'Excellent'; $string['good'] = 'Good'; $string['incorrect'] = 'Incorrect'; $string['mustchoosegrade'] = 'You have to select a grade for this aspect'; $string['pluginname'] = 'Accumulative grading'; $string['poor'] = 'Poor'; $string['present'] = 'Present'; $string['privacy:metadata'] = 'The Accumulative grading 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['scalename0'] = 'Yes/No (2 point)'; $string['scalename1'] = 'Present/Absent (2 point)'; $string['scalename2'] = 'Correct/Incorrect (2 point)'; $string['scalename3'] = 'Good/Poor (3 point)'; $string['scalename4'] = 'Excellent/Very poor (4 point)'; $string['scalename5'] = 'Excellent/Very poor (5 point)'; $string['scalename6'] = 'Excellent/Very poor (7 point)'; $string['verypoor'] = 'Very poor'; PK\ӡCbackup/moodle2/backup_workshopform_accumulative_subplugin.class.phpnu[. /** * @package workshopform_accumulative * @copyright 2010 David Mudrak * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); /** * Provides the information to backup accumulative grading strategy information */ class backup_workshopform_accumulative_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()); $subplugindimension = new backup_nested_element('workshopform_accumulative_dimension', array('id'), array( 'sort', 'description', 'descriptionformat', 'grade', 'weight')); // connect XML elements into the tree $subplugin->add_child($subpluginwrapper); $subpluginwrapper->add_child($subplugindimension); // set source to populate the data $subplugindimension->set_source_table('workshopform_accumulative', array('workshopid' => backup::VAR_ACTIVITYID)); // id annotations $subplugindimension->annotate_ids('scale', 'grade'); // file annotations $subplugindimension->annotate_files('workshopform_accumulative', '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_accumulative_referencegrade'); } /** * Returns the dimension grades to attach to 'exampleassessment' XML element */ protected function define_exampleassessment_subplugin_structure() { return $this->dimension_grades_structure('workshopform_accumulative_examplegrade'); } /** * Returns the dimension grades to attach to 'assessment' XML element */ protected function define_assessment_subplugin_structure() { return $this->dimension_grades_structure('workshopform_accumulative_grade'); } //////////////////////////////////////////////////////////////////////////// // internal private methods //////////////////////////////////////////////////////////////////////////// /** * Returns the structure of dimension grades * * @param string first 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 = 'accumulative' AND assessmentid = ?", array(backup::VAR_PARENTID)); return $subplugin; } } PK\x&Dbackup/moodle2/restore_workshopform_accumulative_subplugin.class.phpnu[. /** * @package workshopform_accumulative * @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com} * @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_accumulative subplugin. */ class restore_workshopform_accumulative_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('dimension'); $elepath = $this->get_pathfor('/workshopform_accumulative_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_accumulative_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_accumulative_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_accumulative_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_accumulative_dimension element */ public function process_workshopform_accumulative_dimension($data) { global $DB; $data = (object)$data; $oldid = $data->id; $data->workshopid = $this->get_new_parentid('workshop'); if ($data->grade < 0) { // scale found, get mapping $data->grade = -($this->get_mappingid('scale', abs($data->grade))); } $newitemid = $DB->insert_record('workshopform_accumulative', $data); $this->set_mapping($this->get_namefor('dimension'), $oldid, $newitemid, true); // Process files for this workshopform_accumulative->id only $this->add_related_files('workshopform_accumulative', 'description', $this->get_namefor('dimension'), null, $oldid); } /** * Processes the workshopform_accumulative_referencegrade element */ public function process_workshopform_accumulative_referencegrade($data) { $this->process_dimension_grades_structure('workshop_referenceassessment', $data); } /** * Processes the workshopform_accumulative_examplegrade element */ public function process_workshopform_accumulative_examplegrade($data) { $this->process_dimension_grades_structure('workshop_exampleassessment', $data); } /** * Processes the workshopform_accumulative_grade element */ public function process_workshopform_accumulative_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 = 'accumulative'; $data->dimensionid = $this->get_mappingid($this->get_namefor('dimension'), $data->dimensionid); $DB->insert_record('workshop_grades', $data); } } PK\|2S%S%backup/moodle1/lib.phpnu[. /** * Provides support for the conversion of moodle1 backup to the moodle2 format * * @package workshopform_accumulative * @copyright 2011 David Mudrak * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); /** * Conversion handler for the accumulative grading strategy data */ class moodle1_workshopform_accumulative_handler extends moodle1_workshopform_handler { /** @var used by {@link self::migrate_legacy_scales()} */ private $newscaleids = array(); /** * Converts into */ public function process_legacy_element(array $data, array $raw) { // prepare a fake record and re-use the upgrade logic $fakerecord = (object)$data; $newscaleid = $this->get_new_scaleid($data['scale']); $converted = (array)workshopform_accumulative_upgrade_element($fakerecord, $newscaleid, 12345678); unset($converted['workshopid']); $converted['id'] = $data['id']; $this->write_xml('workshopform_accumulative_dimension', $converted, array('/workshopform_accumulative_dimension/id')); return $converted; } /** * If needed, creates new standard (global) scale to replace the legacy workshop one and returns the mapping * * If the given $oldscaleid represents a scale, returns array $oldscaleid => $newscaleid that * can be used as a parameter for {@link workshopform_accumulative_upgrade_element()}. Otherwise * this method returns empty array. * * In workshop 1.x, scale field in workshop_elements had the following meaning: * 0 | 2 point Yes/No scale * 1 | 2 point Present/Absent scale * 2 | 2 point Correct/Incorrect scale * 3 | 3 point Good/Poor scale * 4 | 4 point Excellent/Very Poor scale * 5 | 5 point Excellent/Very Poor scale * 6 | 7 point Excellent/Very Poor scale * 7 | Score out of 10 * 8 | Score out of 20 * 9 | Score out of 100 * * @see workshopform_accumulative_upgrade_scales() * @param int $oldscaleid the value of the 'scale' field in the moodle.xml backup file * @return array (int)oldscaleid => (int)newscaleid */ protected function get_new_scaleid($oldscaleid) { if ($oldscaleid >= 0 and $oldscaleid <= 6) { // we need a new scale id if (!isset($this->newscaleids[$oldscaleid])) { // this is the first time the legacy scale is used in moodle.xml // let us migrate it $scale = $this->get_new_scale_definition($oldscaleid); // other scales are already stashed - let us append a new artificial record $currentscaleids = $this->converter->get_stash_itemids('scales'); if (empty($currentscaleids)) { $scale['id'] = 1; } else { $scale['id'] = max($currentscaleids) + 1; } $this->converter->set_stash('scales', $scale, $scale['id']); $this->newscaleids[$oldscaleid] = $scale['id']; // inform the workshop instance that it should annotate the new scale $inforefman = $this->parenthandler->get_inforef_manager(); $inforefman->add_ref('scale', $scale['id']); } return array($oldscaleid => $this->newscaleids[$oldscaleid]); } else { // not a scale return array(); } } /** * Returns a definition of a legacy workshop scale * * @see workshopform_accumulative_upgrade_scales * @param object $oldscaleid * @return array */ private function get_new_scale_definition($oldscaleid) { $data = array( 'userid' => 0, // restore will remap to the current user 'courseid' => 0, // global scale 'description' => '', 'descriptionformat' => FORMAT_HTML, ); switch ($oldscaleid) { case 0: $data['name'] = get_string('scalename0', 'workshopform_accumulative'); $data['scale'] = implode(',', array(get_string('no'), get_string('yes'))); break; case 1: $data['name'] = get_string('scalename1', 'workshopform_accumulative'); $data['scale'] = implode(',', array(get_string('absent', 'workshopform_accumulative'), get_string('present', 'workshopform_accumulative'))); break; case 2: $data['name'] = get_string('scalename2', 'workshopform_accumulative'); $data['scale'] = implode(',', array(get_string('incorrect', 'workshopform_accumulative'), get_string('correct', 'workshopform_accumulative'))); break; case 3: $data['name'] = get_string('scalename3', 'workshopform_accumulative'); $data['scale'] = implode(',', array('* ' . get_string('poor', 'workshopform_accumulative'), '**', '*** ' . get_string('good', 'workshopform_accumulative'))); break; case 4: $data['name'] = get_string('scalename4', 'workshopform_accumulative'); $data['scale'] = implode(',', array('* ' . get_string('verypoor', 'workshopform_accumulative'), '**', '***', '**** ' . get_string('excellent', 'workshopform_accumulative'))); break; case 5: $data['name'] = get_string('scalename5', 'workshopform_accumulative'); $data['scale'] = implode(',', array('* ' . get_string('verypoor', 'workshopform_accumulative'), '**', '***', '****', '***** ' . get_string('excellent', 'workshopform_accumulative'))); break; case 6: $data['name'] = get_string('scalename6', 'workshopform_accumulative'); $data['scale'] = implode(',', array('* ' . get_string('verypoor', 'workshopform_accumulative'), '**', '***', '****', '*****', '******', '******* ' . get_string('excellent', 'workshopform_accumulative'))); break; } return $data; } } /** * Transforms a given record from workshop_elements_old into an object to be saved into workshopform_accumulative * * @param stdClass $old legacy record from workshop_elements_old * @param array $newscaleids mapping from old scale types into new standard ones * @param int $newworkshopid id of the new workshop instance that replaced the previous one * @return stdclass to be saved in workshopform_accumulative */ function workshopform_accumulative_upgrade_element(stdclass $old, array $newscaleids, $newworkshopid) { $new = new stdclass(); $new->workshopid = $newworkshopid; $new->sort = $old->elementno; $new->description = $old->description; $new->descriptionformat = FORMAT_HTML; // calculate new grade/scale of the element if ($old->scale >= 0 and $old->scale <= 6 and isset($newscaleids[$old->scale])) { $new->grade = -$newscaleids[$old->scale]; } elseif ($old->scale == 7) { $new->grade = 10; } elseif ($old->scale == 8) { $new->grade = 20; } elseif ($old->scale == 9) { $new->grade = 100; } else { $new->grade = 0; // something is wrong } // 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; } PK\Q? ? edit_form.phpnu[. /** * This file defines an mform to edit accumulative grading strategy forms. * * @package workshopform_accumulative * @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 accumulative grading strategy forms. * * @uses moodleform */ class workshop_edit_accumulative_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) { $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_accumulative', $i+1)); $mform->addElement('hidden', 'dimensionid__idx_'.$i); $mform->setType('dimensionid__idx_'.$i, PARAM_INT); $mform->addElement('editor', 'description__idx_'.$i.'_editor', get_string('dimensiondescription', 'workshopform_accumulative'), '', $descriptionopts); // todo replace modgrade with an advanced element (usability issue discussed with Olli) $mform->addElement('modgrade', 'grade__idx_'.$i, get_string('dimensionmaxgrade','workshopform_accumulative'), null, true); $mform->setDefault('grade__idx_'.$i, 10); $mform->addElement('select', 'weight__idx_'.$i, get_string('dimensionweight', 'workshopform_accumulative'), workshop::available_dimension_weights_list()); $mform->setDefault('weight__idx_'.$i, 1); } $mform->registerNoSubmitButton('noadddims'); $mform->addElement('submit', 'noadddims', get_string('addmoredimensions', 'workshopform_accumulative', workshop_accumulative_strategy::ADDDIMS)); $mform->closeHeaderBefore('noadddims'); $this->set_data($current); } } PK\ ː]]lib.phpnu[. /** * This file defines a class with accumulative grading strategy logic * * @package workshopform_accumulative * @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_accumulative_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_accumulative', 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_accumulative/$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); } /** * Accumulative grading strategy logic. */ class workshop_accumulative_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 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->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); $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_accumulative', 'description', $fields->{'dimensionid__idx_'.$i}); } $customdata = array(); $customdata['workshop'] = $this->workshop; $customdata['strategy'] = $this; $customdata['norepeats'] = $norepeats; $customdata['descriptionopts'] = $this->descriptionopts; $customdata['current'] = $fields; $attributes = array('class' => 'editstrategyform'); return new workshop_edit_accumulative_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->accumulative; // records to be saved into {workshopform_accumulative} $todelete = array(); // dimension ids to be deleted for ($i=0; $i < $norepeats; $i++) { $record = $records[$i]; if (0 == strlen(trim($record->description_editor['text']))) { if (!empty($record->id)) { // existing record with empty description - to be deleted $todelete[] = $record->id; } continue; } if (empty($record->id)) { // new field $record->id = $DB->insert_record('workshopform_accumulative', $record); } else { // exiting field $DB->update_record('workshopform_accumulative', $record); } // re-save with correct path to embeded media files $record = file_postupdate_standard_editor($record, 'description', $this->descriptionopts, $PAGE->context, 'workshopform_accumulative', 'description', $record->id); $DB->update_record('workshopform_accumulative', $record); } $this->delete_dimensions($todelete); } /** * 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 The current 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); $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_accumulative', '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; $current->{'peercomment__idx_'.$i} = $grades[$dimid]->peercomment; } } } // set up the required custom data common for all strategies $customdata['strategy'] = $this; $customdata['workshop'] = $this->workshop; $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 accumulative'); return new workshop_accumulative_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 (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 = 'accumulative'; $grade->dimensionid = $data->{'dimensionid__idx_' . $i}; if (isset($data->{'grade__idx_' . $i})) { $grade->grade = $data->{'grade__idx_' . $i}; } $grade->peercomment = $data->{'peercomment__idx_' . $i}; $grade->peercommentformat = FORMAT_MOODLE; 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; $sql = 'SELECT d.id, d.grade, d.weight, s.scale FROM {workshopform_accumulative} d LEFT JOIN {scale} s ON (d.grade < 0 AND -d.grade = s.id) WHERE d.workshopid = :workshopid'; $params = array('workshopid' => $this->workshop->id); $dimrecords = $DB->get_records_sql($sql, $params); $diminfo = array(); foreach ($dimrecords as $dimid => $dimrecord) { $diminfo[$dimid] = new stdclass(); $diminfo[$dimid]->id = $dimid; $diminfo[$dimid]->weight = $dimrecord->weight; if ($dimrecord->grade < 0) { // the dimension uses a scale $diminfo[$dimid]->min = 1; $diminfo[$dimid]->max = count(explode(',', $dimrecord->scale)); $diminfo[$dimid]->scale = $dimrecord->scale; } else { // the dimension uses points $diminfo[$dimid]->min = 0; $diminfo[$dimid]->max = grade_floatval($dimrecord->grade); } } return $diminfo; } /** * Is a given scale used by the instance of workshop? * * @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) { global $DB; $conditions['grade'] = -$scaleid; if (!is_null($workshopid)) { $conditions['workshopid'] = $workshopid; } return $DB->record_exists('workshopform_accumulative', $conditions); } /** * 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_accumulative', 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_accumulative} WHERE workshopid = :workshopid ORDER BY sort'; $params = array('workshopid' => $this->workshop->id); return $DB->get_records_sql($sql, $params); } /** * Maps the dimension data from DB to the form fields * * @param array $raw Array of raw dimension records as returned by {@link load_fields()} * @return array Array of fields data to be used by the mform set_data */ protected function prepare_form_fields(array $raw) { $formdata = new stdclass(); $key = 0; foreach ($raw as $dimension) { $formdata->{'dimensionid__idx_' . $key} = $dimension->id; $formdata->{'description__idx_' . $key} = $dimension->description; $formdata->{'description__idx_' . $key.'format'} = $dimension->descriptionformat; $formdata->{'grade__idx_' . $key} = $dimension->grade; $formdata->{'weight__idx_' . $key} = $dimension->weight; $key++; } 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 $masterids * @return void */ protected function delete_dimensions(array $ids) { global $DB, $PAGE; $fs = get_file_storage(); foreach ($ids as $id) { if (!empty($id)) { // to prevent accidental removal of all files in the area $fs->delete_area_files($PAGE->context->id, 'workshopform_accumulative', 'description', $id); } } $DB->delete_records_list('workshopform_accumulative', 'id', $ids); } /** * Prepares data returned by {@link workshop_edit_accumulative_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->accumulative = array(); // records to be stored in {workshopform_accumulative} for ($i = 0; $i < $raw->norepeats; $i++) { $cook->accumulative[$i] = new stdclass(); $cook->accumulative[$i]->id = $raw->{'dimensionid__idx_'.$i}; $cook->accumulative[$i]->workshopid = $this->workshop->id; $cook->accumulative[$i]->sort = $i + 1; $cook->accumulative[$i]->description_editor = $raw->{'description__idx_'.$i.'_editor'}; $cook->accumulative[$i]->grade = $raw->{'grade__idx_'.$i}; $cook->accumulative[$i]->weight = $raw->{'weight__idx_'.$i}; } return $cook; } /** * Returns the list of current grades filled by the reviewer indexed by dimensionid * * @param stdClass $assessment Assessment record * @return array [int dimensionid] => stdclass workshop_grades record */ 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' => 'accumulative'); $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 (from 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} * @uses $this->dimensions * @return float|null Raw grade (from 0.00000 to 100.00000) for submission as suggested by the peer */ protected function calculate_peer_grade(array $grades) { if (empty($grades)) { return null; } $sumgrades = 0; $sumweights = 0; foreach ($grades as $grade) { $dimension = $this->dimensions[$grade->dimensionid]; if ($dimension->weight < 0) { throw new coding_exception('Negative weights are not supported any more. Something is wrong with your data'); } if (grade_floats_equal($dimension->weight, 0) or grade_floats_equal($dimension->grade, 0)) { // does not influence the final grade continue; } if ($dimension->grade < 0) { // this is a scale $scaleid = -$dimension->grade; $sumgrades += $this->scale_to_grade($scaleid, $grade->grade) * $dimension->weight * 100; $sumweights += $dimension->weight; } else { // regular grade $sumgrades += ($grade->grade / $dimension->grade) * $dimension->weight * 100; $sumweights += $dimension->weight; } } if ($sumweights === 0) { return 0; } return grade_floatval($sumgrades / $sumweights); } /** * Convert scale grade to numerical grades * * In accumulative grading strategy, scales are considered as grades from 0 to M-1, where M is the number of scale items. * * @throws coding_exception * @param string $scaleid Scale identifier * @param int $item Selected scale item number, numbered 1, 2, 3, ... M * @return float */ protected function scale_to_grade($scaleid, $item) { global $DB; /** @var cache of numbers of scale items */ static $numofscaleitems = array(); if (!isset($numofscaleitems[$scaleid])) { $scale = $DB->get_field('scale', 'scale', array('id' => $scaleid), MUST_EXIST); $items = explode(',', $scale); $numofscaleitems[$scaleid] = count($items); unset($scale); unset($items); } if ($numofscaleitems[$scaleid] <= 1) { throw new coding_exception('Invalid scale definition, no scale items found'); } if ($item <= 0 or $numofscaleitems[$scaleid] < $item) { throw new coding_exception('Invalid scale item number'); } return ($item - 1) / ($numofscaleitems[$scaleid] - 1); } } PK\ version.phpnu[. /** * Defines the version of workshop accumulative grading strategy subplugin * * This code fragment is called by moodle_needs_upgrading() and * /admin/index.php * * @package workshopform_accumulative * @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_accumulative'; PK\tassessment_form.phpnu[. /** * This file defines an mform to assess a submission by accumulative grading strategy * * @package workshopform_accumulative * @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 accumulative grading strategy * * @uses moodleform */ class workshop_accumulative_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); // minimal grade value to select - used by the 'compare' rule below // (just an implementation detail to make the rule work, this element is // not processed by the server) $mform->addElement('hidden', 'minusone', -1); $mform->setType('minusone', PARAM_INT); for ($i = 0; $i < $nodims; $i++) { // dimension header $dimtitle = get_string('dimensionnumber', 'workshopform_accumulative', $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); // grade for this aspect $label = get_string('dimensiongradefor', 'workshopform_accumulative', $dimtitle); if ($fields->{'grade__idx_' . $i}) { $options = make_grades_menu($fields->{'grade__idx_' . $i}); $options = array('-1' => get_string('choosedots')) + $options; $mform->addElement('select', 'grade__idx_' . $i, $label, $options); $mform->addRule(array('grade__idx_' . $i, 'minusone'), get_string('mustchoosegrade', 'workshopform_accumulative'), 'compare', 'gt'); } // comment $label = get_string('dimensioncommentfor', 'workshopform_accumulative', $dimtitle); //$mform->addElement('editor', 'peercomment__idx_' . $i, $label, null, array('maxfiles' => 0)); $mform->addElement('textarea', 'peercomment__idx_' . $i, $label, array('cols' => 60, 'rows' => 5)); } $this->set_data($current); } } PK\IdUclasses/privacy/provider.phpnu[. /** * Provides the class {@link workshopform_accumulative\privacy\provider} * * @package workshopform_accumulative * @category privacy * @copyright 2018 David Mudrák * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace workshopform_accumulative\privacy; use core_privacy\local\request\writer; defined('MOODLE_INTERNAL') || die(); /** * Privacy API implementation for the Accumulative grading 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.description, dim.descriptionformat, dim.grade AS dimgrade, 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_accumulative} 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' => 'accumulative', 'contextlevel' => CONTEXT_MODULE, 'contextid' => $context->id, 'assessmentid' => $assessmentid, ]; $writer = \core_privacy\local\request\writer::with_context($context); $data = []; $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_accumulative', 'description', $record->id, $record->description); $dimensionids[] = $record->id; unset($record->id); $data[] = $record; } if ($hasdata) { $writer->export_data($subcontext, (object) ['aspects' => $data]); foreach ($dimensionids as $dimensionid) { $writer->export_area_files($subcontext, 'workshopform_accumulative', 'description', $dimensionid); } } } } PK\CZtests/privacy/provider_test.phpnu[. /** * Provides the {@see workshopform_accumulative\provider_test} class. * * @package workshopform_accumulative * @category test * @copyright 2018 David Mudrák * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace workshopform_accumulative\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_accumulative\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_accumulative', [ 'workshopid' => $this->workshop11->id, 'sort' => 1, 'description' => 'Aspect 1 description', 'descriptionformat' => FORMAT_MARKDOWN, 'grade' => 6, 'weight' => 1, ]); $this->dim2 = $DB->insert_record('workshopform_accumulative', [ 'workshopid' => $this->workshop11->id, 'sort' => 2, 'description' => 'Aspect 2 description', 'descriptionformat' => FORMAT_MARKDOWN, 'grade' => 4, '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' => 'accumulative', 'dimensionid' => $this->dim1, 'grade' => 3, 'peercomment' => 'Not awesome', 'peercommentformat' => FORMAT_PLAIN, ]); $DB->insert_record('workshop_grades', [ 'assessmentid' => $this->assessment1112, 'strategy' => 'accumulative', 'dimensionid' => $this->dim2, 'grade' => 4, 'peercomment' => 'All good', '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_accumulative'), ]); $this->assertEquals('Aspect 1 description', $form->aspects[0]->description); $this->assertEquals(4, $form->aspects[1]->grade); } } PK\bð""tests/lib_test.phpnu[. /** * Unit tests for Accumulative grading strategy logic * * @package workshopform_accumulative * @category test * @copyright 2009 David Mudrak * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace workshopform_accumulative; use workshop; use workshop_accumulative_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/accumulative/lib.php'); /** * Unit tests for Accumulative grading strategy lib.php */ final class lib_test extends \advanced_testcase { /** workshop instance emulation */ protected $workshop; /** @var testable_workshop_accumulative_strategy 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' => 'accumulative', 'course' => $course)); $cm = get_fast_modinfo($course)->instances['workshop'][$workshop->id]; $this->workshop = new workshop($workshop, $cm, $course); $this->strategy = new testable_workshop_accumulative_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(); $grades = array(); // exercise SUT $suggested = $this->strategy->calculate_peer_grade($grades); // validate $this->assertNull($suggested); } public function test_calculate_peer_grade_one_numerical(): void { // fixture set-up $this->strategy->dimensions[1003] = (object)array('grade' => '20', 'weight' => '1'); $grades[] = (object)array('dimensionid' => 1003, 'grade' => '5.00000'); // exercise SUT $suggested = $this->strategy->calculate_peer_grade($grades); // validate $this->assertEquals(grade_floatval(5/20 * 100), $suggested); } public function test_calculate_peer_grade_negative_weight(): void { // fixture set-up $this->strategy->dimensions[1003] = (object)array('grade' => '20', 'weight' => '-1'); $grades[] = (object)array('dimensionid' => 1003, 'grade' => '20'); // exercise SUT $this->expectException(\coding_exception::class); $suggested = $this->strategy->calculate_peer_grade($grades); } public function test_calculate_peer_grade_one_numerical_weighted(): void { // fixture set-up $this->strategy->dimensions[1003] = (object)array('grade' => '20', 'weight' => '3'); $grades[] = (object)array('dimensionid' => '1003', 'grade' => '5'); // exercise SUT $suggested = $this->strategy->calculate_peer_grade($grades); // validate $this->assertEquals(grade_floatval(5/20 * 100), $suggested); } public function test_calculate_peer_grade_three_numericals_same_weight(): void { // fixture set-up $this->strategy->dimensions[1003] = (object)array('grade' => '20', 'weight' => '2'); $this->strategy->dimensions[1004] = (object)array('grade' => '100', 'weight' => '2'); $this->strategy->dimensions[1005] = (object)array('grade' => '10', 'weight' => '2'); $grades[] = (object)array('dimensionid' => 1003, 'grade' => '11.00000'); $grades[] = (object)array('dimensionid' => 1004, 'grade' => '87.00000'); $grades[] = (object)array('dimensionid' => 1005, 'grade' => '10.00000'); // exercise SUT $suggested = $this->strategy->calculate_peer_grade($grades); // validate $this->assertEquals(grade_floatval((11/20 + 87/100 + 10/10)/3 * 100), $suggested); } public function test_calculate_peer_grade_three_numericals_different_weights(): void { // fixture set-up $this->strategy->dimensions[1003] = (object)array('grade' => '15', 'weight' => 3); $this->strategy->dimensions[1004] = (object)array('grade' => '80', 'weight' => 1); $this->strategy->dimensions[1005] = (object)array('grade' => '5', 'weight' => 2); $grades[] = (object)array('dimensionid' => 1003, 'grade' => '7.00000'); $grades[] = (object)array('dimensionid' => 1004, 'grade' => '66.00000'); $grades[] = (object)array('dimensionid' => 1005, 'grade' => '4.00000'); // exercise SUT $suggested = $this->strategy->calculate_peer_grade($grades); // validate $this->assertEquals(grade_floatval((7/15*3 + 66/80*1 + 4/5*2)/6 * 100), $suggested); } public function test_calculate_peer_grade_one_scale_max(): void { $this->resetAfterTest(true); // fixture set-up $scale11 = $this->getDataGenerator()->create_scale(array('scale'=>'E,D,C,B,A', 'id'=>11)); $this->strategy->dimensions[1008] = (object)array('grade' => (-$scale11->id), 'weight' => 1); $grades[] = (object)array('dimensionid' => 1008, 'grade' => '5.00000'); // exercise SUT $suggested = $this->strategy->calculate_peer_grade($grades); // validate $this->assertEquals(100.00000, $suggested); } public function test_calculate_peer_grade_one_scale_min_with_scale_caching(): void { $this->resetAfterTest(true); // fixture set-up $scale11 = $this->getDataGenerator()->create_scale(array('scale'=>'E,D,C,B,A', 'id'=>11)); $this->strategy->dimensions[1008] = (object)array('grade' => (-$scale11->id), 'weight' => 1); $grades[] = (object)array('dimensionid' => 1008, 'grade' => '1.00000'); // exercise SUT $suggested = $this->strategy->calculate_peer_grade($grades); // validate $this->assertEquals(0.00000, $suggested); } public function test_calculate_peer_grade_two_scales_weighted(): void { $this->resetAfterTest(true); // fixture set-up $scale13 = $this->getDataGenerator()->create_scale(array('scale'=>'Poor,Good,Excellent', 'id'=>13)); $scale17 = $this->getDataGenerator()->create_scale(array('scale'=>'-,*,**,***,****,*****,******', 'id'=>17)); $this->strategy->dimensions[1012] = (object)array('grade' => (-$scale13->id), 'weight' => 2); $this->strategy->dimensions[1019] = (object)array('grade' => (-$scale17->id), 'weight' => 3); $grades[] = (object)array('dimensionid' => 1012, 'grade' => '2.00000'); // "Good" $grades[] = (object)array('dimensionid' => 1019, 'grade' => '5.00000'); // "****" // exercise SUT $suggested = $this->strategy->calculate_peer_grade($grades); // validate $this->assertEquals(grade_floatval((1/2*2 + 4/6*3)/5 * 100), $suggested); } public function test_calculate_peer_grade_scale_exception(): void { $this->resetAfterTest(true); // fixture set-up $scale13 = $this->getDataGenerator()->create_scale(array('scale'=>'Poor,Good,Excellent', 'id'=>13)); $this->strategy->dimensions[1012] = (object)array('grade' => (-$scale13->id), 'weight' => 1); $grades[] = (object)array('dimensionid' => 1012, 'grade' => '4.00000'); // exceeds the number of scale items // Exercise SUT. $this->expectException(\coding_exception::class); $suggested = $this->strategy->calculate_peer_grade($grades); } } /** * Test subclass that makes all the protected methods we want to test public */ class testable_workshop_accumulative_strategy extends workshop_accumulative_strategy { /** allows to set dimensions manually */ public $dimensions = 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); } } PK\q*xxdb/install.phpnu[PK\_4db/upgrade.phpnu[PK\6 I db/install.xmlnu[PK\[\  %lang/en/workshopform_accumulative.phpnu[PK\ӡCJbackup/moodle2/backup_workshopform_accumulative_subplugin.class.phpnu[PK\x&DZ-backup/moodle2/restore_workshopform_accumulative_subplugin.class.phpnu[PK\|2S%S%Ebackup/moodle1/lib.phpnu[PK\Q? ? ykedit_form.phpnu[PK\ ː]]xlib.phpnu[PK\ ;version.phpnu[PK\t<assessment_form.phpnu[PK\IdUPclasses/privacy/provider.phpnu[PK\CZStests/privacy/provider_test.phpnu[PK\bð""itests/lib_test.phpnu[PK3