ÿØÿà 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-Ї Ç styles.css000064400000005407152267045070006617 0ustar00/* Rubric - grid layout */ .path-mod-workshop .mform.frozen #id_rubric-grid-wrapper, .path-mod-workshop .assessmentform.rubric #id_rubric-grid-wrapper { border: none; } .path-mod-workshop .mform.frozen #id_rubric-grid-wrapper legend, .path-mod-workshop .assessmentform.rubric #id_rubric-grid-wrapper legend { display: none; } .path-mod-workshop .mform.frozen #id_rubric-grid-wrapper th, .path-mod-workshop .mform.frozen #id_rubric-grid-wrapper td, .path-mod-workshop .assessmentform.rubric.grid #id_rubric-grid-wrapper th, .path-mod-workshop .assessmentform.rubric.grid #id_rubric-grid-wrapper td { border: 1px solid #ddd; padding: 5px; vertical-align: top; } .path-mod-workshop .mform.frozen #id_rubric-grid-wrapper, .path-mod-workshop .assessmentform.rubric.grid #id_rubric-grid-wrapper .criterion { text-align: center; } .path-mod-workshop .assessmentform.rubric.grid #id_rubric-grid-wrapper .fitem { text-align: center; } .path-mod-workshop .mform.frozen #id_rubric-grid-wrapper .fitem .fitemtitle, .path-mod-workshop .assessmentform.rubric.grid #id_rubric-grid-wrapper .fitem .fitemtitle { display: none; } .path-mod-workshop .mform.frozen #id_rubric-grid-wrapper, .path-mod-workshop #id_rubric-grid-wrapper { margin-left: auto; margin-right: auto; width: 100%; } .path-mod-workshop .mform.frozen #id_rubric-grid-wrapper .checkbox, .path-mod-workshop .assessmentform.rubric.grid #id_rubric-grid-wrapper .checkbox { max-width: 100%; flex: 0 0 100%; text-align: left; } .path-mod-workshop .mform.frozen #id_rubric-grid-wrapper .fitem .felement, .path-mod-workshop .assessmentform.rubric.grid #id_rubric-grid-wrapper .fitem .felement { width: 100%; margin-left: auto; margin-right: auto; } .path-mod-workshop .mform.frozen #id_rubric-grid-wrapper .fitem .felement { border: none; } .path-mod-workshop .assessmentform.rubric.grid #id_rubric-grid-wrapper .fitem .felement span { display: block; text-align: center; } .path-mod-workshop .assessmentform.rubric.grid #id_rubric-grid-wrapper .fitem .felement span label { display: block; text-align: center; } /* Rubric - list layout */ .path-mod-workshop .mform.frozen .fitem.description.rubric + .fitem .fitemtitle, .path-mod-workshop .assessmentform.rubric.list #id_rubric-grid-wrapper .fitem .fitemtitle { display: none; } .path-mod-workshop .mform.frozen .fitem.description.rubric + .fitem .fitemtitle + .felement, .path-mod-workshop .assessmentform.rubric.list .fitem .felement { width: auto; border: none; } .path-mod-workshop .assessmentform.rubric.list .fitem .felement span input { display: block; float: left; } .path-mod-workshop .assessmentform.rubric.list .fitem .felement.fgroup span label { display: block; margin-left: 30px; } db/install.php000064400000002154152267045070007322 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_rubric * @copyright 2010 David Mudrak * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Post installation procedure */ function xmldb_workshopform_rubric_install() { global $CFG, $DB; } db/upgrade.php000064400000003260152267045070007302 0ustar00. /** * Keeps track of upgrades to the workshop rubric grading strategy * * @package workshopform_rubric * @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_rubric_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.xml000064400000005645152267045070007343 0ustar00
lang/en/workshopform_rubric.php000064400000003501152267045070012715 0ustar00. /** * Strings for component 'workshopform_rubric', language 'en', branch 'MOODLE_20_STABLE' * * @package workshopform_rubric * @copyright 2009 David Mudrak * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['addmoredimensions'] = 'Blanks for {$a} more criteria'; $string['configuration'] = 'Rubric configuration'; $string['criteria'] = 'Criteria'; $string['dimensiondescription'] = 'Description'; $string['dimensionnumber'] = 'Criterion {$a}'; $string['layout'] = 'Rubric layout'; $string['layoutgrid'] = 'Grid'; $string['layoutlist'] = 'List'; $string['levelgroup'] = 'Level grade and definition'; $string['levels'] = 'Levels'; $string['mustbeunique'] = 'Level grades must be unique within a criterion'; $string['mustdefinelevel'] = 'At least one level is required'; $string['mustchooseone'] = 'You have to select one of these items'; $string['pluginname'] = 'Rubric'; $string['privacy:metadata'] = 'The Rubric 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.'; backup/moodle2/restore_workshopform_rubric_subplugin.class.php000064400000015360152267045070021145 0ustar00. /** * @package workshopform_rubric * @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_rubric subplugin. */ class restore_workshopform_rubric_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('config'); $elepath = $this->get_pathfor('/workshopform_rubric_config'); $paths[] = new restore_path_element($elename, $elepath); $elename = $this->get_namefor('dimension'); $elepath = $this->get_pathfor('/workshopform_rubric_dimension'); $paths[] = new restore_path_element($elename, $elepath); $elename = $this->get_namefor('level'); $elepath = $this->get_pathfor('/workshopform_rubric_dimension/workshopform_rubric_level'); $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_rubric_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_rubric_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_rubric_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_rubric_map element */ public function process_workshopform_rubric_config($data) { global $DB; $data = (object)$data; $data->workshopid = $this->get_new_parentid('workshop'); $DB->insert_record('workshopform_rubric_config', $data); } /** * Processes the workshopform_rubric_dimension element */ public function process_workshopform_rubric_dimension($data) { global $DB; $data = (object)$data; $oldid = $data->id; $data->workshopid = $this->get_new_parentid('workshop'); $newitemid = $DB->insert_record('workshopform_rubric', $data); $this->set_mapping($this->get_namefor('dimension'), $oldid, $newitemid, true); // Process files for this workshopform_rubric->id only $this->add_related_files('workshopform_rubric', 'description', $this->get_namefor('dimension'), null, $oldid); } /** * Processes the workshopform_rubric_level element */ public function process_workshopform_rubric_level($data) { global $DB; $data = (object)$data; $data->dimensionid = $this->get_new_parentid($this->get_namefor('dimension')); $DB->insert_record('workshopform_rubric_levels', $data); } /** * Processes the workshopform_rubric_referencegrade element */ public function process_workshopform_rubric_referencegrade($data) { $this->process_dimension_grades_structure('workshop_referenceassessment', $data); } /** * Processes the workshopform_rubric_examplegrade element */ public function process_workshopform_rubric_examplegrade($data) { $this->process_dimension_grades_structure('workshop_exampleassessment', $data); } /** * Processes the workshopform_rubric_grade element */ public function process_workshopform_rubric_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 = 'rubric'; $data->dimensionid = $this->get_mappingid($this->get_namefor('dimension'), $data->dimensionid); $DB->insert_record('workshop_grades', $data); } } backup/moodle2/backup_workshopform_rubric_subplugin.class.php000064400000011025152267045070020721 0ustar00. /** * @package workshopform_rubric * @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 rubric grading strategy information */ class backup_workshopform_rubric_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()); $subpluginconfig = new backup_nested_element('workshopform_rubric_config', null, 'layout'); $subplugindimension = new backup_nested_element('workshopform_rubric_dimension', array('id'), array( 'sort', 'description', 'descriptionformat')); $subpluginlevel = new backup_nested_element('workshopform_rubric_level', array('id'), array( 'grade', 'definition', 'definitionformat')); // connect XML elements into the tree $subplugin->add_child($subpluginwrapper); $subpluginwrapper->add_child($subpluginconfig); $subpluginwrapper->add_child($subplugindimension); $subplugindimension->add_child($subpluginlevel); // set source to populate the data $subpluginconfig->set_source_table('workshopform_rubric_config', array('workshopid' => backup::VAR_ACTIVITYID)); $subplugindimension->set_source_table('workshopform_rubric', array('workshopid' => backup::VAR_ACTIVITYID)); $subpluginlevel->set_source_table('workshopform_rubric_levels', array('dimensionid' => backup::VAR_PARENTID)); // file annotations $subplugindimension->annotate_files('workshopform_rubric', '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_rubric_referencegrade'); } /** * Returns the dimension grades to attach to 'exampleassessment' XML element */ protected function define_exampleassessment_subplugin_structure() { return $this->dimension_grades_structure('workshopform_rubric_examplegrade'); } /** * Returns the dimension grades to attach to 'assessment' XML element */ protected function define_assessment_subplugin_structure() { return $this->dimension_grades_structure('workshopform_rubric_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')); // 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 FROM {workshop_grades} WHERE strategy = 'rubric' AND assessmentid = ?", array(backup::VAR_PARENTID)); return $subplugin; } } backup/moodle1/lib.php000064400000015177152267045070010653 0ustar00. /** * Provides support for the conversion of moodle1 backup to the moodle2 format * * @package workshopform_rubric * @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 rubric grading strategy data */ class moodle1_workshopform_rubric_handler extends moodle1_workshopform_handler { /** @var array legacy elements to process */ protected $elements = array(); /** @var array legacy rubrics records to process */ protected $rubrics = array(); /** * Prepare to gather legacy elements info for a new workshop instance */ public function on_elements_start() { $this->elements = array(); $this->rubrics = array(); } /** * Processes one * * @param array $data legacy element data * @param array $raw raw element data */ public function process_legacy_element(array $data, array $raw) { $this->elements[] = $data; $this->rubrics[$data['id']] = array(); } /** * Processes one */ public function process_legacy_rubric($data, $raw) { $this->rubrics[$data['elementid']][] = $data; } /** * Processes gathered elements and rubrics */ public function on_elements_end() { $numofrubrics = 0; foreach ($this->rubrics as $itemid => $levels) { $numofrubrics += count($levels); } if ($numofrubrics == 0) { $this->convert_legacy_criterion_elements(); } else { $this->convert_legacy_rubric_elements(); } } /** * Processes gathered elements coming from the legacy criterion strategy * * Legacy criterion strategy is converted to a rubric with single rubric item * and the layout set to 'list'. */ protected function convert_legacy_criterion_elements() { $this->write_xml('workshopform_rubric_config', array('layout' => 'list')); $firstelement = reset($this->elements); if ($firstelement === false) { // no elements defined in moodle.xml return; } // write the xml describing the artificial single rubric item $this->xmlwriter->begin_tag('workshopform_rubric_dimension', array('id' => $firstelement['id'])); $this->xmlwriter->full_tag('sort', 1); $this->xmlwriter->full_tag('description', trim(get_string('dimensionnumber', 'workshopform_rubric', ''))); $this->xmlwriter->full_tag('descriptionformat', FORMAT_HTML); foreach ($this->elements as $element) { $this->write_xml('workshopform_rubric_level', array( 'id' => $element['id'], 'grade' => $element['maxscore'], 'definition' => $element['description'], 'definitionformat' => FORMAT_HTML ), array('/workshopform_rubric_level/id')); } $this->xmlwriter->end_tag('workshopform_rubric_dimension'); } /** * Processes gathered elements coming from the legacy rubric strategy */ protected function convert_legacy_rubric_elements() { $this->write_xml('workshopform_rubric_config', array('layout' => 'grid')); foreach ($this->elements as $element) { $this->xmlwriter->begin_tag('workshopform_rubric_dimension', array('id' => $element['id'])); $this->xmlwriter->full_tag('sort', $element['elementno']); $this->xmlwriter->full_tag('description', $element['description']); $this->xmlwriter->full_tag('descriptionformat', FORMAT_HTML); foreach ($this->rubrics[$element['id']] as $rubric) { $fakerecord = new stdClass(); $fakerecord->rgrade = $rubric['rubricno']; $fakerecord->eweight = $element['weight']; $fakerecord->rdesc = $rubric['description']; $level = (array)workshopform_rubric_upgrade_rubric_level($fakerecord, $element['id']); unset($level['dimensionid']); $level['id'] = $this->converter->get_nextid(); $this->write_xml('workshopform_rubric_level', $level, array('/workshopform_rubric_level/id')); } $this->xmlwriter->end_tag('workshopform_rubric_dimension'); } } } /** * Transforms given record into an object to be saved into workshopform_rubric_levels * * This is used during Rubric 1.9 -> Rubric 2.0 conversion * * @param stdClass $old legacy record from joined workshop_elements_old + workshop_rubrics_old * @param int $newdimensionid id of the new workshopform_rubric dimension record to be linked to * @return stdclass to be saved in workshopform_rubric_levels */ function workshopform_rubric_upgrade_rubric_level(stdclass $old, $newdimensionid) { $new = new stdclass(); $new->dimensionid = $newdimensionid; $new->grade = $old->rgrade * workshopform_rubric_upgrade_weight($old->eweight); $new->definition = $old->rdesc; $new->definitionformat = FORMAT_HTML; return $new; } /** * Given old workshop element weight, returns the weight multiplier * * 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. * * @param $oldweight index in legacy $WORKSHOP_EWEIGHTS * @return int new weight */ function workshopform_rubric_upgrade_weight($oldweight) { switch ($oldweight) { case 8: $weight = 1; break; case 9: $weight = 2; break; case 10: $weight = 3; break; case 11: $weight = 4; break; case 12: $weight = 6; break; case 13: $weight = 8; break; case 14: $weight = 16; break; default: $weight = 0; } return $weight; } edit_form.php000064400000014636152267045070007247 0ustar00. /** * This file defines an mform to edit rubric grading strategy forms. * * @package workshopform_rubric * @copyright 2009 David Mudrak * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); require_once(__DIR__ . '/../edit_form.php'); // parent class definition /** * Class for editing rubric grading strategy forms. * * @uses moodleform */ class workshop_edit_rubric_strategy_form extends workshop_edit_strategy_form { const MINLEVELS = 4; const ADDLEVELS = 2; /** * 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)); $levelgrades = array(); for ($i = 100; $i >= 0; $i--) { $levelgrades[$i] = $i; } for ($i = 0; $i < $norepeats; $i++) { $mform->addElement('header', 'dimension'.$i, get_string('dimensionnumber', 'workshopform_rubric', $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_rubric'), '', $descriptionopts); if (isset($current->{'numoflevels__idx_' . $i})) { $numoflevels = max($current->{'numoflevels__idx_' . $i} + self::ADDLEVELS, self::MINLEVELS); } else { $numoflevels = self::MINLEVELS; } $prevlevel = -1; for ($j = 0; $j < $numoflevels; $j++) { $mform->addElement('hidden', 'levelid__idx_' . $i . '__idy_' . $j); $mform->setType('levelid__idx_' . $i . '__idy_' . $j, PARAM_INT); $levelgrp = array(); $levelgrp[] = $mform->createElement('select', 'grade__idx_'.$i.'__idy_'.$j,'', $levelgrades); $levelgrp[] = $mform->createElement('textarea', 'definition__idx_'.$i.'__idy_'.$j, '', array('cols' => 60, 'rows' => 3)); $mform->addGroup($levelgrp, 'level__idx_'.$i.'__idy_'.$j, get_string('levelgroup', 'workshopform_rubric'), array(' '), false); $mform->setDefault('grade__idx_'.$i.'__idy_'.$j, $prevlevel + 1); if (isset($current->{'grade__idx_'.$i.'__idy_'.$j})) { $prevlevel = $current->{'grade__idx_'.$i.'__idy_'.$j}; } else { $prevlevel++; } } } $mform->registerNoSubmitButton('adddims'); $mform->addElement('submit', 'adddims', get_string('addmoredimensions', 'workshopform_rubric', workshop_rubric_strategy::ADDDIMS)); $mform->closeHeaderBefore('adddims'); $mform->addElement('header', 'configheader', get_string('configuration', 'workshopform_rubric')); $layoutgrp = array(); $layoutgrp[] = $mform->createElement('radio', 'config_layout', '', get_string('layoutlist', 'workshopform_rubric'), 'list'); $layoutgrp[] = $mform->createElement('radio', 'config_layout', '', get_string('layoutgrid', 'workshopform_rubric'), 'grid'); $mform->addGroup($layoutgrp, 'layoutgrp', get_string('layout', 'workshopform_rubric'), array('
'), false); $mform->setDefault('config_layout', 'list'); $this->set_data($current); } /** * Provide validation rules for the rubric editor form. * * @param array $data * @param array $files * @return array */ protected function validation_inner($data, $files) { $errors = array(); // Iterate over all submitted dimensions (criteria). for ($i = 0; isset($data['dimensionid__idx_'.$i]); $i++) { $dimgrades = array(); if (0 == strlen(trim($data['description__idx_'.$i.'_editor']['text']))) { // The description text is empty and this criterion will be deleted. continue; } // Make sure the levels grades are unique within the criterion. $atleastonelevel = false; for ($j = 0; isset($data['levelid__idx_'.$i.'__idy_'.$j]); $j++) { if (0 == strlen(trim($data['definition__idx_'.$i.'__idy_'.$j]))) { // The level definition is empty and will not be saved. continue; } $atleastonelevel = true; $levelgrade = $data['grade__idx_'.$i.'__idy_'.$j]; if (isset($dimgrades[$levelgrade])) { // This grade has already been set for another level. $k = $dimgrades[$levelgrade]; $errors['level__idx_'.$i.'__idy_'.$j] = $errors['level__idx_'.$i.'__idy_'.$k] = get_string('mustbeunique', 'workshopform_rubric'); } else { $dimgrades[$levelgrade] = $j; } } if (!$atleastonelevel) { $errors['level__idx_'.$i.'__idy_0'] = get_string('mustdefinelevel', 'workshopform_rubric'); } } return $errors; } } lib.php000064400000063254152267045070006045 0ustar00. /** * This file defines a class with rubric grading strategy logic * * @package workshopform_rubric * @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_rubric_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_rubric', 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_rubric/$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); } /** * Rubric grading strategy logic. */ class workshop_rubric_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; /** @var array options for level definition fields */ protected $definitionopts; /** @var object rubric configuration */ protected $config; /** * Constructor * * @param workshop $workshop The workshop instance record * @return void */ public function __construct(workshop $workshop) { $this->workshop = $workshop; $this->dimensions = $this->load_fields(); $this->config = $this->load_config(); $this->descriptionopts = array('trusttext' => true, 'subdirs' => false, 'maxfiles' => -1); //one day the definitions may become proper wysiwyg fields - not used yet $this->definitionopts = 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 require_once(__DIR__ . '/edit_form.php'); $fields = $this->prepare_form_fields($this->dimensions); $fields->config_layout = $this->config->layout; $nodimensions = count($this->dimensions); $norepeatsdefault = max($nodimensions + self::ADDDIMS, self::MINDIMS); $norepeats = optional_param('norepeats', $norepeatsdefault, PARAM_INT); // number of dimensions $adddims = optional_param('adddims', '', PARAM_ALPHA); // shall we add more dimensions? if ($adddims) { $norepeats += self::ADDDIMS; } // Append editor context to editor options, giving preference to existing context. $this->descriptionopts = array_merge(array('context' => $this->workshop->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, $this->workshop->context, 'workshopform_rubric', '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_rubric_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; $norepeats = $data->norepeats; $layout = $data->config_layout; $data = $this->prepare_database_fields($data); $deletedims = array(); // dimension ids to be deleted $deletelevs = array(); // level ids to be deleted if ($DB->record_exists('workshopform_rubric_config', array('workshopid' => $this->workshop->id))) { $DB->set_field('workshopform_rubric_config', 'layout', $layout, array('workshopid' => $this->workshop->id)); } else { $record = new stdclass(); $record->workshopid = $this->workshop->id; $record->layout = $layout; $DB->insert_record('workshopform_rubric_config', $record, false); } foreach ($data as $record) { if (0 == strlen(trim($record->description_editor['text']))) { if (!empty($record->id)) { // existing record with empty description - to be deleted $deletedims[] = $record->id; foreach ($record->levels as $level) { if (!empty($level->id)) { $deletelevs[] = $level->id; } } } continue; } if (empty($record->id)) { // new field $record->id = $DB->insert_record('workshopform_rubric', $record); } else { // exiting field $DB->update_record('workshopform_rubric', $record); } // re-save with correct path to embeded media files $record = file_postupdate_standard_editor($record, 'description', $this->descriptionopts, $this->workshop->context, 'workshopform_rubric', 'description', $record->id); $DB->update_record('workshopform_rubric', $record); // create/update the criterion levels foreach ($record->levels as $level) { $level->dimensionid = $record->id; if (0 == strlen(trim($level->definition))) { if (!empty($level->id)) { $deletelevs[] = $level->id; } continue; } if (empty($level->id)) { // new field $level->id = $DB->insert_record('workshopform_rubric_levels', $level); } else { // exiting field $DB->update_record('workshopform_rubric_levels', $level); } } } $DB->delete_records_list('workshopform_rubric_levels', 'id', $deletelevs); $this->delete_dimensions($deletedims); } /** * 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/readonly * @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 $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', $this->workshop->context->id, 'workshopform_rubric', '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])) { $givengrade = $grades[$dimid]->grade; // find a level with this grade $levelid = null; foreach ($this->dimensions[$dimid]->levels as $level) { if (grade_floats_equal($level->grade, $givengrade)) { $levelid = $level->id; break; } } $current->{'gradeid__idx_'.$i} = $grades[$dimid]->id; $current->{'chosenlevelid__idx_'.$i} = $levelid; } } } // 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 rubric ' . $this->config->layout); $formclassname = 'workshop_rubric_' . $this->config->layout . '_assessment_form'; return new $formclassname($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; for ($i = 0; isset($data->{'dimensionid__idx_' . $i}); $i++) { $grade = new stdclass(); $grade->id = $data->{'gradeid__idx_' . $i}; $grade->assessmentid = $assessment->id; $grade->strategy = 'rubric'; $grade->dimensionid = $data->{'dimensionid__idx_' . $i}; $chosenlevel = $data->{'chosenlevelid__idx_'.$i}; $grade->grade = $this->dimensions[$grade->dimensionid]->levels[$chosenlevel]->grade; 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 AS id, MIN(l.grade) AS min, MAX(l.grade) AS max, 1 AS weight FROM {workshopform_rubric} d INNER JOIN {workshopform_rubric_levels} l ON (d.id = l.dimensionid) WHERE d.workshopid = :workshopid GROUP BY d.id'; $params = array('workshopid' => $this->workshop->id); return $DB->get_records_sql($sql, $params); } /** * 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; $dimensions = $DB->get_records('workshopform_rubric', array('workshopid' => $workshopid), '', 'id'); $DB->delete_records_list('workshopform_rubric_levels', 'dimensionid', array_keys($dimensions)); $DB->delete_records('workshopform_rubric', array('workshopid' => $workshopid)); $DB->delete_records('workshopform_rubric_config', 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 r.id AS rid, r.sort, r.description, r.descriptionformat, l.id AS lid, l.grade, l.definition, l.definitionformat FROM {workshopform_rubric} r LEFT JOIN {workshopform_rubric_levels} l ON (l.dimensionid = r.id) WHERE r.workshopid = :workshopid ORDER BY r.sort, l.grade"; $params = array('workshopid' => $this->workshop->id); $rs = $DB->get_recordset_sql($sql, $params); $fields = array(); foreach ($rs as $record) { if (!isset($fields[$record->rid])) { $fields[$record->rid] = new stdclass(); $fields[$record->rid]->id = $record->rid; $fields[$record->rid]->sort = $record->sort; $fields[$record->rid]->description = $record->description; $fields[$record->rid]->descriptionformat = $record->descriptionformat; $fields[$record->rid]->levels = array(); } if (!empty($record->lid)) { $fields[$record->rid]->levels[$record->lid] = new stdclass(); $fields[$record->rid]->levels[$record->lid]->id = $record->lid; $fields[$record->rid]->levels[$record->lid]->grade = $record->grade; $fields[$record->rid]->levels[$record->lid]->definition = $record->definition; $fields[$record->rid]->levels[$record->lid]->definitionformat = $record->definitionformat; } } $rs->close(); return $fields; } /** * Get the configuration for the current rubric strategy * * @return object */ protected function load_config() { global $DB; if (!$config = $DB->get_record('workshopform_rubric_config', array('workshopid' => $this->workshop->id), 'layout')) { $config = (object)array('layout' => 'list'); } return $config; } /** * Maps the dimension data from DB to the form fields * * @param array $fields Array of dimensions definition as returned by {@link load_fields()} * @return stdclass of fields data to be used by the mform set_data */ protected function prepare_form_fields(array $fields) { $formdata = new stdclass(); $key = 0; foreach ($fields as $field) { $formdata->{'dimensionid__idx_' . $key} = $field->id; $formdata->{'description__idx_' . $key} = $field->description; $formdata->{'description__idx_' . $key.'format'} = $field->descriptionformat; $formdata->{'numoflevels__idx_' . $key} = count($field->levels); $lev = 0; foreach ($field->levels as $level) { $formdata->{'levelid__idx_' . $key . '__idy_' . $lev} = $level->id; $formdata->{'grade__idx_' . $key . '__idy_' . $lev} = $level->grade; $formdata->{'definition__idx_' . $key . '__idy_' . $lev} = $level->definition; $formdata->{'definition__idx_' . $key . '__idy_' . $lev . 'format'} = $level->definitionformat; $lev++; } $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; $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($this->workshop->context->id, 'workshopform_rubric', 'description', $id); } } $DB->delete_records_list('workshopform_rubric', 'id', $ids); } /** * Prepares data returned by {@link workshop_edit_rubric_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) { $cook = array(); for ($i = 0; $i < $raw->norepeats; $i++) { $cook[$i] = new stdclass(); $cook[$i]->id = $raw->{'dimensionid__idx_'.$i}; $cook[$i]->workshopid = $this->workshop->id; $cook[$i]->sort = $i + 1; $cook[$i]->description_editor = $raw->{'description__idx_'.$i.'_editor'}; $cook[$i]->levels = array(); $j = 0; while (isset($raw->{'levelid__idx_' . $i . '__idy_' . $j})) { $level = new stdclass(); $level->id = $raw->{'levelid__idx_' . $i . '__idy_' . $j}; $level->grade = $raw->{'grade__idx_'.$i.'__idy_'.$j}; $level->definition = $raw->{'definition__idx_'.$i.'__idy_'.$j}; $level->definitionformat = FORMAT_HTML; $cook[$i]->levels[] = $level; $j++; } } 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' => 'rubric'); $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; } // summarize the grades given in rubrics $sumgrades = 0; foreach ($grades as $grade) { $sumgrades += $grade->grade; } // get the minimal and maximal possible grade (sum of minimal/maximal grades across all dimensions) $mingrade = 0; $maxgrade = 0; foreach ($this->dimensions as $dimension) { $mindimensiongrade = null; $maxdimensiongrade = null; foreach ($dimension->levels as $level) { if (is_null($mindimensiongrade) or $level->grade < $mindimensiongrade) { $mindimensiongrade = $level->grade; } if (is_null($maxdimensiongrade) or $level->grade > $maxdimensiongrade) { $maxdimensiongrade = $level->grade; } } $mingrade += $mindimensiongrade; $maxgrade += $maxdimensiongrade; } if ($maxgrade - $mingrade > 0) { return grade_floatval(100 * ($sumgrades - $mingrade) / ($maxgrade - $mingrade)); } else { return null; } } } version.php000064400000002264152267045070006756 0ustar00. /** * Defines the version of workshop rubric grading strategy subplugin * * This code fragment is called by moodle_needs_upgrading() and * /admin/index.php * * @package workshopform_rubric * @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_rubric'; assessment_form.php000064400000016615152267045070010506 0ustar00. /** * This file defines mforms to assess a submission by rubric grading strategy * * Rubric can be displayed in two possible layouts - list or grid. This file defines * therefore defines two classes, respectively. * * @package workshopform_rubric * @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 /** * Base class representing a form for assessing submissions by rubric grading strategy */ abstract class workshop_rubric_assessment_form extends workshop_assessment_form { public function validation($data, $files) { $errors = parent::validation($data, $files); for ($i = 0; isset($data['dimensionid__idx_'.$i]); $i++) { if (empty($data['chosenlevelid__idx_'.$i])) { $errors['chosenlevelid__idx_'.$i] = get_string('mustchooseone', 'workshopform_rubric'); // used in grid $errors['levelgrp__idx_'.$i] = get_string('mustchooseone', 'workshopform_rubric'); // used in list } } return $errors; } } /** * Class representing a form for assessing submissions by rubric grading strategy - list layout */ class workshop_rubric_list_assessment_form extends workshop_rubric_assessment_form { /** * Define the elements to be displayed at the form * * Called by the parent::definition() * * @return void */ protected function definition_inner(&$mform) { $workshop = $this->_customdata['workshop']; $fields = $this->_customdata['fields']; $current = $this->_customdata['current']; $nodims = $this->_customdata['nodims']; // number of assessment dimensions for ($i = 0; $i < $nodims; $i++) { // dimension header $dimtitle = get_string('dimensionnumber', 'workshopform_rubric', $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); $numoflevels = $fields->{'numoflevels__idx_'.$i}; $levelgrp = array(); for ($j = 0; $j < $numoflevels; $j++) { $levelid = $fields->{'levelid__idx_'.$i.'__idy_'.$j}; $definition = $fields->{'definition__idx_'.$i.'__idy_'.$j}; $definitionformat = $fields->{'definition__idx_'.$i.'__idy_'.$j.'format'}; $levelgrp[] = $mform->createElement( 'radio', "chosenlevelid__idx_{$i}", '', format_text($definition, $definitionformat, [ 'context' => \core\context\course::instance($workshop->course->id), ]), $levelid, ); } $mform->addGroup($levelgrp, 'levelgrp__idx_'.$i, '', "
\n", false); } $this->set_data($current); } } /** * Class representing a form for assessing submissions by rubric grading strategy - grid layout */ class workshop_rubric_grid_assessment_form extends workshop_rubric_assessment_form { /** * Define the elements to be displayed at the form * * Called by the parent::definition() * * @return void */ protected function definition_inner(&$mform) { $workshop = $this->_customdata['workshop']; $fields = $this->_customdata['fields']; $current = $this->_customdata['current']; $nodims = $this->_customdata['nodims']; // number of assessment dimensions // get the number of required grid columns $levelcounts = array(); for ($i = 0; $i < $nodims; $i++) { if ($fields->{'numoflevels__idx_'.$i} > 0) { $levelcounts[] = $fields->{'numoflevels__idx_'.$i}; } } $numofcolumns = array_reduce($levelcounts, 'workshop::lcm', 1); $mform->addElement('header', 'rubric-grid-wrapper', get_string('layoutgrid', 'workshopform_rubric')); $mform->addElement('html', '' . "\n"); $mform->addElement('html', ''); $mform->addElement('html', ''); for ($i = 0; $i < $nodims; $i++) { $mform->addElement('html', '\n"; $mform->addElement('html', $desc); $numoflevels = $fields->{'numoflevels__idx_'.$i}; for ($j = 0; $j < $numoflevels; $j++) { $colspan = $numofcolumns / $numoflevels; $mform->addElement('html', '' . "\n"); } $mform->addElement('html', '' . "\n"); } $mform->addElement('html', '
' . get_string('criteria', 'workshopform_rubric') . ''.get_string('levels', 'workshopform_rubric').'
' . "\n"); // dimension id $mform->addElement('hidden', 'dimensionid__idx_'.$i, $fields->{'dimensionid__idx_'.$i}); $mform->setType('dimensionid__idx_'.$i, PARAM_INT); // given grade id $mform->addElement('hidden', 'gradeid__idx_'.$i); // value set by set_data() later $mform->setType('gradeid__idx_'.$i, PARAM_INT); // dimension description $desc = format_text($fields->{'description__idx_'.$i}, $fields->{'description__idx_'.$i.'format'}); $desc .= "' . "\n"); $levelid = $fields->{'levelid__idx_'.$i.'__idy_'.$j}; $definition = $fields->{'definition__idx_'.$i.'__idy_'.$j}; $definitionformat = $fields->{'definition__idx_'.$i.'__idy_'.$j.'format'}; $mform->addElement('radio', 'chosenlevelid__idx_'.$i, '', format_text($definition, $definitionformat, null, $workshop->course->id), $levelid); $mform->addElement('html', '
' . "\n"); $this->set_data($current); } } classes/privacy/provider.php000064400000011455152267045070012237 0ustar00. /** * Provides the class {@link workshopform_rubric\privacy\provider} * * @package workshopform_rubric * @category privacy * @copyright 2018 David Mudrák * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace workshopform_rubric\privacy; use core_privacy\local\request\writer; defined('MOODLE_INTERNAL') || die(); /** * Privacy API implementation for the Rubric 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 r.id, r.workshopid, r.description, r.descriptionformat, rl.id AS levelid, rl.grade AS levelgrade, rl.definition, rl.definitionformat, wg.grade 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_rubric} r ON r.workshopid = w.id JOIN {workshopform_rubric_levels} rl ON rl.dimensionid = r.id LEFT JOIN {workshop_grades} wg ON wg.strategy = :strategy AND wg.dimensionid = r.id AND wg.assessmentid = :assessmentid WHERE ctx.id = :contextid ORDER BY r.sort, rl.grade DESC"; $params = [ 'strategy' => 'rubric', 'contextlevel' => CONTEXT_MODULE, 'contextid' => $context->id, 'assessmentid' => $assessmentid, ]; $writer = \core_privacy\local\request\writer::with_context($context); $criteria = []; $workshopid = null; $hasdata = false; $rs = $DB->get_recordset_sql($sql, $params); foreach ($rs as $record) { if (empty($criteria[$record->id])) { $criteria[$record->id] = (object) [ 'description' => $writer->rewrite_pluginfile_urls($subcontext, 'workshopform_rubric', 'description', $record->id, $record->description), 'descriptionformat' => $record->descriptionformat, 'grade' => $record->grade, 'levels' => [], ]; $workshopid = $record->workshopid; } $criteria[$record->id]->levels[] = (object) [ 'grade' => $record->levelgrade, 'definition' => $record->definition, 'definitionformat' => $record->definitionformat, ]; if ($record->grade !== null) { $hasdata = true; } } $rs->close(); if ($hasdata) { $data = (object) [ 'criteria' => array_values($criteria), ]; $layout = $DB->get_field('workshopform_rubric_config', 'layout', ['workshopid' => $workshopid]); foreach (array_keys($criteria) as $dimensionid) { $writer->export_area_files($subcontext, 'workshopform_rubric', 'description', $dimensionid); } $writer->export_data($subcontext, $data); $writer->export_metadata($subcontext, 'layout', $layout, get_string('layout', 'workshopform_rubric')); } } } tests/privacy/provider_test.php000064400000014424152267045070013002 0ustar00. /** * Provides the {@see workshopform_rubric\privacy\provider_test} class. * * @package workshopform_rubric * @category test * @copyright 2018 David Mudrák * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace workshopform_rubric\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_rubric\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_rubric', [ 'workshopid' => $this->workshop11->id, 'sort' => 1, 'description' => 'Criterion 1 description', 'descriptionformat' => FORMAT_MARKDOWN, ]); $DB->insert_record('workshopform_rubric_levels', [ 'dimensionid' => $this->dim1, 'grade' => 0, 'definition' => 'Missing', 'definitionformat' => FORMAT_PLAIN, ]); $DB->insert_record('workshopform_rubric_levels', [ 'dimensionid' => $this->dim1, 'grade' => 1, 'definition' => 'Poor', 'definitionformat' => FORMAT_PLAIN, ]); $DB->insert_record('workshopform_rubric_levels', [ 'dimensionid' => $this->dim1, 'grade' => 2, 'definition' => 'Good', 'definitionformat' => FORMAT_PLAIN, ]); $this->dim2 = $DB->insert_record('workshopform_rubric', [ 'workshopid' => $this->workshop11->id, 'sort' => 2, 'description' => 'Criterion 2 description', 'descriptionformat' => FORMAT_MARKDOWN, ]); $DB->insert_record('workshopform_rubric_levels', [ 'dimensionid' => $this->dim2, 'grade' => 0, 'definition' => 'Missing', 'definitionformat' => FORMAT_PLAIN, ]); $DB->insert_record('workshopform_rubric_levels', [ 'dimensionid' => $this->dim2, 'grade' => 5, 'definition' => 'Great', 'definitionformat' => FORMAT_PLAIN, ]); $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' => 'rubric', 'dimensionid' => $this->dim1, 'grade' => 1, 'peercomment' => '', 'peercommentformat' => FORMAT_PLAIN, ]); $DB->insert_record('workshop_grades', [ 'assessmentid' => $this->assessment1112, 'strategy' => 'rubric', 'dimensionid' => $this->dim2, 'grade' => 5, 'peercomment' => '', '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_rubric'), ]); $this->assertEquals('Criterion 1 description', $form->criteria[0]->description); $this->assertEquals(3, count($form->criteria[0]->levels)); $this->assertEquals(2, count($form->criteria[1]->levels)); $this->assertEquals(5, $form->criteria[1]->grade); } } tests/lib_test.php000064400000013132152267045070010234 0ustar00. /** * Unit tests for Rubric grading strategy logic * * @package workshopform_rubric * @category test * @copyright 2009 David Mudrak * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace workshopform_rubric; use workshop; use workshop_rubric_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/rubric/lib.php'); /** * Unit tests for Rubric grading strategy 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' => 'rubric', 'course' => $course)); $cm = get_fast_modinfo($course)->instances['workshop'][$workshop->id]; $this->workshop = new workshop($workshop, $cm, $course); $this->strategy = new testable_workshop_rubric_strategy($this->workshop); // prepare dimensions definition $dim = new \stdClass(); $dim->id = 6; $dim->levels[10] = (object)array('id' => 10, 'grade' => 0); $dim->levels[13] = (object)array('id' => 13, 'grade' => 2); $dim->levels[14] = (object)array('id' => 14, 'grade' => 6); $dim->levels[16] = (object)array('id' => 16, 'grade' => 8); $this->strategy->dimensions[$dim->id] = $dim; $dim = new \stdClass(); $dim->id = 8; $dim->levels[17] = (object)array('id' => 17, 'grade' => 0); $dim->levels[18] = (object)array('id' => 18, 'grade' => 1); $dim->levels[19] = (object)array('id' => 19, 'grade' => 2); $dim->levels[20] = (object)array('id' => 20, 'grade' => 3); $this->strategy->dimensions[$dim->id] = $dim; $dim = new \stdClass(); $dim->id = 10; $dim->levels[27] = (object)array('id' => 27, 'grade' => 10); $dim->levels[28] = (object)array('id' => 28, 'grade' => 20); $dim->levels[29] = (object)array('id' => 29, 'grade' => 30); $dim->levels[30] = (object)array('id' => 30, 'grade' => 40); $this->strategy->dimensions[$dim->id] = $dim; } protected function tearDown(): void { $this->strategy = null; $this->workshop = null; parent::tearDown(); } public function test_calculate_peer_grade_null_grade(): void { // fixture set-up $grades = array(); // exercise SUT $suggested = $this->strategy->calculate_peer_grade($grades); // validate $this->assertNull($suggested); } public function test_calculate_peer_grade_worst_possible(): void { // fixture set-up $grades[6] = (object)array('dimensionid' => 6, 'grade' => 0); $grades[8] = (object)array('dimensionid' => 8, 'grade' => 0); $grades[10] = (object)array('dimensionid' => 10, 'grade' => 10); // exercise SUT $suggested = $this->strategy->calculate_peer_grade($grades); // validate $this->assertEquals(grade_floatval($suggested), 0.00000); } public function test_calculate_peer_grade_best_possible(): void { // fixture set-up $grades[6] = (object)array('dimensionid' => 6, 'grade' => 8); $grades[8] = (object)array('dimensionid' => 8, 'grade' => 3); $grades[10] = (object)array('dimensionid' => 10, 'grade' => 40); // exercise SUT $suggested = $this->strategy->calculate_peer_grade($grades); // validate $this->assertEquals(grade_floatval($suggested), 100.00000); } public function test_calculate_peer_grade_something(): void { // fixture set-up $grades[6] = (object)array('dimensionid' => 6, 'grade' => 2); $grades[8] = (object)array('dimensionid' => 8, 'grade' => 2); $grades[10] = (object)array('dimensionid' => 10, 'grade' => 30); // exercise SUT $suggested = $this->strategy->calculate_peer_grade($grades); // validate // minimal rubric score is 10, maximal is 51. We have 34 here $this->assertEquals(grade_floatval($suggested), grade_floatval(100 * 24 / 41)); } } /** * Test subclass that makes all the protected methods we want to test public */ class testable_workshop_rubric_strategy extends workshop_rubric_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); } }