ÿØÿà 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|\YH local_settings_form.phpnu[. namespace core_filters; use core\context; use moodleform; defined('MOODLE_INTERNAL') || die(); require_once($CFG->libdir . '/formslib.php'); /** * A Moodle form base class for editing local filter settings. * * @copyright Tim Hunt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @package core_filters */ abstract class local_settings_form extends moodleform { /** * Create an instance of the form. * * @param string $submiturl * @param string $filter * @param context $context */ public function __construct( string $submiturl, /** @var string The filter to manage */ protected string $filter, /** @var \core\context The context */ protected context $context, ) { parent::__construct($submiturl); } #[\Override] public function definition() { $mform = $this->_form; $this->definition_inner($mform); $mform->addElement('hidden', 'contextid'); $mform->setType('contextid', PARAM_INT); $mform->setDefault('contextid', $this->context->id); $mform->addElement('hidden', 'filter'); $mform->setType('filter', PARAM_SAFEPATH); $mform->setDefault('filter', $this->filter); $this->add_action_buttons(); } /** * Override this method to add your form controls. * * @param \MoodleQuickForm $mform the form we are building. $this->_form, but passed in for convenience. */ abstract protected function definition_inner($mform); /** * Override this method to save the settings to the database. * * The default implementation will probably be sufficient for most simple cases. * * @param object $data the form data that was submitted. */ public function save_changes($data) { $data = (array) $data; unset($data['filter']); unset($data['contextid']); foreach ($data as $name => $value) { if ($value !== '') { filter_set_local_config($this->filter, $this->context->id, $name, $value); } else { filter_unset_local_config($this->filter, $this->context->id, $name); } } } } class_alias(local_settings_form::class, \filter_local_settings_form::class); PK,}\xS4PPprofile_field_form.phpnu[. namespace core_user\form; use context; use core_form\dynamic_form; use moodle_url; use profile_define_base; /** * Class field_form used for profile fields. * * @package core_user * @copyright 2007 onwards Shane Elliot {@link http://pukunui.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class profile_field_form extends dynamic_form { /** @var profile_define_base $field */ public $field; /** @var \stdClass */ protected $fieldrecord; /** * Define the form */ public function definition() { global $CFG; require_once($CFG->dirroot.'/user/profile/definelib.php'); $mform = $this->_form; // Everything else is dependant on the data type. $datatype = $this->get_field_record()->datatype; require_once($CFG->dirroot.'/user/profile/field/'.$datatype.'/define.class.php'); $newfield = 'profile_define_'.$datatype; $this->field = new $newfield(); // Add some extra hidden fields. $mform->addElement('hidden', 'id'); $mform->setType('id', PARAM_INT); $mform->addElement('hidden', 'action', 'editfield'); $mform->setType('action', PARAM_ALPHANUMEXT); $mform->addElement('hidden', 'datatype', $datatype); $mform->setType('datatype', PARAM_ALPHA); $this->field->define_form($mform); } /** * Alter definition based on existing or submitted data */ public function definition_after_data() { $mform = $this->_form; $this->field->define_after_data($mform); } /** * Perform some moodle validation. * @param array $data * @param array $files * @return array */ public function validation($data, $files) { return $this->field->define_validate($data, $files); } /** * Returns the defined editors for the field. * @return array */ public function editors(): array { $editors = $this->field->define_editors(); return is_array($editors) ? $editors : []; } /** * Returns context where this form is used * * @return context */ protected function get_context_for_dynamic_submission(): context { return \context_system::instance(); } /** * Checks if current user has access to this form, otherwise throws exception */ protected function check_access_for_dynamic_submission(): void { require_capability('moodle/site:config', $this->get_context_for_dynamic_submission()); } /** * Process the form submission, used if form was submitted via AJAX */ public function process_dynamic_submission() { global $CFG; require_once($CFG->dirroot.'/user/profile/definelib.php'); profile_save_field($this->get_data(), $this->editors()); } /** * Load in existing data as form defaults */ public function set_data_for_dynamic_submission(): void { $field = $this->get_field_record(); // Clean and prepare description for the editor. $description = clean_text($field->description, $field->descriptionformat); $field->description = ['text' => $description, 'format' => $field->descriptionformat, 'itemid' => 0]; // Convert the data format for. if (is_array($this->editors())) { foreach ($this->editors() as $editor) { if (isset($field->$editor)) { $editordesc = clean_text($field->$editor, $field->{$editor.'format'}); $field->$editor = ['text' => $editordesc, 'format' => $field->{$editor.'format'}, 'itemid' => 0]; } } } $this->set_data($field); } /** * Returns url to set in $PAGE->set_url() when form is being rendered or submitted via AJAX * * @return moodle_url */ protected function get_page_url_for_dynamic_submission(): moodle_url { $id = $this->optional_param('id', 0, PARAM_INT); $datatype = $this->optional_param('datatype', 'text', PARAM_PLUGIN); return new moodle_url('/user/profile/index.php', ['action' => 'editfield', 'id' => $id, 'datatype' => $id ? null : $datatype]); } /** * Record for the field from the database (or generic record for a new field) * * @return false|mixed|\stdClass * @throws \coding_exception * @throws \dml_exception */ public function get_field_record() { global $DB; if (!$this->fieldrecord) { $id = $this->optional_param('id', 0, PARAM_INT); if (!$id || !($this->fieldrecord = $DB->get_record('user_info_field', ['id' => $id]))) { $datatype = $this->optional_param('datatype', 'text', PARAM_PLUGIN); $this->fieldrecord = new \stdClass(); $this->fieldrecord->datatype = $datatype; $this->fieldrecord->description = ''; $this->fieldrecord->descriptionformat = FORMAT_HTML; $this->fieldrecord->defaultdata = ''; $this->fieldrecord->defaultdataformat = FORMAT_HTML; $this->fieldrecord->categoryid = $this->optional_param('categoryid', 0, PARAM_INT); } if (!\core_component::get_component_directory('profilefield_'.$this->fieldrecord->datatype)) { throw new \moodle_exception('fieldnotfound', 'customfield'); } } return $this->fieldrecord; } } PK,}\&defaulthomepage_form.phpnu[. namespace core_user\form; use core\di; use core\hook\manager; use core\lang_string; use core_user\hook\extend_default_homepage; defined('MOODLE_INTERNAL') || die; require_once($CFG->dirroot . '/lib/formslib.php'); /** * Form to allow user to set their default home page * * @package core_user * @copyright 2019 Paul Holden * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class defaulthomepage_form extends \moodleform { /** * Define the form. */ public function definition() { global $CFG; $mform = $this->_form; $mform->addElement('hidden', 'id'); $mform->setType('id', PARAM_INT); $options = [HOMEPAGE_SITE => new lang_string('home')]; if (!empty($CFG->enabledashboard)) { $options[HOMEPAGE_MY] = new lang_string('mymoodle', 'admin'); } $options[HOMEPAGE_MYCOURSES] = new lang_string('mycourses', 'admin'); // Allow hook callbacks to extend options. $hook = new extend_default_homepage(true); di::get(manager::class)->dispatch($hook); $options += $hook->get_options(); $mform->addElement('select', 'defaulthomepage', get_string('defaulthomepageuser'), $options); $mform->addHelpButton('defaulthomepage', 'defaulthomepageuser'); $mform->setDefault('defaulthomepage', get_default_home_page()); $this->add_action_buttons(true, get_string('savechanges')); } } PK,}\y 66%contentbank_user_preferences_form.phpnu[. namespace core_user\form; use \core_contentbank\content; defined('MOODLE_INTERNAL') || die; require_once($CFG->dirroot.'/lib/formslib.php'); /** * Form to edit a user's preferences concerning the content bank. * * @package core_user * @copyright 2020 François Moreau * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class contentbank_user_preferences_form extends \moodleform { /** * Define the form. */ public function definition() { global $CFG, $USER; $mform = $this->_form; $mform->addElement('hidden', 'id'); $mform->setType('id', PARAM_INT); $options = [ content::VISIBILITY_PUBLIC => get_string('visibilitychoicepublic', 'core_contentbank'), content::VISIBILITY_UNLISTED => get_string('visibilitychoiceunlisted', 'core_contentbank') ]; $mform->addElement('select', 'contentvisibility', get_string('visibilitypref', 'core_contentbank'), $options); $mform->addHelpButton('contentvisibility', 'visibilitypref', 'core_contentbank'); $this->add_action_buttons(true, get_string('savechanges')); } } PK,}\q;calendar_form.phpnu[. /** * Form to edit a users preferred language * * @copyright 2015 Shamim Rezaie http://foodle.org * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @package core_user */ namespace core_user\form; if (!defined('MOODLE_INTERNAL')) { die('Direct access to this script is forbidden.'); // It must be included from a Moodle page. } require_once($CFG->dirroot.'/lib/formslib.php'); /** * Class user_edit_calendar_form. * * @copyright 2015 Shamim Rezaie http://foodle.org * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class calendar_form extends \moodleform { /** * Define the form. */ public function definition() { global $CFG, $USER; $mform = $this->_form; $userid = $USER->id; if (is_array($this->_customdata)) { if (array_key_exists('userid', $this->_customdata)) { $userid = $this->_customdata['userid']; } } // Add some extra hidden fields. $mform->addElement('hidden', 'id'); $mform->setType('id', PARAM_INT); // We do not want to show this option unless there is more than one calendar type to display. if (count(\core_calendar\type_factory::get_list_of_calendar_types()) > 1) { $calendartypes = \core_calendar\type_factory::get_list_of_calendar_types(); $mform->addElement('select', 'calendartype', get_string('preferredcalendar', 'calendar'), $calendartypes); $mform->setType('calendartype', PARAM_ALPHANUM); $mform->setDefault('calendartype', $CFG->calendartype); } else { $mform->addElement('hidden', 'calendartype', $CFG->calendartype); $mform->setType('calendartype', PARAM_ALPHANUM); } // Date / Time settings. $options = array( '0' => get_string('default', 'calendar'), CALENDAR_TF_12 => get_string('timeformat_12', 'calendar'), CALENDAR_TF_24 => get_string('timeformat_24', 'calendar') ); $mform->addElement('select', 'timeformat', get_string('pref_timeformat', 'calendar'), $options); $mform->addHelpButton('timeformat', 'pref_timeformat', 'calendar'); // First day of week. $options = array( 0 => get_string('sunday', 'calendar'), 1 => get_string('monday', 'calendar'), 2 => get_string('tuesday', 'calendar'), 3 => get_string('wednesday', 'calendar'), 4 => get_string('thursday', 'calendar'), 5 => get_string('friday', 'calendar'), 6 => get_string('saturday', 'calendar') ); $mform->addElement('select', 'startwday', get_string('pref_startwday', 'calendar'), $options); $mform->addHelpButton('startwday', 'pref_startwday', 'calendar'); // Maximum events to display. $options = array(); for ($i = 1; $i <= 20; $i++) { $options[$i] = $i; } $mform->addElement('select', 'maxevents', get_string('pref_maxevents', 'calendar'), $options); $mform->addHelpButton('maxevents', 'pref_maxevents', 'calendar'); // Calendar lookahead. $options = array(365 => new \lang_string('numyear', '', 1), 270 => get_string('nummonths', '', 9), 180 => get_string('nummonths', '', 6), 150 => get_string('nummonths', '', 5), 120 => get_string('nummonths', '', 4), 90 => get_string('nummonths', '', 3), 60 => get_string('nummonths', '', 2), 30 => get_string('nummonth', '', 1), 21 => get_string('numweeks', '', 3), 14 => get_string('numweeks', '', 2), 7 => get_string('numweek', '', 1), 6 => get_string('numdays', '', 6), 5 => get_string('numdays', '', 5), 4 => get_string('numdays', '', 4), 3 => get_string('numdays', '', 3), 2 => get_string('numdays', '', 2), 1 => get_string('numday', '', 1)); $mform->addElement('select', 'lookahead', get_string('pref_lookahead', 'calendar'), $options); $mform->addHelpButton('lookahead', 'pref_lookahead', 'calendar'); // Remember event filtering. $options = array( 0 => get_string('no'), 1 => get_string('yes') ); $mform->addElement('select', 'persistflt', get_string('pref_persistflt', 'calendar'), $options); $mform->addHelpButton('persistflt', 'pref_persistflt', 'calendar'); $this->add_action_buttons(true, get_string('savechanges')); } /** * Extend the form definition after the data has been parsed. */ public function definition_after_data() { global $CFG; $mform = $this->_form; // If calendar type does not exist, use site default calendar type. if ($calendarselected = $mform->getElementValue('calendartype')) { if (is_array($calendarselected)) { // There are multiple calendar types available. $calendar = reset($calendarselected); } else { // There is only one calendar type available. $calendar = $calendarselected; } // Check calendar type exists. if (!array_key_exists($calendar, \core_calendar\type_factory::get_list_of_calendar_types())) { $calendartypeel = $mform->getElement('calendartype'); $calendartypeel->setValue($CFG->calendartype); } } } } PK,}\yyprivate_files.phpnu[. namespace core_user\form; use html_writer; use moodle_url; /** * Manage user private area files form * * @package core_user * @copyright 2010 Petr Skoda (http://skodak.org) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class private_files extends \core_form\dynamic_form { /** * Add elements to this form. */ public function definition() { global $OUTPUT; $mform = $this->_form; $options = $this->get_options(); // Show file area space usage. $maxareabytes = $options['areamaxbytes']; if ($maxareabytes != FILE_AREA_MAX_BYTES_UNLIMITED) { $fileareainfo = file_get_file_area_info($this->get_context_for_dynamic_submission()->id, 'user', 'private'); // Display message only if we have files. if ($fileareainfo['filecount']) { $a = (object) [ 'used' => display_size($fileareainfo['filesize_without_references']), 'total' => display_size($maxareabytes, 0) ]; $quotamsg = get_string('quotausage', 'moodle', $a); $notification = new \core\output\notification($quotamsg, \core\output\notification::NOTIFY_INFO); $mform->addElement('static', 'areabytes', '', $OUTPUT->render($notification)); } } $mform->addElement('filemanager', 'files_filemanager', get_string('files'), null, $options); if ($link = $this->get_emaillink()) { $emaillink = html_writer::link(new moodle_url('mailto:' . $link), $link); $mform->addElement('static', 'emailaddress', '', get_string('emailtoprivatefiles', 'moodle', $emaillink)); } $mform->setType('returnurl', PARAM_LOCALURL); // The 'nosubmit' param (default false) determines whether we should show the standard form action buttons (save/cancel). // This value is set when the form is displayed within a modal, which adds the action buttons itself. if (!$this->optional_param('nosubmit', false, PARAM_BOOL)) { $this->add_action_buttons(); } } /** * Validate incoming data. * * @param array $data * @param array $files * @return array */ public function validation($data, $files) { $errors = array(); $draftitemid = $data['files_filemanager']; $options = $this->get_options(); if (file_is_draft_area_limit_reached($draftitemid, $options['areamaxbytes'])) { $errors['files_filemanager'] = get_string('userquotalimit', 'error'); } return $errors; } /** * Link to email private files * * @return string|null * @throws \coding_exception */ protected function get_emaillink() { global $USER; // Attempt to generate an inbound message address to support e-mail to private files. $generator = new \core\message\inbound\address_manager(); $generator->set_handler('\core\message\inbound\private_files_handler'); $generator->set_data(-1); return $generator->generate($USER->id); } /** * Check if current user has access to this form, otherwise throw exception * * Sometimes permission check may depend on the action and/or id of the entity. * If necessary, form data is available in $this->_ajaxformdata or * by calling $this->optional_param() */ public function check_access_for_dynamic_submission(): void { require_capability('moodle/user:manageownfiles', $this->get_context_for_dynamic_submission()); } /** * Returns form context * * If context depends on the form data, it is available in $this->_ajaxformdata or * by calling $this->optional_param() * * @return \context */ protected function get_context_for_dynamic_submission(): \context { global $USER; return \context_user::instance($USER->id); } /** * File upload options * * @return array * @throws \coding_exception */ public function get_options(): array { global $CFG; $maxbytes = $CFG->userquota; $maxareabytes = $CFG->userquota; if (has_capability('moodle/user:ignoreuserquota', $this->get_context_for_dynamic_submission())) { $maxbytes = USER_CAN_IGNORE_FILE_SIZE_LIMITS; $maxareabytes = FILE_AREA_MAX_BYTES_UNLIMITED; } return ['subdirs' => 1, 'maxbytes' => $maxbytes, 'maxfiles' => -1, 'accepted_types' => '*', 'areamaxbytes' => $maxareabytes]; } /** * Process the form submission, used if form was submitted via AJAX * * This method can return scalar values or arrays that can be json-encoded, they will be passed to the caller JS. * * Submission data can be accessed as: $this->get_data() * * @return mixed */ public function process_dynamic_submission() { file_postupdate_standard_filemanager($this->get_data(), 'files', $this->get_options(), $this->get_context_for_dynamic_submission(), 'user', 'private', 0); return null; } /** * Load in existing data as form defaults * * Can be overridden to retrieve existing values from db by entity id and also * to preprocess editor and filemanager elements * * Example: * $this->set_data(get_entity($this->_ajaxformdata['id'])); */ public function set_data_for_dynamic_submission(): void { $data = new \stdClass(); file_prepare_standard_filemanager($data, 'files', $this->get_options(), $this->get_context_for_dynamic_submission(), 'user', 'private', 0); $this->set_data($data); } /** * Returns url to set in $PAGE->set_url() when form is being rendered or submitted via AJAX * * This is used in the form elements sensitive to the page url, such as Atto autosave in 'editor' * * If the form has arguments (such as 'id' of the element being edited), the URL should * also have respective argument. * * @return \moodle_url */ protected function get_page_url_for_dynamic_submission(): \moodle_url { return new moodle_url('/user/files.php'); } } PK,}\\#profile_category_form.phpnu[. namespace core_user\form; use context; use core_form\dynamic_form; use moodle_url; /** * Modal form to edit profile category * * @package core_user * @copyright 2021 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class profile_category_form extends dynamic_form { /** * Form definition */ protected function definition() { $mform = $this->_form; $strrequired = get_string('required'); // Add some extra hidden fields. $mform->addElement('hidden', 'id'); $mform->setType('id', PARAM_INT); $mform->addElement('hidden', 'action', 'editcategory'); $mform->setType('action', PARAM_ALPHANUMEXT); $mform->addElement('text', 'name', get_string('profilecategoryname', 'admin'), 'maxlength="255" size="30"'); $mform->setType('name', PARAM_TEXT); $mform->addRule('name', $strrequired, 'required', null, 'client'); } /** * Perform some moodle validation. * * @param array $data * @param array $files * @return array */ public function validation($data, $files) { global $DB; $errors = parent::validation($data, $files); $duplicate = $DB->get_field('user_info_category', 'id', ['name' => $data['name']]); // Check the name is unique. if (!empty($data['id'])) { // We are editing an existing record. $olddata = $DB->get_record('user_info_category', ['id' => $data['id']]); // Name has changed, new name in use, new name in use by another record. $dupfound = (($olddata->name !== $data['name']) && $duplicate && ($data['id'] != $duplicate)); } else { // New profile category. $dupfound = $duplicate; } if ($dupfound ) { $errors['name'] = get_string('profilecategorynamenotunique', 'admin'); } return $errors; } /** * Returns context where this form is used * * @return context */ protected function get_context_for_dynamic_submission(): context { return \context_system::instance(); } /** * Checks if current user has access to this form, otherwise throws exception */ protected function check_access_for_dynamic_submission(): void { require_capability('moodle/site:config', $this->get_context_for_dynamic_submission()); } /** * Process the form submission, used if form was submitted via AJAX */ public function process_dynamic_submission() { global $CFG; require_once($CFG->dirroot.'/user/profile/definelib.php'); profile_save_category($this->get_data()); } /** * Load in existing data as form defaults */ public function set_data_for_dynamic_submission(): void { global $DB; if ($id = $this->optional_param('id', 0, PARAM_INT)) { $this->set_data($DB->get_record('user_info_category', ['id' => $id], '*', MUST_EXIST)); } } /** * Returns url to set in $PAGE->set_url() when form is being rendered or submitted via AJAX * * @return moodle_url */ protected function get_page_url_for_dynamic_submission(): moodle_url { $id = $this->optional_param('id', 0, PARAM_INT); return new moodle_url('/user/profile/index.php', ['action' => 'editcategory', 'id' => $id]); } }PK,}\l;Xcccontactsitesupport_form.phpnu[. namespace core_user\form; defined('MOODLE_INTERNAL') || die; require_once($CFG->dirroot.'/lib/formslib.php'); /** * Contact site support form. * * @package core_user * @copyright 2022 Simey Lameze * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class contactsitesupport_form extends \moodleform { /** * Define the contact site support form. */ public function definition(): void { global $CFG; $mform = $this->_form; $user = $this->_customdata; $strrequired = get_string('required'); // Name. $mform->addElement('text', 'name', get_string('name')); $mform->addRule('name', $strrequired, 'required', null, 'client'); $mform->setType('name', PARAM_TEXT); // Email. $mform->addElement('text', 'email', get_string('email')); $mform->addRule('email', get_string('missingemail'), 'required', null, 'client'); $mform->setType('email', PARAM_EMAIL); // Subject. $mform->addElement('text', 'subject', get_string('subject')); $mform->addRule('subject', $strrequired, 'required', null, 'client'); $mform->setType('subject', PARAM_TEXT); // Message. $mform->addElement('textarea', 'message', get_string('message')); $mform->addRule('message', $strrequired, 'required', null, 'client'); $mform->setType('message', PARAM_TEXT); // If the user is logged in set name and email fields to the current user info. if (isloggedin() && !isguestuser()) { $mform->setDefault('name', fullname($user)); $mform->hardFreeze('name'); $mform->setDefault('email', $user->email); $mform->hardFreeze('email'); } if (!empty($CFG->recaptchapublickey) && !empty($CFG->recaptchaprivatekey)) { $mform->addElement('recaptcha', 'recaptcha_element', get_string('security_question', 'auth')); $mform->addHelpButton('recaptcha_element', 'recaptcha', 'auth'); $mform->closeHeaderBefore('recaptcha_element'); } $this->add_action_buttons(true, get_string('submit')); } /** * Validate user supplied data on the contact site support form. * * @param array $data array of ("fieldname"=>value) of submitted data * @param array $files array of uploaded files "element_name"=>tmp_file_path * @return array of "element_name"=>"error_description" if there are errors, * or an empty array if everything is OK (true allowed for backwards compatibility too). */ public function validation($data, $files): array { $errors = parent::validation($data, $files); if (!validate_email($data['email'])) { $errors['email'] = get_string('invalidemail'); } if ($this->_form->elementExists('recaptcha_element')) { $recaptchaelement = $this->_form->getElement('recaptcha_element'); if (!empty($this->_form->_submitValues['g-recaptcha-response'])) { $response = $this->_form->_submitValues['g-recaptcha-response']; if (!$recaptchaelement->verify($response)) { $errors['recaptcha_element'] = get_string('incorrectpleasetryagain', 'auth'); } } else { $errors['recaptcha_element'] = get_string('missingrecaptchachallengefield'); } } return $errors; } } PK \_renameroles.phpnu[. namespace core_enrol\form; /** * Form to customise the course role names. * * @package core_enrol * @copyright 2023 Ferran Recio * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class renameroles extends \moodleform { /** * Form definition. */ public function definition() { $mform = $this->_form; $courseid = $this->_customdata['id']; $roles = $this->_customdata['roles'] ?? []; $formdata = new \stdClass(); $mform->addElement('hidden', 'id', $courseid); $mform->setType('id', PARAM_INT); foreach ($roles as $role) { $settingname = 'role_' . $role->id; $mform->addElement('text', $settingname, get_string('yourwordforx', '', $role->localname)); $mform->setType($settingname, PARAM_TEXT); $formdata->{$settingname} = $role->coursealias; } $mform->addElement('submit', 'submit', get_string('save')); $this->set_data($formdata); } } PKl\OOadd_outcome.phpnu[. namespace core_grades\form; defined('MOODLE_INTERNAL') || die; use context; use context_course; use core_form\dynamic_form; use grade_category; use grade_item; use grade_outcome; use grade_plugin_return; use moodle_url; require_once($CFG->dirroot.'/grade/lib.php'); /** * Prints the add outcome gradebook form. * * @copyright 2023 Mathew May * @license http://www.gnu.org/copyleft/gpl.html GNU Public License * @package core_grades */ class add_outcome extends dynamic_form { /** Grade plugin return tracking object. * @var object $gpr */ public $gpr; /** * Helper function to grab the current grade outcome item based on information within the form. * * @return array * @throws \moodle_exception */ private function get_gradeitem(): array { $courseid = $this->optional_param('courseid', null, PARAM_INT); $id = $this->optional_param('itemid', null, PARAM_INT); if ($gradeitem = grade_item::fetch(['id' => $id, 'courseid' => $courseid])) { // Redirect if outcomeid not present. if (empty($gradeitem->outcomeid)) { $url = new moodle_url('/grade/edit/tree/item.php', ['id' => $id, 'courseid' => $courseid]); redirect($this->gpr->add_url_params($url)); } $item = $gradeitem->get_record_data(); $parentcategory = $gradeitem->get_parent_category(); if ($item->itemtype == 'mod') { $cm = get_coursemodule_from_instance($item->itemmodule, $item->iteminstance, $item->courseid); $item->cmid = $cm->id; } else { $item->cmid = 0; } } else { $gradeitem = new grade_item(['courseid' => $courseid, 'itemtype' => 'manual'], false); $item = $gradeitem->get_record_data(); $parentcategory = grade_category::fetch_course_category($courseid); } $item->parentcategory = $parentcategory->id; if ($item->hidden > 1) { $item->hiddenuntil = $item->hidden; $item->hidden = 0; } else { $item->hiddenuntil = 0; } $item->locked = !empty($item->locked); if ($parentcategory->aggregation == GRADE_AGGREGATE_SUM || $parentcategory->aggregation == GRADE_AGGREGATE_WEIGHTED_MEAN2) { $item->aggregationcoef = $item->aggregationcoef == 0 ? 0 : 1; } else { $item->aggregationcoef = format_float($item->aggregationcoef, 4); } if ($parentcategory->aggregation == GRADE_AGGREGATE_SUM) { $item->aggregationcoef2 = format_float($item->aggregationcoef2 * 100.0); } $item->cancontrolvisibility = $gradeitem->can_control_visibility(); return [ 'gradeitem' => $gradeitem, 'item' => $item ]; } /** * Form definition * * @return void * @throws \coding_exception * @throws \dml_exception * @throws \moodle_exception */ protected function definition() { $courseid = $this->optional_param('courseid', null, PARAM_INT); $id = $this->optional_param('itemid', 0, PARAM_INT); $gprplugin = $this->optional_param('gpr_plugin', '', PARAM_TEXT); if ($gprplugin && ($gprplugin !== 'tree')) { $this->gpr = new grade_plugin_return(['type' => 'report', 'plugin' => $gprplugin, 'courseid' => $courseid]); } else { $this->gpr = new grade_plugin_return(['type' => 'edit', 'plugin' => 'tree', 'courseid' => $courseid]); } $mform =& $this->_form; $local = $this->get_gradeitem(); $gradeitem = $local['gradeitem']; $item = $local['item']; // Hidden elements. $mform->addElement('hidden', 'id', 0); $mform->setType('id', PARAM_INT); $mform->addElement('hidden', 'courseid', $courseid); $mform->setType('courseid', PARAM_INT); $mform->addElement('hidden', 'itemid', $id); $mform->setType('itemid', PARAM_INT); // Allow setting of outcomes on module items too. $outcomeoptions = []; if ($outcomes = grade_outcome::fetch_all_available($courseid)) { foreach ($outcomes as $outcome) { $outcomeoptions[$outcome->id] = $outcome->get_name(); } } // Visible elements. $mform->addElement('text', 'itemname', get_string('itemname', 'grades')); $mform->addRule('itemname', get_string('required'), 'required', null, 'client'); $mform->setType('itemname', PARAM_TEXT); $mform->addElement('selectwithlink', 'outcomeid', get_string('outcome', 'grades'), $outcomeoptions); $mform->addHelpButton('outcomeid', 'outcome', 'grades'); $mform->addRule('outcomeid', get_string('required'), 'required'); $options = [0 => get_string('none')]; if ($coursemods = get_course_mods($courseid)) { foreach ($coursemods as $coursemod) { if ($mod = get_coursemodule_from_id($coursemod->modname, $coursemod->id)) { $options[$coursemod->id] = format_string($mod->name); } } } $mform->addElement('select', 'cmid', get_string('linkedactivity', 'grades'), $options); $mform->addHelpButton('cmid', 'linkedactivity', 'grades'); $mform->setDefault('cmid', 0); // Hiding. $mform->addElement('checkbox', 'hidden', get_string('hidden', 'grades')); $mform->addHelpButton('hidden', 'hidden', 'grades'); // Locking. $mform->addElement('advcheckbox', 'locked', get_string('locked', 'grades')); $mform->addHelpButton('locked', 'locked', 'grades'); // Parent category related settings. $mform->addElement('advcheckbox', 'weightoverride', get_string('adjustedweight', 'grades')); $mform->addHelpButton('weightoverride', 'weightoverride', 'grades'); $mform->addElement('text', 'aggregationcoef2', get_string('weight', 'grades')); $mform->addHelpButton('aggregationcoef2', 'weight', 'grades'); $mform->setType('aggregationcoef2', PARAM_RAW); $mform->hideIf('aggregationcoef2', 'weightoverride'); $options = []; $coefstring = ''; $categories = grade_category::fetch_all(['courseid' => $courseid]); foreach ($categories as $cat) { $cat->apply_forced_settings(); $options[$cat->id] = $cat->get_name(); if ($cat->is_aggregationcoef_used()) { if ($cat->aggregation == GRADE_AGGREGATE_WEIGHTED_MEAN) { $coefstring = ($coefstring == '' || $coefstring == 'aggregationcoefweight') ? 'aggregationcoefweight' : 'aggregationcoef'; } else if ($cat->aggregation == GRADE_AGGREGATE_WEIGHTED_MEAN2) { $coefstring = ($coefstring == '' || $coefstring == 'aggregationcoefextrasum') ? 'aggregationcoefextrasum' : 'aggregationcoef'; } else if ($cat->aggregation == GRADE_AGGREGATE_EXTRACREDIT_MEAN) { $coefstring = ($coefstring == '' || $coefstring == 'aggregationcoefextraweight') ? 'aggregationcoefextraweight' : 'aggregationcoef'; } else if ($cat->aggregation == GRADE_AGGREGATE_SUM) { $coefstring = ($coefstring == '' || $coefstring == 'aggregationcoefextrasum') ? 'aggregationcoefextrasum' : 'aggregationcoef'; } else { $coefstring = 'aggregationcoef'; } } else { $mform->disabledIf('aggregationcoef', 'parentcategory', 'eq', $cat->id); } } if (count($categories) > 1) { $mform->addElement('select', 'parentcategory', get_string('gradecategory', 'grades'), $options); $mform->disabledIf('parentcategory', 'cmid', 'noteq', 0); } if ($coefstring !== '') { if ($coefstring == 'aggregationcoefextrasum' || $coefstring == 'aggregationcoefextraweightsum') { $coefstring = 'aggregationcoefextrasum'; $mform->addElement('checkbox', 'aggregationcoef', get_string($coefstring, 'grades')); } else { $mform->addElement('text', 'aggregationcoef', get_string($coefstring, 'grades')); } $mform->addHelpButton('aggregationcoef', $coefstring, 'grades'); } // Remove the aggregation coef element if not needed. if ($gradeitem->is_course_item()) { if ($mform->elementExists('parentcategory')) { $mform->removeElement('parentcategory'); } if ($mform->elementExists('aggregationcoef')) { $mform->removeElement('aggregationcoef'); } } else { // If we wanted to change parent of existing item - we would have to verify there are no circular references in parents. if ($id > -1 && $mform->elementExists('parentcategory')) { $mform->hardFreeze('parentcategory'); } $parentcategory = $gradeitem->get_parent_category(); if (!$parentcategory) { // If we do not have an id, we are creating a new grade item. // Assign the course category to this grade item. $parentcategory = grade_category::fetch_course_category($courseid); $gradeitem->parent_category = $parentcategory; } $parentcategory->apply_forced_settings(); if (!$parentcategory->is_aggregationcoef_used() || !$parentcategory->aggregateoutcomes) { if ($mform->elementExists('aggregationcoef')) { $mform->removeElement('aggregationcoef'); } } else { // Fix label if needed. $agg_el =& $mform->getElement('aggregationcoef'); $aggcoef = ''; if ($parentcategory->aggregation == GRADE_AGGREGATE_WEIGHTED_MEAN) { $aggcoef = 'aggregationcoefweight'; } else if ($parentcategory->aggregation == GRADE_AGGREGATE_WEIGHTED_MEAN2) { $aggcoef = 'aggregationcoefextrasum'; } else if ($parentcategory->aggregation == GRADE_AGGREGATE_EXTRACREDIT_MEAN) { $aggcoef = 'aggregationcoefextraweight'; } else if ($parentcategory->aggregation == GRADE_AGGREGATE_SUM) { $aggcoef = 'aggregationcoefextrasum'; } if ($aggcoef !== '') { $agg_el->setLabel(get_string($aggcoef, 'grades')); $mform->addHelpButton('aggregationcoef', $aggcoef, 'grades'); } } // Remove the natural weighting fields for other aggregations, // or when the category does not aggregate outcomes. if ($parentcategory->aggregation != GRADE_AGGREGATE_SUM || !$parentcategory->aggregateoutcomes) { if ($mform->elementExists('weightoverride')) { $mform->removeElement('weightoverride'); } if ($mform->elementExists('aggregationcoef2')) { $mform->removeElement('aggregationcoef2'); } } } $url = new moodle_url('/grade/edit/tree/outcomeitem.php', ['id' => $id, 'courseid' => $courseid]); $url = $this->gpr->add_url_params($url); $url = '' . get_string('showmore', 'form') .''; $mform->addElement('static', 'advancedform', $url); // Add return tracking info. $this->gpr->add_mform_elements($mform); $this->set_data($item); } /** * Return form context * * @return context */ protected function get_context_for_dynamic_submission(): context { $courseid = $this->optional_param('courseid', null, PARAM_INT); return context_course::instance($courseid); } /** * Check if current user has access to this form, otherwise throw exception * * @return void * @throws \required_capability_exception */ protected function check_access_for_dynamic_submission(): void { $courseid = $this->optional_param('courseid', null, PARAM_INT); require_capability('moodle/grade:manage', context_course::instance($courseid)); } /** * Load in existing data as form defaults * * @return void */ public function set_data_for_dynamic_submission(): void { $this->set_data((object)[ 'courseid' => $this->optional_param('courseid', null, PARAM_INT), 'itemid' => $this->optional_param('itemid', null, PARAM_INT) ]); } /** * Returns url to set in $PAGE->set_url() when form is being rendered or submitted via AJAX * * @return moodle_url * @throws \moodle_exception */ protected function get_page_url_for_dynamic_submission(): moodle_url { $params = [ 'id' => $this->optional_param('courseid', null, PARAM_INT), 'itemid' => $this->optional_param('itemid', null, PARAM_INT), ]; return new moodle_url('/grade/edit/tree/index.php', $params); } /** * Process the form submission, used if form was submitted via AJAX * * @return array * @throws \moodle_exception */ public function process_dynamic_submission() { global $DB; $data = $this->get_data(); $url = $this->gpr->get_return_url('index.php?id=' . $data->courseid); $local = $this->get_gradeitem(); $gradeitem = $local['gradeitem']; $item = $local['item']; $parentcategory = grade_category::fetch_course_category($data->courseid); // Form submission handling. // If unset, give the aggregation values a default based on parent aggregation method. $defaults = grade_category::get_default_aggregation_coefficient_values($parentcategory->aggregation); if (!isset($data->aggregationcoef) || $data->aggregationcoef == '') { $data->aggregationcoef = $defaults['aggregationcoef']; } if (!isset($data->weightoverride)) { $data->weightoverride = $defaults['weightoverride']; } if (property_exists($data, 'calculation')) { $data->calculation = grade_item::normalize_formula($data->calculation, $data->courseid); } $hide = empty($data->hiddenuntil) ? 0 : $data->hiddenuntil; if (!$hide) { $hide = empty($data->hidden) ? 0 : $data->hidden; } $locked = empty($data->locked) ? 0 : $data->locked; $locktime = empty($data->locktime) ? 0 : $data->locktime; $convert = ['gradepass', 'aggregationcoef', 'aggregationcoef2']; foreach ($convert as $param) { if (property_exists($data, $param)) { $data->$param = unformat_float($data->$param); } } if (isset($data->aggregationcoef2) && $parentcategory->aggregation == GRADE_AGGREGATE_SUM) { $data->aggregationcoef2 = $data->aggregationcoef2 / 100.0; } else { $data->aggregationcoef2 = $defaults['aggregationcoef2']; } grade_item::set_properties($gradeitem, $data); // Link this outcome item to the user specified linked activity. if (empty($data->cmid) || $data->cmid == 0) { // Manual item. $gradeitem->itemtype = 'manual'; $gradeitem->itemmodule = null; $gradeitem->iteminstance = null; $gradeitem->itemnumber = 0; } else { $params = [$data->cmid]; $module = $DB->get_record_sql("SELECT cm.*, m.name as modname FROM {modules} m, {course_modules} cm WHERE cm.id = ? AND cm.module = m.id ", $params); $gradeitem->itemtype = 'mod'; $gradeitem->itemmodule = $module->modname; $gradeitem->iteminstance = $module->instance; if ($items = grade_item::fetch_all(['itemtype' => 'mod', 'itemmodule' => $gradeitem->itemmodule, 'iteminstance' => $gradeitem->iteminstance, 'courseid' => $data->courseid])) { if (!empty($gradeitem->id) && in_array($gradeitem, $items)) { // No change needed. } else { $max = 999; foreach ($items as $item) { if (empty($item->outcomeid)) { continue; } if ($item->itemnumber > $max) { $max = $item->itemnumber; } } $gradeitem->itemnumber = $max + 1; } } else { $gradeitem->itemnumber = 1000; } } // Fix scale used. $outcome = grade_outcome::fetch(['id' => $data->outcomeid]); $gradeitem->gradetype = GRADE_TYPE_SCALE; $gradeitem->scaleid = $outcome->scaleid; // TODO: we might recalculate existing outcome grades when changing scale. if (empty($gradeitem->id)) { $gradeitem->insert(); // Move next to activity if adding linked outcome. if ($gradeitem->itemtype == 'mod') { if ($linkeditem = grade_item::fetch(['itemtype' => 'mod', 'itemmodule' => $gradeitem->itemmodule, 'iteminstance' => $gradeitem->iteminstance, 'itemnumber' => 0, 'courseid' => $data->courseid])) { $gradeitem->set_parent($linkeditem->categoryid); $gradeitem->move_after_sortorder($linkeditem->sortorder); } } else { // Set parent if needed. if (isset($data->parentcategory)) { $gradeitem->set_parent($data->parentcategory, false); } } } else { $gradeitem->update(); } if ($item->cancontrolvisibility) { // Update hiding flag. $gradeitem->set_hidden($hide, true); } $gradeitem->set_locktime($locktime); // Locktime first - it might be removed when unlocking. $gradeitem->set_locked($locked, false, true); return [ 'result' => true, 'url' => $url, 'errors' => [], ]; } /** * Form validation. * * @param array $data array of ("fieldname"=>value) of submitted data * @param array $files array of uploaded files "element_name"=>tmp_file_path * @return array of "element_name"=>"error_description" if there are errors, * or an empty array if everything is OK (true allowed for backwards compatibility too). */ public function validation($data, $files): array { $errors = []; $local = $this->get_gradeitem(); $gradeitem = $local['gradeitem']; $item = $local['item']; if (!grade_verify_idnumber($gradeitem->id, $item->courseid, $gradeitem)) { $errors['idnumber'] = get_string('idnumbertaken'); } return $errors; } } PKl\uuadd_category.phpnu[. namespace core_grades\form; use context; use context_course; use core_form\dynamic_form; use grade_category; use grade_edit_tree; use grade_helper; use grade_item; use grade_plugin_return; use grade_scale; use moodle_url; defined('MOODLE_INTERNAL') || die(); require_once($CFG->dirroot . '/grade/lib.php'); require_once($CFG->dirroot . '/grade/edit/tree/lib.php'); /** * Prints the add category gradebook form * * @copyright 2023 Ilya Tregubov * @license http://www.gnu.org/copyleft/gpl.html GNU Public License * @package core_grades */ class add_category extends dynamic_form { /** Grade plugin return tracking object. * @var object $gpr */ public $gpr; /** Available aggregations. * @var array|null $aggregation_options */ private ?array $aggregation_options; /** * Helper function to grab the current grade category based on information within the form. * * @return array * @throws \moodle_exception */ private function get_gradecategory(): array { $courseid = $this->optional_param('courseid', null, PARAM_INT); $id = $this->optional_param('category', null, PARAM_INT); if ($gradecategory = grade_category::fetch(['id' => $id, 'courseid' => $courseid])) { $gradecategory->apply_forced_settings(); $category = $gradecategory->get_record_data(); // Set parent. $category->parentcategory = $gradecategory->parent; $gradeitem = $gradecategory->load_grade_item(); // Normalize coef values if needed. $parentcategory = $gradecategory->get_parent_category(); foreach ($gradeitem->get_record_data() as $key => $value) { $category->{"grade_item_$key"} = $value; } $decimalpoints = $gradeitem->get_decimals(); $category->grade_item_grademax = format_float($category->grade_item_grademax, $decimalpoints); $category->grade_item_grademin = format_float($category->grade_item_grademin, $decimalpoints); $category->grade_item_gradepass = format_float($category->grade_item_gradepass, $decimalpoints); $category->grade_item_multfactor = format_float($category->grade_item_multfactor, 4); $category->grade_item_plusfactor = format_float($category->grade_item_plusfactor, 4); $category->grade_item_aggregationcoef2 = format_float($category->grade_item_aggregationcoef2 * 100.0, 4); if (isset($parentcategory)) { if ($parentcategory->aggregation == GRADE_AGGREGATE_SUM || $parentcategory->aggregation == GRADE_AGGREGATE_WEIGHTED_MEAN2) { $category->grade_item_aggregationcoef = $category->grade_item_aggregationcoef == 0 ? 0 : 1; } else { $category->grade_item_aggregationcoef = format_float($category->grade_item_aggregationcoef, 4); } } // Check if the gradebook is frozen. This allows grades not be altered at all until a user verifies that they // wish to update the grades. $gradebookcalculationsfreeze = get_config('core', 'gradebook_calculations_freeze_' . $courseid); // Stick with the original code if the grade book is frozen. if ($gradebookcalculationsfreeze && (int)$gradebookcalculationsfreeze <= 20150627) { if ($category->aggregation == GRADE_AGGREGATE_SUM) { // Input fields for grademin and grademax are disabled for the "Natural" category, // this means they will be ignored if user does not change aggregation method. // But if user does change aggregation method the default values should be used. $category->grademax = 100; $category->grade_item_grademax = 100; $category->grademin = 0; $category->grade_item_grademin = 0; } } else { if ($category->aggregation == GRADE_AGGREGATE_SUM && !$gradeitem->is_calculated()) { // Input fields for grademin and grademax are disabled for the "Natural" category, // this means they will be ignored if user does not change aggregation method. // But if user does change aggregation method the default values should be used. // This does not apply to calculated category totals. $category->grademax = 100; $category->grade_item_grademax = 100; $category->grademin = 0; $category->grade_item_grademin = 0; } } } else { $gradecategory = new grade_category(['courseid' => $courseid], false); $gradecategory->apply_default_settings(); $gradecategory->apply_forced_settings(); $category = $gradecategory->get_record_data(); $gradeitem = new grade_item(['courseid' => $courseid, 'itemtype' => 'manual'], false); foreach ($gradeitem->get_record_data() as $key => $value) { $category->{"grade_item_$key"} = $value; } } return [ 'gradecategory' => $gradecategory, 'categoryitem' => $category, 'gradeitem' => $gradeitem ]; } /** * Form definition * * @return void * @throws \coding_exception * @throws \dml_exception * @throws \moodle_exception */ protected function definition(): void { global $CFG, $OUTPUT, $COURSE; $courseid = $this->optional_param('courseid', null, PARAM_INT); $id = $this->optional_param('category', 0, PARAM_INT); $gprplugin = $this->optional_param('gpr_plugin', '', PARAM_TEXT); if ($gprplugin && ($gprplugin !== 'tree')) { $this->gpr = new grade_plugin_return(['type' => 'report', 'plugin' => $gprplugin, 'courseid' => $courseid]); } else { $this->gpr = new grade_plugin_return(['type' => 'edit', 'plugin' => 'tree', 'courseid' => $courseid]); } $mform = $this->_form; $this->aggregation_options = grade_helper::get_aggregation_strings(); $local = $this->get_gradecategory(); $category = $local['categoryitem']; // Hidden elements. $mform->addElement('hidden', 'id', 0); $mform->setType('id', PARAM_INT); $mform->addElement('hidden', 'courseid', $courseid); $mform->setType('courseid', PARAM_INT); $mform->addElement('hidden', 'category', $id); $mform->setType('category', PARAM_INT); // Visible elements. $mform->addElement('text', 'fullname', get_string('categoryname', 'grades')); $mform->setType('fullname', PARAM_TEXT); $mform->addRule('fullname', null, 'required', null, 'client'); $mform->addElement('select', 'aggregation', get_string('aggregation', 'grades'), $this->aggregation_options); $mform->addHelpButton('aggregation', 'aggregation', 'grades'); $mform->addElement('checkbox', 'aggregateonlygraded', get_string('aggregateonlygraded', 'grades')); $mform->addHelpButton('aggregateonlygraded', 'aggregateonlygraded', 'grades'); if (empty($CFG->enableoutcomes)) { $mform->addElement('hidden', 'aggregateoutcomes'); $mform->setType('aggregateoutcomes', PARAM_INT); } else { $mform->addElement('checkbox', 'aggregateoutcomes', get_string('aggregateoutcomes', 'grades')); $mform->addHelpButton('aggregateoutcomes', 'aggregateoutcomes', 'grades'); } $mform->addElement('text', 'keephigh', get_string('keephigh', 'grades'), 'size="3"'); $mform->setType('keephigh', PARAM_INT); $mform->addHelpButton('keephigh', 'keephigh', 'grades'); $mform->addElement('text', 'droplow', get_string('droplow', 'grades'), 'size="3"'); $mform->setType('droplow', PARAM_INT); $mform->addHelpButton('droplow', 'droplow', 'grades'); $mform->hideIf('droplow', 'keephigh', 'noteq', 0); $mform->hideIf('keephigh', 'droplow', 'noteq', 0); $mform->hideIf('droplow', 'keephigh', 'noteq', 0); if (!empty($category->id)) { $gradeitem = $local['gradeitem']; // If grades exist set a message so the user knows why they can not alter the grade type or scale. // We could never change the grade type for external items, so only need to show this for manual grade items. if ($gradeitem->has_overridden_grades()) { // Set a message so the user knows why the can not alter the grade type or scale. if ($gradeitem->gradetype == GRADE_TYPE_SCALE) { $gradesexistmsg = get_string('modgradecategorycantchangegradetyporscalemsg', 'grades'); } else { $gradesexistmsg = get_string('modgradecategorycantchangegradetypemsg', 'grades'); } $notification = new \core\output\notification($gradesexistmsg, \core\output\notification::NOTIFY_INFO); $notification->set_show_closebutton(false); $mform->addElement('static', 'gradesexistmsg', '', $OUTPUT->render($notification)); } } $options = [ GRADE_TYPE_NONE => get_string('typenone', 'grades'), GRADE_TYPE_VALUE => get_string('typevalue', 'grades'), GRADE_TYPE_SCALE => get_string('typescale', 'grades'), GRADE_TYPE_TEXT => get_string('typetext', 'grades') ]; $mform->addElement('select', 'grade_item_gradetype', get_string('gradetype', 'grades'), $options); $mform->addHelpButton('grade_item_gradetype', 'gradetype', 'grades'); $mform->setDefault('grade_item_gradetype', GRADE_TYPE_VALUE); $mform->hideIf('grade_item_gradetype', 'aggregation', 'eq', GRADE_AGGREGATE_SUM); $options = [0 => get_string('usenoscale', 'grades')]; if ($scales = grade_scale::fetch_all_local($COURSE->id)) { foreach ($scales as $scale) { $options[$scale->id] = $scale->get_name(); } } if ($scales = grade_scale::fetch_all_global()) { foreach ($scales as $scale) { $options[$scale->id] = $scale->get_name(); } } // Ugly BC hack - it was possible to use custom scale from other courses. if (!empty($category->grade_item_scaleid) && !isset($options[$category->grade_item_scaleid])) { if ($scale = grade_scale::fetch(['id' => $category->grade_item_scaleid])) { $options[$scale->id] = $scale->get_name().' '.get_string('incorrectcustomscale', 'grades'); } } $mform->addElement('select', 'grade_item_scaleid', get_string('scale'), $options); $mform->addHelpButton('grade_item_scaleid', 'typescale', 'grades'); $mform->hideIf('grade_item_scaleid', 'grade_item_gradetype', 'noteq', GRADE_TYPE_SCALE); $mform->hideIf('grade_item_scaleid', 'aggregation', 'eq', GRADE_AGGREGATE_SUM); $choices = []; $choices[''] = get_string('choose'); $choices['no'] = get_string('no'); $choices['yes'] = get_string('yes'); $mform->addElement('select', 'grade_item_rescalegrades', get_string('modgradecategoryrescalegrades', 'grades'), $choices); $mform->addHelpButton('grade_item_rescalegrades', 'modgradecategoryrescalegrades', 'grades'); $mform->hideIf('grade_item_rescalegrades', 'grade_item_gradetype', 'noteq', GRADE_TYPE_VALUE); $mform->addElement('float', 'grade_item_grademax', get_string('grademax', 'grades')); $mform->addHelpButton('grade_item_grademax', 'grademax', 'grades'); $mform->hideIf('grade_item_grademax', 'grade_item_gradetype', 'noteq', GRADE_TYPE_VALUE); $mform->hideIf('grade_item_grademax', 'aggregation', 'eq', GRADE_AGGREGATE_SUM); if ((bool) get_config('moodle', 'grade_report_showmin')) { $mform->addElement('float', 'grade_item_grademin', get_string('grademin', 'grades')); $mform->addHelpButton('grade_item_grademin', 'grademin', 'grades'); $mform->hideIf('grade_item_grademin', 'grade_item_gradetype', 'noteq', GRADE_TYPE_VALUE); $mform->hideIf('grade_item_grademin', 'aggregation', 'eq', GRADE_AGGREGATE_SUM); } // Hiding. // advcheckbox is not compatible with disabledIf! $mform->addElement('checkbox', 'grade_item_hidden', get_string('hidden', 'grades')); $mform->addHelpButton('grade_item_hidden', 'hidden', 'grades'); // Locking. $mform->addElement('checkbox', 'grade_item_locked', get_string('locked', 'grades')); $mform->addHelpButton('grade_item_locked', 'locked', 'grades'); $mform->addElement('advcheckbox', 'grade_item_weightoverride', get_string('adjustedweight', 'grades')); $mform->addHelpButton('grade_item_weightoverride', 'weightoverride', 'grades'); $mform->addElement('float', 'grade_item_aggregationcoef2', get_string('weight', 'grades')); $mform->addHelpButton('grade_item_aggregationcoef2', 'weight', 'grades'); $mform->hideIf('grade_item_aggregationcoef2', 'grade_item_weightoverride'); $options = []; $default = -1; $categories = grade_category::fetch_all(['courseid' => $courseid]); foreach ($categories as $cat) { $cat->apply_forced_settings(); $options[$cat->id] = $cat->get_name(); if ($cat->is_course_category()) { $default = $cat->id; } } if (count($categories) > 1) { $mform->addElement('select', 'parentcategory', get_string('parentcategory', 'grades'), $options); $mform->setDefault('parentcategory', $default); } $params = ['courseid' => $courseid]; if ($id > 0) { $params['id'] = $id; } $url = new moodle_url('/grade/edit/tree/category.php', $params); $url = $this->gpr->add_url_params($url); $url = '' . get_string('showmore', 'form') .''; $mform->addElement('static', 'advancedform', $url); // Add return tracking info. $this->gpr->add_mform_elements($mform); $this->set_data($category); } /** * This method implements changes to the form that need to be made once the form data is set. */ public function definition_after_data(): void { global $CFG; $mform =& $this->_form; $categoryobject = new grade_category(); foreach ($categoryobject->forceable as $property) { if ((int)$CFG->{"grade_{$property}_flag"} & 1) { if ($mform->elementExists($property)) { if (empty($CFG->grade_hideforcedsettings)) { $mform->hardFreeze($property); } else { if ($mform->elementExists($property)) { $mform->removeElement($property); } } } } } if ($CFG->grade_droplow > 0) { if ($mform->elementExists('keephigh')) { $mform->removeElement('keephigh'); } } else if ($CFG->grade_keephigh > 0) { if ($mform->elementExists('droplow')) { $mform->removeElement('droplow'); } } if ($id = $mform->getElementValue('id')) { $gradecategory = grade_category::fetch(['id' => $id]); $gradeitem = $gradecategory->load_grade_item(); // Remove agg coef if not used. if ($gradecategory->is_course_category()) { if ($mform->elementExists('parentcategory')) { $mform->removeElement('parentcategory'); } } else { // If we wanted to change parent of existing category // we would have to verify there are no circular references in parents!!! if ($mform->elementExists('parentcategory')) { $mform->hardFreeze('parentcategory'); } } // Prevent the user from using drop lowest/keep highest when the aggregation method cannot handle it. if (!$gradecategory->can_apply_limit_rules()) { if ($mform->elementExists('keephigh')) { $mform->setConstant('keephigh', 0); $mform->hardFreeze('keephigh'); } if ($mform->elementExists('droplow')) { $mform->setConstant('droplow', 0); $mform->hardFreeze('droplow'); } } if ($gradeitem->is_calculated()) { $gradesexistmsg = get_string('calculationwarning', 'grades'); $gradesexisthtml = '
' . $gradesexistmsg . '
'; $mform->addElement('static', 'gradesexistmsg', '', $gradesexisthtml); // Following elements are ignored when calculation formula used. if ($mform->elementExists('aggregation')) { $mform->removeElement('aggregation'); } if ($mform->elementExists('keephigh')) { $mform->removeElement('keephigh'); } if ($mform->elementExists('droplow')) { $mform->removeElement('droplow'); } if ($mform->elementExists('aggregateonlygraded')) { $mform->removeElement('aggregateonlygraded'); } if ($mform->elementExists('aggregateoutcomes')) { $mform->removeElement('aggregateoutcomes'); } } // If it is a course category, remove the "required" rule from the "fullname" element. if ($gradecategory->is_course_category()) { unset($mform->_rules['fullname']); $key = array_search('fullname', $mform->_required); unset($mform->_required[$key]); } // If it is a course category and its fullname is ?, show an empty field. if ($gradecategory->is_course_category() && $mform->getElementValue('fullname') == '?') { $mform->setDefault('fullname', ''); } // Remove unwanted aggregation options. if ($mform->elementExists('aggregation')) { $allaggoptions = array_keys($this->aggregation_options); $aggel =& $mform->getElement('aggregation'); $visible = explode(',', $CFG->grade_aggregations_visible); if (!is_null($gradecategory->aggregation)) { // Current type is always visible. $visible[] = $gradecategory->aggregation; } foreach ($allaggoptions as $type) { if (!in_array($type, $visible)) { $aggel->removeOption($type); } } } } else { // Adding new category // Remove unwanted aggregation options. if ($mform->elementExists('aggregation')) { $allaggoptions = array_keys($this->aggregation_options); $aggel =& $mform->getElement('aggregation'); $visible = explode(',', $CFG->grade_aggregations_visible); foreach ($allaggoptions as $type) { if (!in_array($type, $visible)) { $aggel->removeOption($type); } } } $mform->removeElement('grade_item_rescalegrades'); } // Grade item. if ($id = $mform->getElementValue('id')) { $gradecategory = grade_category::fetch(['id' => $id]); $gradeitem = $gradecategory->load_grade_item(); // Load appropriate "hidden"/"hidden until" defaults. if (!$gradeitem->is_hiddenuntil()) { $mform->setDefault('grade_item_hidden', $gradeitem->get_hidden()); } if ($gradeitem->has_overridden_grades()) { // Can't change the grade type or the scale if there are grades. $mform->hardFreeze('grade_item_gradetype, grade_item_scaleid'); // If we are using scales then remove the unnecessary rescale and grade fields. if ($gradeitem->gradetype == GRADE_TYPE_SCALE) { $mform->removeElement('grade_item_rescalegrades'); $mform->removeElement('grade_item_grademax'); if ($mform->elementExists('grade_item_grademin')) { $mform->removeElement('grade_item_grademin'); } } else { // Not using scale, so remove it. $mform->removeElement('grade_item_scaleid'); $mform->hideIf('grade_item_grademax', 'grade_item_rescalegrades', 'eq', ''); $mform->hideIf('grade_item_grademin', 'grade_item_rescalegrades', 'eq', ''); } } else { // Remove the rescale element if there are no grades. $mform->removeElement('grade_item_rescalegrades'); } // Remove the aggregation coef element if not needed. if ($gradeitem->is_course_item()) { if ($mform->elementExists('grade_item_aggregationcoef')) { $mform->removeElement('grade_item_aggregationcoef'); } if ($mform->elementExists('grade_item_weightoverride')) { $mform->removeElement('grade_item_weightoverride'); } if ($mform->elementExists('grade_item_aggregationcoef2')) { $mform->removeElement('grade_item_aggregationcoef2'); } } else { if ($gradeitem->is_category_item()) { $category = $gradeitem->get_item_category(); $parentcategory = $category->get_parent_category(); } else { $parentcategory = $gradeitem->get_parent_category(); } $parentcategory->apply_forced_settings(); if (!$parentcategory->is_aggregationcoef_used()) { if ($mform->elementExists('grade_item_aggregationcoef')) { $mform->removeElement('grade_item_aggregationcoef'); } } else { $coefstring = $gradeitem->get_coefstring(); if ($coefstring == 'aggregationcoefextrasum' || $coefstring == 'aggregationcoefextraweightsum') { // Advcheckbox is not compatible with disabledIf! $coefstring = 'aggregationcoefextrasum'; $element =& $mform->createElement('checkbox', 'grade_item_aggregationcoef', get_string($coefstring, 'grades')); } else { $element =& $mform->createElement('text', 'grade_item_aggregationcoef', get_string($coefstring, 'grades')); $mform->setType('grade_item_aggregationcoef', PARAM_FLOAT); } $mform->insertElementBefore($element, 'parentcategory'); $mform->addHelpButton('grade_item_aggregationcoef', $coefstring, 'grades'); } // Remove fields used by natural weighting if the parent category is not using natural weighting. // Or if the item is a scale and scales are not used in aggregation. if ($parentcategory->aggregation != GRADE_AGGREGATE_SUM || (empty($CFG->grade_includescalesinaggregation) && $gradeitem->gradetype == GRADE_TYPE_SCALE)) { if ($mform->elementExists('grade_item_weightoverride')) { $mform->removeElement('grade_item_weightoverride'); } if ($mform->elementExists('grade_item_aggregationcoef2')) { $mform->removeElement('grade_item_aggregationcoef2'); } } } } } /** * Return form context * * @return context */ protected function get_context_for_dynamic_submission(): context { $courseid = $this->optional_param('courseid', null, PARAM_INT); return context_course::instance($courseid); } /** * Check if current user has access to this form, otherwise throw exception * * @return void * @throws \required_capability_exception */ protected function check_access_for_dynamic_submission(): void { $courseid = $this->optional_param('courseid', null, PARAM_INT); require_capability('moodle/grade:manage', context_course::instance($courseid)); } /** * Load in existing data as form defaults * * @return void */ public function set_data_for_dynamic_submission(): void { $this->set_data((object)[ 'courseid' => $this->optional_param('courseid', null, PARAM_INT), 'category' => $this->optional_param('category', null, PARAM_INT) ]); } /** * Returns url to set in $PAGE->set_url() when form is being rendered or submitted via AJAX * * @return moodle_url * @throws \moodle_exception */ protected function get_page_url_for_dynamic_submission(): moodle_url { $params = [ 'id' => $this->optional_param('courseid', null, PARAM_INT), 'category' => $this->optional_param('category', null, PARAM_INT), ]; return new moodle_url('/grade/edit/tree/index.php', $params); } /** * Process the form submission, used if form was submitted via AJAX * * @return array * @throws \moodle_exception */ public function process_dynamic_submission(): array { $data = $this->get_data(); $url = $this->gpr->get_return_url('index.php?id=' . $data->courseid); $local = $this->get_gradecategory(); $gradecategory = $local['gradecategory']; grade_edit_tree::update_gradecategory($gradecategory, $data); return [ 'result' => true, 'url' => $url, 'errors' => [], ]; } /** * Form validation. * * @param array $data array of ("fieldname"=>value) of submitted data * @param array $files array of uploaded files "element_name"=>tmp_file_path * @return array of "element_name"=>"error_description" if there are errors, * or an empty array if everything is OK (true allowed for backwards compatibility too). */ public function validation($data, $files): array { $gradeitem = false; if ($data['id']) { $gradecategory = grade_category::fetch(['id' => $data['id']]); $gradeitem = $gradecategory->load_grade_item(); } $errors = parent::validation($data, $files); if (array_key_exists('grade_item_gradetype', $data) && $data['grade_item_gradetype'] == GRADE_TYPE_SCALE) { if (empty($data['grade_item_scaleid'])) { $errors['grade_item_scaleid'] = get_string('missingscale', 'grades'); } } // We need to make all the validations related with grademax and grademin // with them being correct floats, keeping the originals unmodified for // later validations / showing the form back... // TODO: Note that once MDL-73994 is fixed we'll have to re-visit this and // adapt the code below to the new values arriving here, without forgetting // the special case of empties and nulls. $grademax = isset($data['grade_item_grademax']) ? unformat_float($data['grade_item_grademax']) : null; $grademin = isset($data['grade_item_grademin']) ? unformat_float($data['grade_item_grademin']) : null; if (!is_null($grademin) && !is_null($grademax)) { if (($grademax != 0 || $grademin != 0) && ($grademax == $grademin || $grademax < $grademin)) { $errors['grade_item_grademin'] = get_string('incorrectminmax', 'grades'); $errors['grade_item_grademax'] = get_string('incorrectminmax', 'grades'); } } if ($data['id'] && $gradeitem->has_overridden_grades()) { if ($gradeitem->gradetype == GRADE_TYPE_VALUE) { if (grade_floats_different($grademin, $gradeitem->grademin) || grade_floats_different($grademax, $gradeitem->grademax)) { if (empty($data['grade_item_rescalegrades'])) { $errors['grade_item_rescalegrades'] = get_string('mustchooserescaleyesorno', 'grades'); } } } } return $errors; } } PKl\j$Ϲl_l_ add_item.phpnu[. namespace core_grades\form; defined('MOODLE_INTERNAL') || die; use context; use context_course; use core_form\dynamic_form; use grade_category; use grade_item; use grade_plugin_return; use grade_scale; use moodle_url; require_once($CFG->dirroot.'/grade/lib.php'); /** * Prints the add item gradebook form * * @copyright 2023 Mathew May * @license http://www.gnu.org/copyleft/gpl.html GNU Public License * @package core_grades */ class add_item extends dynamic_form { /** Grade plugin return tracking object. * @var object $gpr */ public $gpr; /** * Helper function to grab the current grade item based on information within the form. * * @return array * @throws \moodle_exception */ private function get_gradeitem(): array { $courseid = $this->optional_param('courseid', null, PARAM_INT); $id = $this->optional_param('itemid', null, PARAM_INT); if ($gradeitem = grade_item::fetch(['id' => $id, 'courseid' => $courseid])) { $item = $gradeitem->get_record_data(); $parentcategory = $gradeitem->get_parent_category(); } else { $gradeitem = new grade_item(['courseid' => $courseid, 'itemtype' => 'manual'], false); $item = $gradeitem->get_record_data(); $parentcategory = grade_category::fetch_course_category($courseid); } $item->parentcategory = $parentcategory->id; $decimalpoints = $gradeitem->get_decimals(); if ($item->hidden > 1) { $item->hiddenuntil = $item->hidden; $item->hidden = 0; } else { $item->hiddenuntil = 0; } $item->locked = !empty($item->locked); $item->grademax = format_float($item->grademax, $decimalpoints); $item->grademin = format_float($item->grademin, $decimalpoints); if ($parentcategory->aggregation == GRADE_AGGREGATE_SUM || $parentcategory->aggregation == GRADE_AGGREGATE_WEIGHTED_MEAN2) { $item->aggregationcoef = $item->aggregationcoef == 0 ? 0 : 1; } else { $item->aggregationcoef = format_float($item->aggregationcoef, 4); } if ($parentcategory->aggregation == GRADE_AGGREGATE_SUM) { $item->aggregationcoef2 = format_float($item->aggregationcoef2 * 100.0); } $item->cancontrolvisibility = $gradeitem->can_control_visibility(); return [ 'gradeitem' => $gradeitem, 'item' => $item ]; } /** * Form definition * * @return void * @throws \coding_exception * @throws \dml_exception * @throws \moodle_exception */ protected function definition() { global $CFG; $courseid = $this->optional_param('courseid', null, PARAM_INT); $id = $this->optional_param('itemid', 0, PARAM_INT); $gprplugin = $this->optional_param('gpr_plugin', '', PARAM_TEXT); if ($gprplugin && ($gprplugin !== 'tree')) { $this->gpr = new grade_plugin_return(['type' => 'report', 'plugin' => $gprplugin, 'courseid' => $courseid]); } else { $this->gpr = new grade_plugin_return(['type' => 'edit', 'plugin' => 'tree', 'courseid' => $courseid]); } $mform =& $this->_form; $local = $this->get_gradeitem(); $gradeitem = $local['gradeitem']; $item = $local['item']; // Hidden elements. $mform->addElement('hidden', 'id', 0); $mform->setType('id', PARAM_INT); $mform->addElement('hidden', 'courseid', $courseid); $mform->setType('courseid', PARAM_INT); $mform->addElement('hidden', 'itemid', $id); $mform->setType('itemid', PARAM_INT); $mform->addElement('hidden', 'itemtype', 'manual'); // All new items are manual only. $mform->setType('itemtype', PARAM_ALPHA); // Visible elements. $mform->addElement('text', 'itemname', get_string('itemname', 'grades')); $mform->setType('itemname', PARAM_TEXT); if (!empty($item->id)) { // If grades exist set a message so the user knows why they can not alter the grade type or scale. // We could never change the grade type for external items, so only need to show this for manual grade items. if ($gradeitem->has_grades() && !$gradeitem->is_external_item()) { // Set a message so the user knows why they can not alter the grade type or scale. if ($gradeitem->gradetype == GRADE_TYPE_SCALE) { $gradesexistmsg = get_string('modgradecantchangegradetyporscalemsg', 'grades'); } else { $gradesexistmsg = get_string('modgradecantchangegradetypemsg', 'grades'); } $gradesexisthtml = '
' . $gradesexistmsg . '
'; $mform->addElement('static', 'gradesexistmsg', '', $gradesexisthtml); } } // Manual grade items cannot have grade type GRADE_TYPE_NONE. $mform->addElement('select', 'gradetype', get_string('gradetype', 'grades'), [ GRADE_TYPE_VALUE => get_string('typevalue', 'grades'), GRADE_TYPE_SCALE => get_string('typescale', 'grades'), GRADE_TYPE_TEXT => get_string('typetext', 'grades') ]); $mform->addHelpButton('gradetype', 'gradetype', 'grades'); $mform->setDefault('gradetype', GRADE_TYPE_VALUE); $options = [0 => get_string('usenoscale', 'grades')]; if ($scales = grade_scale::fetch_all_local($courseid)) { foreach ($scales as $scale) { $options[$scale->id] = $scale->get_name(); } } if ($scales = grade_scale::fetch_all_global()) { foreach ($scales as $scale) { $options[$scale->id] = $scale->get_name(); } } $mform->addElement('select', 'scaleid', get_string('scale'), $options); $mform->addHelpButton('scaleid', 'typescale', 'grades'); $mform->hideIf('scaleid', 'gradetype', 'noteq', GRADE_TYPE_SCALE); $mform->addElement('select', 'rescalegrades', get_string('modgraderescalegrades', 'grades'), [ '' => get_string('choose'), 'no' => get_string('no'), 'yes' => get_string('yes') ]); $mform->addHelpButton('rescalegrades', 'modgraderescalegrades', 'grades'); $mform->hideIf('rescalegrades', 'gradetype', 'noteq', GRADE_TYPE_VALUE); $mform->addElement('float', 'grademax', get_string('grademax', 'grades')); $mform->addHelpButton('grademax', 'grademax', 'grades'); $mform->hideIf('grademax', 'gradetype', 'noteq', GRADE_TYPE_VALUE); if (get_config('moodle', 'grade_report_showmin')) { $mform->addElement('float', 'grademin', get_string('grademin', 'grades')); $mform->addHelpButton('grademin', 'grademin', 'grades'); $mform->hideIf('grademin', 'gradetype', 'noteq', GRADE_TYPE_VALUE); } // Hiding. if ($item->cancontrolvisibility) { $mform->addElement('advcheckbox', 'hidden', get_string('hidden', 'grades'), '', [], [0, 1]); $mform->hideIf('hidden', 'hiddenuntil[enabled]', 'checked'); } else { $mform->addElement('static', 'hidden', get_string('hidden', 'grades'), get_string('componentcontrolsvisibility', 'grades')); // Unset hidden to avoid data override. unset($item->hidden); } $mform->addHelpButton('hidden', 'hidden', 'grades'); // Locking. $mform->addElement('advcheckbox', 'locked', get_string('locked', 'grades')); $mform->addHelpButton('locked', 'locked', 'grades'); // Weight overrides. $mform->addElement('advcheckbox', 'weightoverride', get_string('adjustedweight', 'grades')); $mform->addHelpButton('weightoverride', 'weightoverride', 'grades'); $mform->hideIf('weightoverride', 'gradetype', 'eq', GRADE_TYPE_NONE); $mform->hideIf('weightoverride', 'gradetype', 'eq', GRADE_TYPE_TEXT); // Parent category related settings. $mform->addElement('float', 'aggregationcoef2', get_string('weight', 'grades')); $mform->addHelpButton('aggregationcoef2', 'weight', 'grades'); $mform->hideIf('aggregationcoef2', 'weightoverride'); $mform->hideIf('aggregationcoef2', 'gradetype', 'eq', GRADE_TYPE_NONE); $mform->hideIf('aggregationcoef2', 'gradetype', 'eq', GRADE_TYPE_TEXT); $options = []; $categories = grade_category::fetch_all(['courseid' => $courseid]); foreach ($categories as $cat) { $cat->apply_forced_settings(); $options[$cat->id] = $cat->get_name(); } if (count($categories) > 1) { $mform->addElement('select', 'parentcategory', get_string('gradecategory', 'grades'), $options); } $parentcategory = $gradeitem->get_parent_category(); if (!$parentcategory) { // If we do not have an id, we are creating a new grade item. // Assign the course category to this grade item. $parentcategory = grade_category::fetch_course_category($courseid); $gradeitem->parent_category = $parentcategory; } if ($gradeitem->is_external_item()) { // Following items are set up from modules and should not be overrided by user. if ($mform->elementExists('grademin')) { // The site setting grade_report_showmin may have prevented grademin being added to the form. $mform->hardFreeze('grademin'); } $mform->hardFreeze('itemname,gradetype,grademax,scaleid'); // For external items we can not change the grade type, even if no grades exist, so if it is set to // scale, then remove the grademax and grademin fields from the form - no point displaying them. if ($gradeitem->gradetype == GRADE_TYPE_SCALE) { $mform->removeElement('grademax'); if ($mform->elementExists('grademin')) { $mform->removeElement('grademin'); } } else { // Not using scale, so remove it. $mform->removeElement('scaleid'); } // Always remove the rescale grades element if it's an external item. $mform->removeElement('rescalegrades'); } else if ($gradeitem->has_grades()) { // Can't change the grade type or the scale if there are grades. $mform->hardFreeze('gradetype, scaleid'); // If we are using scales then remove the unnecessary rescale and grade fields. if ($gradeitem->gradetype == GRADE_TYPE_SCALE) { $mform->removeElement('rescalegrades'); $mform->removeElement('grademax'); if ($mform->elementExists('grademin')) { $mform->removeElement('grademin'); } } else { // Remove the scale field. $mform->removeElement('scaleid'); // Set the maximum grade to disabled unless a grade is chosen. $mform->hideIf('grademax', 'rescalegrades', 'eq', ''); } } else { // Remove rescale element if there are no grades. $mform->removeElement('rescalegrades'); } // If we wanted to change parent of existing item - we would have to verify there are no circular references in parents!!! if ($id > -1 && $mform->elementExists('parentcategory')) { $mform->hardFreeze('parentcategory'); } $parentcategory->apply_forced_settings(); if (!$parentcategory->is_aggregationcoef_used()) { if ($mform->elementExists('aggregationcoef')) { $mform->removeElement('aggregationcoef'); } } else { $coefstring = $gradeitem->get_coefstring(); if ($coefstring !== '') { if ($coefstring == 'aggregationcoefextrasum' || $coefstring == 'aggregationcoefextraweightsum') { // The advcheckbox is not compatible with disabledIf! $coefstring = 'aggregationcoefextrasum'; $element =& $mform->createElement('checkbox', 'aggregationcoef', get_string($coefstring, 'grades')); } else { $element =& $mform->createElement('text', 'aggregationcoef', get_string($coefstring, 'grades')); $mform->setType('aggregationcoef', PARAM_FLOAT); } if ($mform->elementExists('parentcategory')) { $mform->insertElementBefore($element, 'parentcategory'); } else { $mform->insertElementBefore($element, 'aggregationcoef2'); } $mform->addHelpButton('aggregationcoef', $coefstring, 'grades'); } $mform->hideIf('aggregationcoef', 'gradetype', 'eq', GRADE_TYPE_NONE); $mform->hideIf('aggregationcoef', 'gradetype', 'eq', GRADE_TYPE_TEXT); $mform->hideIf('aggregationcoef', 'parentcategory', 'eq', $parentcategory->id); } // Remove fields used by natural weighting if the parent category is not using natural weighting. // Or if the item is a scale and scales are not used in aggregation. if ($parentcategory->aggregation != GRADE_AGGREGATE_SUM || (empty($CFG->grade_includescalesinaggregation) && $gradeitem->gradetype == GRADE_TYPE_SCALE)) { if ($mform->elementExists('weightoverride')) { $mform->removeElement('weightoverride'); } if ($mform->elementExists('aggregationcoef2')) { $mform->removeElement('aggregationcoef2'); } } if ($category = $gradeitem->get_item_category()) { if ($category->aggregation == GRADE_AGGREGATE_SUM) { if ($mform->elementExists('gradetype')) { $mform->hardFreeze('gradetype'); } if ($mform->elementExists('grademin')) { $mform->hardFreeze('grademin'); } if ($mform->elementExists('grademax')) { $mform->hardFreeze('grademax'); } if ($mform->elementExists('scaleid')) { $mform->removeElement('scaleid'); } } } $url = new moodle_url('/grade/edit/tree/item.php', ['id' => $id, 'courseid' => $courseid]); $url = $this->gpr->add_url_params($url); $url = '' . get_string('showmore', 'form') .''; $mform->addElement('static', 'advancedform', $url); // Add return tracking info. $this->gpr->add_mform_elements($mform); $this->set_data($item); } /** * Return form context * * @return context */ protected function get_context_for_dynamic_submission(): context { $courseid = $this->optional_param('courseid', null, PARAM_INT); return context_course::instance($courseid); } /** * Check if current user has access to this form, otherwise throw exception * * @return void * @throws \required_capability_exception */ protected function check_access_for_dynamic_submission(): void { $courseid = $this->optional_param('courseid', null, PARAM_INT); require_capability('moodle/grade:manage', context_course::instance($courseid)); } /** * Load in existing data as form defaults * * @return void */ public function set_data_for_dynamic_submission(): void { $this->set_data((object)[ 'courseid' => $this->optional_param('courseid', null, PARAM_INT), 'itemid' => $this->optional_param('itemid', null, PARAM_INT) ]); } /** * Returns url to set in $PAGE->set_url() when form is being rendered or submitted via AJAX * * @return moodle_url * @throws \moodle_exception */ protected function get_page_url_for_dynamic_submission(): moodle_url { $params = [ 'id' => $this->optional_param('courseid', null, PARAM_INT), 'itemid' => $this->optional_param('itemid', null, PARAM_INT), ]; return new moodle_url('/grade/edit/tree/index.php', $params); } /** * Process the form submission, used if form was submitted via AJAX * * @return array * @throws \moodle_exception */ public function process_dynamic_submission() { $data = $this->get_data(); $url = $this->gpr->get_return_url('index.php?id=' . $data->courseid); $local = $this->get_gradeitem(); $gradeitem = $local['gradeitem']; $item = $local['item']; $parentcategory = grade_category::fetch_course_category($data->courseid); // Form submission handling. // This is a new item, and the category chosen is different than the default category. if (empty($gradeitem->id) && isset($data->parentcategory) && $parentcategory->id != $data->parentcategory) { $parentcategory = grade_category::fetch(['id' => $data->parentcategory]); } // If unset, give the aggregation values a default based on parent aggregation method. $defaults = grade_category::get_default_aggregation_coefficient_values($parentcategory->aggregation); if (!isset($data->aggregationcoef) || $data->aggregationcoef == '') { $data->aggregationcoef = $defaults['aggregationcoef']; } if (!isset($data->weightoverride)) { $data->weightoverride = $defaults['weightoverride']; } if (!isset($data->gradepass) || $data->gradepass == '') { $data->gradepass = 0; } if (!isset($data->grademin) || $data->grademin == '') { $data->grademin = 0; } $hide = empty($data->hiddenuntil) ? 0 : $data->hiddenuntil; if (!$hide) { $hide = empty($data->hidden) ? 0 : $data->hidden; } $locked = empty($data->locked) ? 0 : $data->locked; $locktime = empty($data->locktime) ? 0 : $data->locktime; $convert = ['grademax', 'grademin', 'aggregationcoef', 'aggregationcoef2']; foreach ($convert as $param) { if (property_exists($data, $param)) { $data->$param = unformat_float($data->$param); } } if (isset($data->aggregationcoef2) && $parentcategory->aggregation == GRADE_AGGREGATE_SUM) { $data->aggregationcoef2 = $data->aggregationcoef2 / 100.0; } else { $data->aggregationcoef2 = $defaults['aggregationcoef2']; } $oldmin = $gradeitem->grademin; $oldmax = $gradeitem->grademax; grade_item::set_properties($gradeitem, $data); $gradeitem->outcomeid = null; // Handle null decimals value. if (!property_exists($data, 'decimals') || $data->decimals < 0) { $gradeitem->decimals = null; } if (empty($gradeitem->id)) { $gradeitem->itemtype = 'manual'; // All new items to be manual only. $gradeitem->insert(); // Set parent if needed. if (isset($data->parentcategory)) { $gradeitem->set_parent($data->parentcategory, false); } } else { $gradeitem->update(); if (!empty($data->rescalegrades) && $data->rescalegrades == 'yes') { $newmin = $gradeitem->grademin; $newmax = $gradeitem->grademax; $gradeitem->rescale_grades_keep_percentage($oldmin, $oldmax, $newmin, $newmax, 'gradebook'); } } if ($item->cancontrolvisibility) { // Update hiding flag. $gradeitem->set_hidden($hide, true); } $gradeitem->set_locktime($locktime); // Locktime first - it might be removed when unlocking. $gradeitem->set_locked($locked); return [ 'result' => true, 'url' => $url, 'errors' => [], ]; } /** * Form validation. * * @param array $data array of ("fieldname"=>value) of submitted data * @param array $files array of uploaded files "element_name"=>tmp_file_path * @return array of "element_name"=>"error_description" if there are errors, * or an empty array if everything is OK (true allowed for backwards compatibility too). */ public function validation($data, $files): array { $errors = []; $local = $this->get_gradeitem(); $gradeitem = $local['gradeitem']; if (isset($data['gradetype']) && $data['gradetype'] == GRADE_TYPE_SCALE) { if (empty($data['scaleid'])) { $errors['scaleid'] = get_string('missingscale', 'grades'); } } // We need to make all the validations related with grademax and grademin // with them being correct floats, keeping the originals unmodified for // later validations / showing the form back... // TODO: Note that once MDL-73994 is fixed we'll have to re-visit this and // adapt the code below to the new values arriving here, without forgetting // the special case of empties and nulls. $grademax = isset($data['grademax']) ? unformat_float($data['grademax']) : null; $grademin = isset($data['grademin']) ? unformat_float($data['grademin']) : null; if (!is_null($grademin) && !is_null($grademax)) { if ($grademax == $grademin || $grademax < $grademin) { $errors['grademin'] = get_string('incorrectminmax', 'grades'); $errors['grademax'] = get_string('incorrectminmax', 'grades'); } } // We do not want the user to be able to change the grade type or scale for this item if grades exist. if ($gradeitem && $gradeitem->has_grades()) { // Check that grade type is set - should never not be set unless form has been modified. if (!isset($data['gradetype'])) { $errors['gradetype'] = get_string('modgradecantchangegradetype', 'grades'); } else if ($data['gradetype'] !== $gradeitem->gradetype) { // Check if we are changing the grade type. $errors['gradetype'] = get_string('modgradecantchangegradetype', 'grades'); } else if ($data['gradetype'] == GRADE_TYPE_SCALE) { // Check if we are changing the scale - can't do this when grades exist. if (isset($data['scaleid']) && ($data['scaleid'] !== $gradeitem->scaleid)) { $errors['scaleid'] = get_string('modgradecantchangescale', 'grades'); } } } if ($gradeitem) { if ($gradeitem->gradetype == GRADE_TYPE_VALUE) { if ((((bool) get_config('moodle', 'grade_report_showmin')) && grade_floats_different($grademin, $gradeitem->grademin)) || grade_floats_different($grademax, $gradeitem->grademax)) { if ($gradeitem->has_grades() && empty($data['rescalegrades'])) { $errors['rescalegrades'] = get_string('mustchooserescaleyesorno', 'grades'); } } } } return $errors; } } PK\/NXZZform_trait.phpnu[. namespace core_completion\form; use core_grades\component_gradeitems; use cm_info; /** * Completion trait helper, with methods to add completion elements and validate them. * * @package core_completion * @since Moodle 4.3 * @copyright 2023 Sara Arjona (sara@moodle.com) * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ trait form_trait { /** @var string The suffix to be added to the completion elements when creating them (for example, 'completion_assign'). */ protected $suffix = ''; /** * Called during validation. * Override this method to indicate, based on the data, whether a custom completion rule is selected or not. * * @param array $data Input data (not yet validated) * @return bool True if one or more rules are enabled; false if none are. */ abstract protected function completion_rule_enabled($data); /** * Add completion elements to the form and return the list of element ids. * * @return array Array of string IDs of added items, empty array if none */ abstract protected function add_completion_rules(); /** * Get the form associated to this class, where the completion elements will be added. * This method must be overriden by the class using this trait if it doesn't include a _form property. * * @return \MoodleQuickForm * @throws \coding_exception If the class does not have a _form property. */ protected function get_form(): \MoodleQuickForm { if (property_exists($this, '_form')) { return $this->_form; } throw new \coding_exception('This class does not have a _form property. Please, add it or override the get_form() method.'); } /** * Set the suffix to be added to the completion elements when creating them (for example, 'completion_assign'). * * @param string $suffix */ public function set_suffix(string $suffix): void { $this->suffix = $suffix; } /** * Get the suffix to be added to the completion elements when creating them (for example, 'completion_assign'). * * @return string The suffix */ public function get_suffix(): string { return $this->suffix; } /** * Add completion elements to the form. * * @param string|null $modname The module name (for example, 'assign'). If null and form is moodleform_mod, the parameters are * overriden with the expected values from the form. * @param bool $supportviews True if the module supports views and false otherwise. * @param bool $supportgrades True if the module supports grades and false otherwise. * @param bool $rating True if the rating feature is enabled and false otherwise. * @param int|null $courseid Course where to add completion elements. * @throws \coding_exception If the form is not moodleform_mod and $modname is null. */ protected function add_completion_elements( ?string $modname = null, bool $supportviews = false, bool $supportgrades = false, bool $rating = false, ?int $courseid = null ): void { global $SITE; $mform = $this->get_form(); if ($modname === null) { if ($this instanceof \moodleform_mod) { // By default, all the modules can be initiatized with the same parameters. $modname = $this->_modname; $supportviews = plugin_supports('mod', $modname, FEATURE_COMPLETION_TRACKS_VIEWS, false); $supportgrades = plugin_supports('mod', $modname, FEATURE_GRADE_HAS_GRADE, false); $rating = $this->_features->rating; } else { throw new \coding_exception('You must specify the modname parameter if you are not using a moodleform_mod.'); } } // Unlock button if people have completed it. The button will be removed later in definition_after_data if they haven't. // The unlock buttons don't need suffix because they are only displayed in the module settings page. $mform->addElement('submit', 'unlockcompletion', get_string('unlockcompletion', 'completion')); $mform->registerNoSubmitButton('unlockcompletion'); $mform->addElement('hidden', 'completionunlocked', 0); $mform->setType('completionunlocked', PARAM_INT); $trackingdefault = COMPLETION_TRACKING_NONE; // Get the sufix to add to the completion elements name. $suffix = $this->get_suffix(); $completionel = 'completion' . $suffix; $mform->addElement( 'radio', $completionel, '', get_string('completion_none', 'completion'), COMPLETION_TRACKING_NONE, ['class' => 'left-indented'] ); $mform->addElement( 'radio', $completionel, '', get_string('completion_manual', 'completion'), COMPLETION_TRACKING_MANUAL, ['class' => 'left-indented'] ); $allconditionsel = 'allconditions' . $suffix; $allconditions = $mform->createElement( 'static', $allconditionsel, '', get_string('allconditions', 'completion')); $conditionsgroupel = 'conditionsgroup' . $suffix; $mform->addGroup([$allconditions], $conditionsgroupel, '', null, false); $mform->hideIf($conditionsgroupel, $completionel, 'ne', COMPLETION_TRACKING_AUTOMATIC); $mform->setType($completionel, PARAM_INT); $mform->setDefault($completionel, COMPLETION_TRACKING_NONE); // Automatic completion once you view it. if ($supportviews) { $completionviewel = 'completionview' . $suffix; $mform->addElement( 'checkbox', $completionviewel, '', get_string('completionview_desc', 'completion') ); $mform->hideIf($completionviewel, $completionel, 'ne', COMPLETION_TRACKING_AUTOMATIC); // Check by default if automatic completion tracking is set. if ($trackingdefault == COMPLETION_TRACKING_AUTOMATIC) { $mform->setDefault($completionviewel, 1); } } // Automatic completion according to module-specific rules. $customcompletionelements = $this->add_completion_rules(); if (property_exists($this, '_customcompletionelements')) { $this->_customcompletionelements = $customcompletionelements; } if ($customcompletionelements !== null) { foreach ($customcompletionelements as $element) { $mform->hideIf($element, $completionel, 'ne', COMPLETION_TRACKING_AUTOMATIC); } } // If the activity supports grading, the grade elements must be added. if ($supportgrades) { $this->add_completiongrade_elements($modname, $rating); } $autocompletionpossible = $supportviews || $supportgrades || (count($customcompletionelements) > 0); // Automatic option only appears if possible. if ($autocompletionpossible) { $automatic = $mform->createElement( 'radio', $completionel, '', get_string('completion_automatic', 'completion'), COMPLETION_TRACKING_AUTOMATIC, ['class' => 'left-indented'] ); $mform->insertElementBefore($automatic, $conditionsgroupel); } // Completion expected at particular date? (For progress tracking). // We don't show completion expected at site level default completion. if ($courseid != $SITE->id) { $completionexpectedel = 'completionexpected' . $suffix; $mform->addElement('date_time_selector', $completionexpectedel, get_string('completionexpected', 'completion'), ['optional' => true]); $a = get_string('pluginname', $modname); $mform->addHelpButton($completionexpectedel, 'completionexpected', 'completion', '', false, $a); $mform->hideIf($completionexpectedel, $completionel, 'eq', COMPLETION_TRACKING_NONE); } } /** * Add completion grade elements to the form. * * @param string $modname The name of the module (for example, 'assign'). * @param bool $rating True if the rating feature is enabled and false otherwise. */ protected function add_completiongrade_elements( string $modname, bool $rating = false ): void { $mform = $this->get_form(); // Get the sufix to add to the completion elements name. $suffix = $this->get_suffix(); $completionel = 'completion' . $suffix; $completionelementexists = $mform->elementExists($completionel); $component = "mod_{$modname}"; $itemnames = component_gradeitems::get_itemname_mapping_for_component($component); $indentation = ['parentclass' => 'ms-2']; $receiveagradeel = 'receiveagrade' . $suffix; $completionusegradeel = 'completionusegrade' . $suffix; $completionpassgradeel = 'completionpassgrade' . $suffix; if (count($itemnames) === 1) { // Only one gradeitem in this activity. // We use the completionusegrade field here. $mform->addElement( 'checkbox', $completionusegradeel, '', get_string('completionusegrade_desc', 'completion') ); // Complete if the user has reached any grade. $mform->addElement( 'radio', $completionpassgradeel, null, get_string('completionanygrade_desc', 'completion'), 0, $indentation ); // Complete if the user has reached the pass grade. $mform->addElement( 'radio', $completionpassgradeel, null, get_string('completionpassgrade_desc', 'completion'), 1, $indentation ); $mform->hideIf($completionpassgradeel, $completionusegradeel, 'notchecked'); if ($completionelementexists) { $mform->hideIf($completionpassgradeel, $completionel, 'ne', COMPLETION_TRACKING_AUTOMATIC); $mform->hideIf($completionusegradeel, $completionel, 'ne', COMPLETION_TRACKING_AUTOMATIC); } // The disabledIf logic differs between ratings and other grade items due to different field types. if ($rating) { // If using the rating system, there is no grade unless ratings are enabled. $mform->hideIf($completionusegradeel, 'assessed', 'eq', 0); $mform->hideIf($completionusegradeel, 'assessed', 'eq', 0); } else { // All other field types use the '$gradefieldname' field's modgrade_type. $itemnumbers = array_keys($itemnames); $itemnumber = array_shift($itemnumbers); $gradefieldname = component_gradeitems::get_field_name_for_itemnumber($component, $itemnumber, 'grade'); $mform->hideIf($completionusegradeel, "{$gradefieldname}[modgrade_type]", 'eq', 'none'); $mform->hideIf($completionusegradeel, "{$gradefieldname}[modgrade_type]", 'eq', 'none'); } } else if (count($itemnames) > 1) { // There are multiple grade items in this activity. // Show them all. $options = []; foreach ($itemnames as $itemnumber => $itemname) { $options[$itemnumber] = get_string("grade_{$itemname}_name", $component); } $group = [$mform->createElement( 'checkbox', $completionusegradeel, null, get_string('completionusegrade_desc', 'completion') )]; $completiongradeitemnumberel = 'completiongradeitemnumber' . $suffix; $group[] =& $mform->createElement( 'select', $completiongradeitemnumberel, '', $options ); $receiveagradegroupel = 'receiveagradegroup' . $suffix; $mform->addGroup($group, $receiveagradegroupel, '', [' '], false); if ($completionelementexists) { $mform->hideIf($completionusegradeel, $completionel, 'ne', COMPLETION_TRACKING_AUTOMATIC); $mform->hideIf($receiveagradegroupel, $completionel, 'ne', COMPLETION_TRACKING_AUTOMATIC); } $mform->hideIf($completiongradeitemnumberel, $completionusegradeel, 'notchecked'); // Complete if the user has reached any grade. $mform->addElement( 'radio', $completionpassgradeel, null, get_string('completionanygrade_desc', 'completion'), 0, $indentation ); // Complete if the user has reached the pass grade. $mform->addElement( 'radio', $completionpassgradeel, null, get_string('completionpassgrade_desc', 'completion'), 1, $indentation ); $mform->hideIf($completionpassgradeel, $completionusegradeel, 'notchecked'); if ($completionelementexists) { $mform->hideIf($completiongradeitemnumberel, $completionel, 'ne', COMPLETION_TRACKING_AUTOMATIC); $mform->hideIf($completionpassgradeel, $completionel, 'ne', COMPLETION_TRACKING_AUTOMATIC); } } $customgradingelements = $this->add_completiongrade_rules(); if (property_exists($this, '_customcompletionelements')) { $this->_customcompletionelements = array_merge($this->_customcompletionelements, $customgradingelements); } if ($completionelementexists) { foreach ($customgradingelements as $customgradingelement) { $mform->hideIf($customgradingelement, $completionel, 'ne', COMPLETION_TRACKING_AUTOMATIC); } } } /** * Add completion grading elements to the form and return the list of element ids. * * @return array Array of string IDs of added items, empty array if none */ abstract public function add_completiongrade_rules(): array; /** * Perform some extra validation for completion settings. * * @param array $data Array of ["fieldname" => value] of submitted data. * @return array List of ["element_name" => "error_description"] if there are errors or an empty array if everything is OK. */ protected function validate_completion(array $data): array { $errors = []; // Get the sufix to add to the completion elements name. $suffix = $this->get_suffix(); $completionel = 'completion' . $suffix; // Completion: Don't let them choose automatic completion without turning on some conditions. $automaticcompletion = array_key_exists($completionel, $data) && $data[$completionel] == COMPLETION_TRACKING_AUTOMATIC; // Ignore this check when completion settings are locked, as the options are then disabled. // The unlock buttons don't need suffix because they are only displayed in the module settings page. $automaticcompletion = $automaticcompletion && !empty($data['completionunlocked']); if ($automaticcompletion) { // View to complete. $completionviewel = 'completionview' . $suffix; $rulesenabled = !empty($data[$completionviewel]); // Use grade to complete (only one grade item). $completionusegradeel = 'completionusegrade' . $suffix; $completionpassgradeel = 'completionpassgrade' . $suffix; $rulesenabled = $rulesenabled || !empty($data[$completionusegradeel]) || !empty($data[$completionpassgradeel]); // Use grade to complete (specific grade item). $completiongradeitemnumberel = 'completiongradeitemnumber' . $suffix; if (!$rulesenabled && isset($data[$completiongradeitemnumberel])) { $rulesenabled = $data[$completiongradeitemnumberel] != ''; } // Module-specific completion rules. $rulesenabled = $rulesenabled || $this->completion_rule_enabled($data); if (!$rulesenabled) { // No rules are enabled. Can't set automatically completed without rules. $errors[$completionel] = get_string('badautocompletion', 'completion'); } } return $errors; } /** * It should be called from the definition_after_data() to setup the completion settings in the form. * * @param cm_info|null $cm The course module associated to this form. */ protected function definition_after_data_completion(?cm_info $cm = null): void { global $COURSE, $SITE; $mform = $this->get_form(); $completion = new \completion_info($COURSE); // We use $SITE course for site default activity completion, // so users could set default values regardless of whether completion is enabled or not.". if ($completion->is_enabled() || $COURSE->id == $SITE->id) { $suffix = $this->get_suffix(); // If anybody has completed the activity, these options will be 'locked'. // We use $SITE course for site default activity completion, so we don't need any unlock button. $completedcount = (empty($cm) || $COURSE->id == $SITE->id) ? 0 : $completion->count_user_data($cm); $freeze = false; if (!$completedcount) { // The unlock buttons don't need suffix because they are only displayed in the module settings page. if ($mform->elementExists('unlockcompletion')) { $mform->removeElement('unlockcompletion'); } // Automatically set to unlocked. Note: this is necessary in order to make it recalculate completion once // the option is changed, maybe someone has completed it now. if ($mform->elementExists('completionunlocked')) { $mform->getElement('completionunlocked')->setValue(1); } } else { // Has the element been unlocked, either by the button being pressed in this request, or the field already // being set from a previous one? if ($mform->exportValue('unlockcompletion') || $mform->exportValue('completionunlocked')) { // Yes, add in warning text and set the hidden variable. $completedunlockedel = $mform->createElement( 'static', 'completedunlocked', get_string('completedunlocked', 'completion'), get_string('completedunlockedtext', 'completion') ); $mform->insertElementBefore($completedunlockedel, 'unlockcompletion'); $mform->removeElement('unlockcompletion'); $mform->getElement('completionunlocked')->setValue(1); } else { // No, add in the warning text with the count (now we know it) before the unlock button. $completedwarningel = $mform->createElement( 'static', 'completedwarning', get_string('completedwarning', 'completion'), get_string('completedwarningtext', 'completion', $completedcount) ); $mform->insertElementBefore($completedwarningel, 'unlockcompletion'); $freeze = true; } } if ($freeze) { $completionel = 'completion' . $suffix; $mform->freeze($completionel); $completionviewel = 'completionview' . $suffix; if ($mform->elementExists($completionviewel)) { // Don't use hardFreeze or checkbox value gets lost. $mform->freeze($completionviewel); } $completionusegradeel = 'completionusegrade' . $suffix; if ($mform->elementExists($completionusegradeel)) { $mform->freeze($completionusegradeel); } $completionpassgradeel = 'completionpassgrade' . $suffix; if ($mform->elementExists($completionpassgradeel)) { $mform->freeze($completionpassgradeel); // Has the completion pass grade completion criteria been set? If it has, then we shouldn't change // any of the modules "gradepass" type fields. if ($mform->exportValue($completionpassgradeel)) { // Some modules define separate "gradepass" fields for each of their grade items. $gradepassfieldels = array_merge(['gradepass'], array_map( fn(string $gradeitem) => "{$gradeitem}gradepass", component_gradeitems::get_itemname_mapping_for_component("mod_{$this->_modname}"), )); foreach ($gradepassfieldels as $gradepassfieldel) { if ($mform->elementExists($gradepassfieldel)) { $mform->freeze($gradepassfieldel); } } } } $completiongradeitemnumberel = 'completiongradeitemnumber' . $suffix; if ($mform->elementExists($completiongradeitemnumberel)) { $mform->freeze($completiongradeitemnumberel); } if (property_exists($this, '_customcompletionelements')) { $mform->freeze($this->_customcompletionelements); } } } } } PK6\0c tags_form.phpnu[. namespace core_course\form; defined('MOODLE_INTERNAL') || die(); require_once($CFG->libdir . '/formslib.php'); /** * Edit course tags form. * * @package core_course * @copyright Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class tags_form extends \moodleform { #[\Override] public function definition(): void { $mform = $this->_form; $mform->addElement( 'tags', 'tags', get_string('tags'), [ 'itemtype' => 'course', 'component' => 'core', ], ); $mform->addElement('hidden', 'id', null); $mform->setType('id', PARAM_INT); $this->add_action_buttons(); } } PKG\J-%% badge.phpnu[. namespace core_badges\form; defined('MOODLE_INTERNAL') || die(); require_once($CFG->libdir . '/formslib.php'); require_once($CFG->libdir . '/badgeslib.php'); require_once($CFG->libdir . '/filelib.php'); use moodleform; /** * Form classes for editing badges * * @package core_badges * @copyright 2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @author Yuliya Bozhko */ class badge extends moodleform { /** * Defines the form */ public function definition() { global $CFG, $SITE; $mform = $this->_form; $badge = (isset($this->_customdata['badge'])) ? $this->_customdata['badge'] : false; $action = $this->_customdata['action']; if (array_key_exists('courseid', $this->_customdata)) { $courseid = $this->_customdata['courseid']; } else if (array_key_exists('badge', $this->_customdata)) { $courseid = $this->_customdata['badge']->courseid; } if (!empty($courseid)) { $mform->addElement('hidden', 'courseid', $courseid); $mform->setType('courseid', PARAM_INT); } $mform->addElement('header', 'badgedetails', get_string('badgedetails', 'badges')); $mform->addElement('text', 'name', get_string('name'), ['size' => '70']); // When downloading badge, it will be necessary to clean the name as PARAM_FILE. $mform->setType('name', PARAM_TEXT); $mform->addRule('name', null, 'required'); $mform->addRule('name', get_string('maximumchars', '', 255), 'maxlength', 255, 'client'); $mform->addElement('text', 'version', get_string('version', 'badges'), ['size' => '70']); $mform->setType('version', PARAM_TEXT); $languages = get_string_manager()->get_list_of_languages(); $mform->addElement('select', 'language', get_string('language'), $languages); $mform->addElement( 'textarea', 'description', get_string('description', 'badges'), 'wrap="virtual" rows="8" cols="70" placeholder="' . s(get_string('descriptioninfo', 'badges')) . '"', ); $mform->setType('description', PARAM_NOTAGS); $mform->addRule('description', null, 'required'); $str = $action == 'new' ? get_string('badgeimage', 'badges') : get_string('newimage', 'badges'); $imageoptions = ['maxbytes' => 262144, 'accepted_types' => ['optimised_image']]; $mform->addElement('filepicker', 'image', $str, null, $imageoptions); if ($action == 'new') { $mform->addRule('image', null, 'required'); } else { $currentimage = $mform->createElement('static', 'currentimage', get_string('currentimage', 'badges')); $mform->insertElementBefore($currentimage, 'image'); } $mform->addElement('static', 'imageinfo', null, get_string('badgeimageinfo', 'badges')); $mform->addElement('text', 'imagecaption', get_string('imagecaption', 'badges'), ['size' => '70']); $mform->setType('imagecaption', PARAM_TEXT); $mform->addElement('tags', 'tags', get_string('tags', 'badges'), ['itemtype' => 'badge', 'component' => 'core_badges']); $mform->addElement('header', 'issuerdetails', get_string('issuerdetails', 'badges')); $mform->addElement('text', 'issuername', get_string('issuername', 'badges'), ['size' => '70']); $mform->setType('issuername', PARAM_NOTAGS); $mform->addRule('issuername', null, 'required'); $site = get_site(); $issuername = $CFG->badges_defaultissuername ?: $site->fullname; $mform->setDefault('issuername', $issuername); $mform->addElement('text', 'issuercontact', get_string('contact', 'badges'), ['size' => '70']); if (isset($CFG->badges_defaultissuercontact)) { $mform->setDefault('issuercontact', $CFG->badges_defaultissuercontact); } $mform->setType('issuercontact', PARAM_RAW); $mform->addRule('issuercontact', null, 'email'); // Set issuer URL. // Have to parse URL because badge issuer origin cannot be a subfolder in wwwroot. $url = parse_url($CFG->wwwroot); $mform->addElement('hidden', 'issuerurl', $url['scheme'] . '://' . $url['host']); $mform->setType('issuerurl', PARAM_URL); $mform->addElement('header', 'issuancedetails', get_string('issuancedetails', 'badges')); $issuancedetails = []; $issuancedetails[] = $mform->createElement('radio', 'expiry', '', get_string('never', 'badges'), 0); $issuancedetails[] = $mform->createElement('static', 'none_break', null, '
'); $issuancedetails[] = $mform->createElement('radio', 'expiry', '', get_string('fixed', 'badges'), 1); $issuancedetails[] = $mform->createElement('date_selector', 'expiredate', ''); $issuancedetails[] = $mform->createElement('static', 'expirydate_break', null, '
'); $issuancedetails[] = $mform->createElement('radio', 'expiry', '', get_string('relative', 'badges'), 2); $issuancedetails[] = $mform->createElement('duration', 'expireperiod', '', ['defaultunit' => 86400, 'optional' => false]); $issuancedetails[] = $mform->createElement('static', 'expiryperiods_break', null, get_string('after', 'badges')); $mform->addGroup($issuancedetails, 'expirydategr', get_string('expirydate', 'badges'), [' '], false); $mform->setDefault('expiry', 0); $mform->setDefault('expiredate', strtotime('+1 year')); $mform->disabledIf('expiredate[day]', 'expiry', 'neq', 1); $mform->disabledIf('expiredate[month]', 'expiry', 'neq', 1); $mform->disabledIf('expiredate[year]', 'expiry', 'neq', 1); $mform->disabledIf('expireperiod[number]', 'expiry', 'neq', 2); $mform->disabledIf('expireperiod[timeunit]', 'expiry', 'neq', 2); $mform->addElement('hidden', 'action', $action); $mform->setType('action', PARAM_TEXT); if ($action == 'new') { // Try to set default badge language to that of current language, or it's parent. $language = current_language(); if (isset($languages[$language])) { $defaultlanguage = $language; } else { // Calling get_parent_language returns an empty string instead of 'en'. $defaultlanguage = get_parent_language($language) ?: 'en'; } $mform->setDefault('language', $defaultlanguage); $this->add_action_buttons(true, get_string('createbutton', 'badges')); } else { // Add hidden fields. $mform->addElement('hidden', 'id', $badge->id); $mform->setType('id', PARAM_INT); $this->add_action_buttons(); $this->set_data($badge); // Freeze all elements if badge is active or locked. if ($badge->is_active() || $badge->is_locked()) { $mform->hardFreezeAllVisibleExcept([]); } } } /** * Load in existing data as form defaults * * @param \core_badges\badge $badge object or array of default values */ public function set_data($badge) { $defaultvalues = []; parent::set_data((object) $badge); if (!empty($badge->expiredate)) { $defaultvalues['expiry'] = 1; $defaultvalues['expiredate'] = $badge->expiredate; } else if (!empty($badge->expireperiod)) { $defaultvalues['expiry'] = 2; $defaultvalues['expireperiod'] = $badge->expireperiod; } if (!empty($badge->name)) { $defaultvalues['name'] = trim($badge->name); } $defaultvalues['tags'] = \core_tag_tag::get_item_tags_array('core_badges', 'badge', $badge->id); $defaultvalues['currentimage'] = print_badge_image($badge, $badge->get_context(), 'large'); parent::set_data($defaultvalues); } /** * Validates form data */ public function validation($data, $files) { global $DB, $SITE; // Trim badge name (to guarantee no badges are created with the same name but some extra spaces). $data['name'] = trim($data['name']); $errors = parent::validation($data, $files); if ($data['expiry'] == 2 && $data['expireperiod'] <= 0) { $errors['expirydategr'] = get_string('error:invalidexpireperiod', 'badges'); } if ($data['expiry'] == 1 && $data['expiredate'] <= time()) { $errors['expirydategr'] = get_string('error:invalidexpiredate', 'badges'); } return $errors; } } PKG\#preferences.phpnu[. namespace core_badges\form; use moodleform; defined('MOODLE_INTERNAL') || die(); require_once($CFG->libdir . '/formslib.php'); /** * Form class for editing badges preferences. * * @package core_badges * @copyright 2018 Tung Thai * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @author Yuliya Bozhko * @author Tung Thai */ class preferences extends moodleform { /** * Defines the form. */ public function definition() { global $USER, $CFG; $mform =& $this->_form; $mform->addElement('header', 'badgeprivacy', get_string('badgeprivacysetting', 'badges')); $mform->addElement('advcheckbox', 'badgeprivacysetting', '', get_string('badgeprivacysetting_str', 'badges')); $mform->setType('badgeprivacysetting', PARAM_INT); $mform->setDefault('badgeprivacysetting', 1); $mform->addHelpButton('badgeprivacy', 'badgeprivacysetting', 'badges'); $this->add_action_buttons(); } } PKG\ LRR backpack.phpnu[. /** * Form class for mybackpack.php * * @package core * @subpackage badges * @copyright 2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @author Yuliya Bozhko */ namespace core_badges\form; defined('MOODLE_INTERNAL') || die(); require_once($CFG->libdir . '/formslib.php'); require_once($CFG->libdir . '/badgeslib.php'); use html_writer; use moodleform; use stdClass; /** * Form to edit backpack initial details. * */ class backpack extends external_backpack { /** * Defines the form */ public function definition() { global $USER, $PAGE, $OUTPUT, $CFG; $mform = $this->_form; $this->_customdata['userbackpack'] = 1; $mform->addElement('html', html_writer::tag('span', '', array('class' => 'notconnected', 'id' => 'connection-error'))); $mform->addElement('header', 'backpackheader', get_string('backpackconnection', 'badges')); $mform->addHelpButton('backpackheader', 'backpackconnection', 'badges'); $mform->addElement('hidden', 'userid', $USER->id); $mform->setType('userid', PARAM_INT); $freeze = []; $status = null; if (isset($this->_customdata['email'])) { // Email will be passed in when we're in the process of verifying the user's email address, // so set the connection status, lock the email field, and provide options to resend the verification // email or cancel the verification process entirely and start over. $freeze = ['backpackemail']; $mform->addElement('hidden', 'password', $this->_customdata['backpackpassword']); $mform->setType('password', PARAM_RAW); $mform->addElement('hidden', 'externalbackpackid', $this->_customdata['backpackid']); $mform->setType('externalbackpackid', PARAM_INT); $status = html_writer::tag('span', get_string('backpackemailverificationpending', 'badges'), array('class' => 'notconnected', 'id' => 'connection-status')); } else { $sitebackpacks = badges_get_site_backpacks(); $choices = [ '' => get_string('choosedots'), ]; $restrictedoptions = ['']; foreach ($sitebackpacks as $backpack) { $choices[$backpack->id] = $backpack->backpackweburl; if ($backpack->apiversion == OPEN_BADGES_V2P1) { $restrictedoptions[] = $backpack->id; } } $mform->addElement('select', 'externalbackpackid', get_string('backpackprovider', 'badges'), $choices); $mform->setType('externalbackpackid', PARAM_INT); $mform->addRule('externalbackpackid', get_string('required'), 'required'); $mform->hideIf('password', 'externalbackpackid', 'in', $restrictedoptions); $mform->hideIf('backpackemail', 'externalbackpackid', 'in', $restrictedoptions); // Static form element can't be used because they don't support hideIf. This is a workaround until MDL-66251 is fixed. $group = []; $group[] = $mform->createElement('static', 'loginbackpackgroup', '', get_string('loginbackpacktitle', 'badges')); $mform->addGroup($group, 'loginbackpackgroup', '', '', false); $mform->hideIf('loginbackpackgroup', 'externalbackpackid', 'in', $restrictedoptions); } if ($status) { // Only display the status if it's set. $mform->addElement('static', 'status', get_string('status'), $status); } $this->add_auth_fields($this->_customdata['email'] ?? $USER->email, !isset($this->_customdata['email'])); // Only display email and password when the user has selected a backpack. $mform->hideIf('backpackemail', 'externalbackpackid', 'eq', ''); $mform->hideIf('password', 'externalbackpackid', 'eq', ''); $mform->setDisableShortforms(false); // Freeze any elemnts after definition. if ($freeze) { $mform->freeze($freeze); } $this->add_action_buttons(); } /** * Override add_action_buttons * * @param bool $cancel * @param null|text $submitlabel */ public function add_action_buttons($cancel = true, $submitlabel = null) { $mform = $this->_form; if (isset($this->_customdata['email'])) { $buttonarray = []; $buttonarray[] = &$mform->createElement('submit', 'submitbutton', get_string('backpackconnectionresendemail', 'badges')); $buttonarray[] = &$mform->createElement('submit', 'revertbutton', get_string('backpackconnectioncancelattempt', 'badges')); $mform->addGroup($buttonarray, 'buttonar', '', [''], false); $mform->closeHeaderBefore('buttonar'); } else { // Email isn't present, so provide an input element to get it and a button to start the verification process. parent::add_action_buttons(false, get_string('backpackconnectionconnect', 'badges')); } } /** * Validates form data */ public function validation($data, $files) { // Verify that the user has selected a backpack. if (empty($data['externalbackpackid'])) { $errors['externalbackpackid'] = get_string('externalbackpack_required', 'badges'); return $errors; } // We don't need to verify anything for OBv2.1. if (badges_open_badges_backpack_api() == OPEN_BADGES_V2P1) { return []; } // We don't need to verify the email address if we're clearing a pending email verification attempt. if (isset($data['revertbutton'])) { return []; } $errors = []; // Email and password can't be blank. if (empty($data['backpackemail'])) { $errors['backpackemail'] = get_string('backpackemail_required', 'badges'); } if (empty($data['password'])) { $errors['password'] = get_string('password_required', 'badges'); } if (!empty($errors)) { return $errors; } // Check the given credentials (email and password) are valid for this backpack. $check = new stdClass(); $check->email = $data['backpackemail']; $check->password = $data['password']; $sitebackpack = badges_get_site_backpack($data['externalbackpackid']); $bp = new \core_badges\backpack_api($sitebackpack, $check); $result = $bp->authenticate(); if ($result === false || !empty($result->error)) { $msg = $bp->get_authentication_error(); $errors['backpackemail'] = get_string('backpackconnectionunexpectedresult', 'badges', $msg); } return $errors; } } PKG\Webb message.phpnu[. /** * Form class for badge message. * * @package core * @subpackage badges * @copyright 2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @author Yuliya Bozhko */ namespace core_badges\form; defined('MOODLE_INTERNAL') || die(); require_once($CFG->libdir . '/formslib.php'); require_once($CFG->libdir . '/badgeslib.php'); require_once($CFG->libdir . '/filelib.php'); use moodleform; /** * Form to edit badge message. * * @copyright 2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class message extends moodleform { /** * Create the form. */ public function definition() { global $CFG, $OUTPUT; $mform = $this->_form; $badge = $this->_customdata['badge']; $action = $this->_customdata['action']; $editoroptions = $this->_customdata['editoroptions']; // Add hidden fields. $mform->addElement('hidden', 'id', $badge->id); $mform->setType('id', PARAM_INT); $mform->addElement('hidden', 'action', $action); $mform->setType('action', PARAM_TEXT); $mform->addElement('header', 'badgemessage', get_string('configuremessage', 'badges')); $mform->addHelpButton('badgemessage', 'variablesubstitution', 'badges'); $mform->addElement('text', 'messagesubject', get_string('subject', 'badges'), array('size' => '70')); $mform->setType('messagesubject', PARAM_TEXT); $mform->addRule('messagesubject', null, 'required'); $mform->addRule('messagesubject', get_string('maximumchars', '', 255), 'maxlength', 255); $mform->addElement('editor', 'message_editor', get_string('message', 'badges'), null, $editoroptions); $mform->setType('message_editor', PARAM_RAW); $mform->addRule('message_editor', null, 'required'); $mform->addElement('advcheckbox', 'attachment', get_string('attachment', 'badges'), '', null, array(0, 1)); $mform->addHelpButton('attachment', 'attachment', 'badges'); if (empty($CFG->allowattachments)) { $mform->freeze('attachment'); } $options = array( BADGE_MESSAGE_NEVER => get_string('never'), BADGE_MESSAGE_ALWAYS => get_string('notifyevery', 'badges'), BADGE_MESSAGE_DAILY => get_string('notifydaily', 'badges'), BADGE_MESSAGE_WEEKLY => get_string('notifyweekly', 'badges'), BADGE_MESSAGE_MONTHLY => get_string('notifymonthly', 'badges'), ); $mform->addElement('select', 'notification', get_string('notification', 'badges'), $options); $mform->addHelpButton('notification', 'notification', 'badges'); $this->add_action_buttons(); $this->set_data($badge); } } PKG\П44collections.phpnu[. namespace core_badges\form; defined('MOODLE_INTERNAL') || die(); require_once($CFG->libdir . '/formslib.php'); require_once($CFG->libdir . '/badgeslib.php'); use html_writer; use moodleform; /** * Form to select backpack collections. * * @package core_badges * @copyright 2012 onwards Totara Learning Solutions Ltd {@link http://www.totaralms.com/} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @author Yuliya Bozhko */ class collections extends moodleform { /** * Defines the form */ public function definition() { global $USER; $mform = $this->_form; $email = $this->_customdata['email']; $backpackweburl = $this->_customdata['backpackweburl']; $selected = $this->_customdata['selected']; if (isset($this->_customdata['groups'])) { $groups = $this->_customdata['groups']; $nogroups = null; } else { $groups = null; $nogroups = $this->_customdata['nogroups']; } $backpack = get_backpack_settings($USER->id); $sitebackpack = badges_get_site_backpack($backpack->backpackid); $mform->addElement('header', 'backpackheader', get_string('backpackconnection_connected', 'badges')); $mform->addElement('static', 'url', get_string('url'), $backpackweburl); $status = html_writer::tag('span', get_string('connected', 'badges'), array('class' => 'connected')); $mform->addElement('static', 'status', get_string('status'), $status); $mform->addElement('static', 'email', get_string('email'), $email); $mform->addElement('submit', 'disconnect', get_string('disconnect', 'badges')); $mform->addElement('header', 'collectionheader', get_string('backpackimport', 'badges')); $mform->addHelpButton('collectionheader', 'backpackimport', 'badges'); $hasgroups = false; if (!empty($groups)) { foreach ($groups as $group) { $count = 0; // Handle attributes based on backpack's supported version. if (empty($group->published)) { // Only public collections. continue; } // Get the number of badges associated with this collection from the assertions array returned. $count = count($group->assertions); if (!$hasgroups) { $mform->addElement('static', 'selectgroup', '', get_string('selectgroup_start', 'badges')); } $hasgroups = true; $name = $group->name . ' (' . $count . ')'; $mform->addElement( 'advcheckbox', 'group[' . $group->entityId . ']', null, $name, array('group' => 1), array(false, $group->entityId) ); if (in_array($group->entityId, $selected)) { $mform->setDefault('group[' . $group->entityId . ']', $group->entityId); } } $mform->addElement('static', 'selectgroup', '', get_string('selectgroup_end', 'badges', $backpackweburl)); } if (!$hasgroups) { $mform->addElement('static', 'selectgroup', '', $nogroups); } $this->add_action_buttons(); } } PKG\i alignment.phpnu[. namespace core_badges\form; use moodleform; use stdClass; defined('MOODLE_INTERNAL') || die(); require_once($CFG->libdir . '/formslib.php'); require_once($CFG->libdir . '/badgeslib.php'); /** * Form to edit alignment. * * @package core_badges * @copyright 2018 Tung Thai * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @author Tung Thai */ class alignment extends moodleform { /** * Defines the form. */ public function definition() { global $DB; $mform = $this->_form; $badge = $this->_customdata['badge']; $action = $this->_customdata['action']; $alignmentid = $this->_customdata['alignmentid']; $mform->addElement('header', 'alignment', get_string('alignment', 'badges')); $mform->addElement('text', 'targetname', get_string('targetname', 'badges'), ['size' => '70']); $mform->setType('targetname', PARAM_TEXT); $mform->addRule('targetname', get_string('maximumchars', '', 255), 'maxlength', 255, 'client'); $mform->addRule('targetname', null, 'required'); $mform->addHelpButton('targetname', 'targetname', 'badges'); $mform->addElement('text', 'targeturl', get_string('targeturl', 'badges'), ['size' => '70']); $mform->setType('targeturl', PARAM_URL); $mform->addRule('targeturl', get_string('maximumchars', '', 255), 'maxlength', 255, 'client'); $mform->addRule('targeturl', null, 'required'); $mform->addHelpButton('targeturl', 'targeturl', 'badges'); $mform->addElement('text', 'targetframework', get_string('targetframework', 'badges'), ['size' => '70']); $mform->setType('targetframework', PARAM_TEXT); $mform->addRule('targetframework', get_string('maximumchars', '', 255), 'maxlength', 255, 'client'); $mform->addHelpButton('targetframework', 'targetframework', 'badges'); $mform->addElement('text', 'targetcode', get_string('targetcode', 'badges'), ['size' => '70']); $mform->setType('targetcode', PARAM_TEXT); $mform->addRule('targetcode', get_string('maximumchars', '', 255), 'maxlength', 255, 'client'); $mform->addHelpButton('targetcode', 'targetcode', 'badges'); $mform->addElement('textarea', 'targetdescription', get_string('targetdescription', 'badges'), 'wrap="virtual" rows="8" cols="70"'); $this->add_action_buttons(); if ($action == 'edit' || $alignmentid) { $alignment = new stdClass(); $alignment = $DB->get_record_select('badge_alignment', 'id = ?', [$alignmentid]); $this->set_data($alignment); // Freeze all elements if badge is active or locked. if ($badge->is_active() || $badge->is_locked()) { $mform->hardFreezeAllVisibleExcept([]); } } } /** * Validate the data from the form. * * @param array $data form data * @param array $files form files * @return array An array of error messages. */ public function validation($data, $files) { $errors = parent::validation($data, $files); if (!empty($data['targeturl']) && !preg_match('@^https?://.+@', $data['targeturl'])) { $errors['targeturl'] = get_string('invalidurl', 'badges'); } return $errors; } } PKG\Gc related.phpnu[. namespace core_badges\form; use moodleform; defined('MOODLE_INTERNAL') || die(); require_once($CFG->libdir . '/formslib.php'); require_once($CFG->libdir . '/badgeslib.php'); /** * Form to edit badge details. * * @package core_badges * @copyright 2018 Tung Thai * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @author Tung Thai */ class related extends moodleform { /** * Defines the form. */ public function definition() { $mform = $this->_form; $badge = $this->_customdata['badge']; $badgesarray = $this->get_badges_option($badge); $mform->addElement('header', 'alignment', get_string('relatedbages', 'badges')); if (!$badgesarray) { $badgesarray = [get_string('none')]; $attributes = ['size' => '3', 'disabled' => true, 'style' => 'min-width: 200px']; } else { $attributes = ['size' => '10']; } $mform->addElement('select', 'relatedbadgeids', get_string('relatedbages', 'badges'), $badgesarray, $attributes); $mform->getElement('relatedbadgeids')->setMultiple(true); $this->add_action_buttons(); // Freeze all elements if badge is active or locked. if ($badge->is_active() || $badge->is_locked()) { $mform->hardFreezeAllVisibleExcept([]); } } /** * Validates form data. * * @param array $data submitted data. * @param array $files submitted files. * @return array $errors An array of errors. */ public function validation($data, $files) { $errors = parent::validation($data, $files); return $errors; } /** * Return list badge of a course or list badge site. * * @param object $badge Badge object. * @return array $options An array the badges. */ public function get_badges_option($badge) { global $DB; $sql = "SELECT b.id, b.name, b.version, b.language, b.type FROM {badge} b WHERE b.id <> :badgeid AND b.id NOT IN ( SELECT DISTINCT b.id FROM {badge_related} br JOIN {badge} b ON (br.relatedbadgeid = b.id OR br.badgeid = b.id) WHERE (br.badgeid = :badgeid2 OR br.relatedbadgeid = :badgeid3) AND b.id != :badgeid4)"; $params = ['badgeid' => $badge->id, 'badgeid2' => $badge->id, 'badgeid3' => $badge->id, 'badgeid4' => $badge->id]; if ($badge->type == BADGE_TYPE_COURSE) { $sql .= " AND (b.courseid = :courseid OR b.type = :badgetype)"; $params['courseid'] = $badge->courseid; $params['badgetype'] = BADGE_TYPE_SITE; } $records = $DB->get_records_sql($sql, $params); $languages = get_string_manager()->get_list_of_languages(); $options = []; foreach ($records as $record) { $language = isset($languages[$record->language]) ? $languages[$record->language] : ''; $options[$record->id] = $record->name . ' (version: ' . $record->version . ', language: ' . $language . ', ' . ($record->type == BADGE_TYPE_COURSE ? get_string('badgesview', 'badges') : get_string('sitebadges', 'badges')) . ')'; } return $options; } } PKG\ endorsement.phpnu[. namespace core_badges\form; use moodleform; use stdClass; defined('MOODLE_INTERNAL') || die(); require_once($CFG->libdir . '/formslib.php'); require_once($CFG->libdir . '/badgeslib.php'); /** * Form endorsement for editing. * * @package core_badges * @copyright 2018 Tung Thai * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @author Tung Thai */ class endorsement extends moodleform { /** * Defines the form. */ public function definition() { $mform = $this->_form; $badge = $this->_customdata['badge']; $mform->addElement('header', 'endorsement', get_string('issuerdetails', 'badges')); $mform->addElement('text', 'issuername', get_string('issuername_endorsement', 'badges'), ['size' => '70']); $mform->setType('issuername', PARAM_TEXT); $mform->addRule('issuername', null, 'required'); $mform->addHelpButton('issuername', 'issuername_endorsement', 'badges'); $mform->addElement('text', 'issueremail', get_string('issueremail', 'badges'), ['size' => '70']); $mform->addRule('issueremail', null, 'required'); $mform->setType('issueremail', PARAM_RAW); $mform->addHelpButton('issueremail', 'issueremail', 'badges'); $mform->addElement('text', 'issuerurl', get_string('issuerurl', 'badges'), ['size' => '70']); $mform->setType('issuerurl', PARAM_URL); $mform->addRule('issuerurl', null, 'required'); $mform->addHelpButton('issuerurl', 'issuerurl', 'badges'); $mform->addElement('date_time_selector', 'dateissued', get_string('dateawarded', 'badges')); $mform->addElement('header', 'claim', get_string('claim', 'badges')); $mform->addElement('text', 'claimid', get_string('claimid', 'badges'), ['size' => '70']); $mform->setType('claimid', PARAM_URL); $mform->addRule('claimid', null, 'required'); $mform->addElement('textarea', 'claimcomment', get_string('claimcomment', 'badges'), 'wrap="virtual" rows="8" cols="70"'); $mform->setType('claimcomment', PARAM_NOTAGS); $endorsement = new stdClass(); $endorsement = $badge->get_endorsement(); if ($endorsement) { $mform->setDefault('dateissued', $endorsement->dateissued); $this->set_data($endorsement); } $this->add_action_buttons(); // Freeze all elements if badge is active or locked. if ($badge->is_active() || $badge->is_locked()) { $mform->hardFreezeAllVisibleExcept([]); } } /** * Validates form data. * * @param array $data submitted data. * @param array $files submitted files. * @return array $errors An array of errors. */ public function validation($data, $files) { $errors = parent::validation($data, $files); if ($data['issueremail'] && !validate_email($data['issueremail'])) { $errors['issueremail'] = get_string('invalidemail'); } if ($data['issuerurl'] && !preg_match('@^https?://.+@', $data['issuerurl'])) { $errors['issuerurl'] = get_string('invalidurl', 'badges'); } if ($data['claimid'] && !preg_match('@^https?://.+@', $data['claimid'])) { $errors['claimid'] = get_string('invalidurl', 'badges'); } return $errors; } } PKG\&}[LLexternal_backpack.phpnu[. namespace core_badges\form; use core_badges\backpack_api; defined('MOODLE_INTERNAL') || die(); require_once($CFG->libdir.'/formslib.php'); /** * Backpack form class. * * @package core_badges * @copyright 2019 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class external_backpack extends \moodleform { /** * Create the form. * */ public function definition() { global $CFG; $mform = $this->_form; $backpack = $this->_customdata['externalbackpack'] ?? null; $mform->addElement('hidden', 'action', 'edit'); $mform->setType('action', PARAM_ALPHA); $apiversions = badges_get_badge_api_versions(); $mform->addElement('select', 'apiversion', get_string('apiversion', 'core_badges'), $apiversions); $mform->setType('apiversion', PARAM_RAW); $mform->setDefault('apiversion', OPEN_BADGES_V2P1); $mform->addRule('apiversion', null, 'required', null, 'client'); $this->add_provider_fields(); $mform->addElement('text', 'backpackweburl', get_string('backpackweburl', 'core_badges')); $mform->setType('backpackweburl', PARAM_URL); $mform->addRule('backpackweburl', get_string('maximumchars', '', 255), 'maxlength', 255, 'client'); $mform->hideIf('backpackweburl', 'apiversion', 'ne', OPEN_BADGES_V2); $mform->addElement('text', 'backpackapiurl', get_string('backpackapiurl', 'core_badges')); $mform->setType('backpackapiurl', PARAM_URL); $mform->addRule('backpackapiurl', get_string('maximumchars', '', 255), 'maxlength', 255, 'client'); $mform->hideIf('backpackapiurl', 'apiversion', 'ne', OPEN_BADGES_V2); $mform->addElement('text', 'backpackweburlv2p1', get_string('backpackweburl', 'core_badges')); $mform->setType('backpackweburlv2p1', PARAM_URL); $mform->addRule('backpackweburlv2p1', get_string('maximumchars', '', 255), 'maxlength', 255, 'client'); $mform->hideIf('backpackweburlv2p1', 'apiversion', 'ne', (string) OPEN_BADGES_V2P1); $mform->addElement('hidden', 'id', ($backpack->id ?? null)); $mform->setType('id', PARAM_INT); $mform->addElement('hidden', 'badgebackpack', 0); $mform->setType('badgebackpack', PARAM_INT); $mform->addElement('hidden', 'userid', 0); $mform->setType('userid', PARAM_INT); $mform->addElement('hidden', 'backpackuid', 0); $mform->setType('backpackuid', PARAM_INT); // Add rules for backpack URL fields. if (backpack_api::display_canvas_credentials_fields()) { $mform->hideIf('backpackweburl', 'provider', 'ne', backpack_api::PROVIDER_OTHER); $mform->hideIf('backpackapiurl', 'provider', 'ne', backpack_api::PROVIDER_OTHER); } $issueremail = $CFG->badges_defaultissuercontact; // Connect to a Canvas Credentials provider. $this->add_connect_issuer_canvas_fields($issueremail); // Connect to another provider. $this->add_connect_issuer_fields($backpack, $issueremail); if ($backpack) { $this->set_data($backpack); } // Disable short forms. $mform->setDisableShortforms(); $this->add_action_buttons(); } #[\Override] public function definition_after_data(): void { parent::definition_after_data(); $mform = $this->_form; if ($this->is_submitted()) { if (!$mform->elementExists('apiversion')) { return; } $apiversion = $mform->getElement('apiversion')->getValue(); $apiversion = $apiversion ? array_pop($apiversion) : null; $provider = $mform->elementExists('provider') ? $mform->getElement('provider')->getValue() : null; $provider = $provider ? array_pop($provider) : null; $region = $mform->elementExists('region') ? $mform->getElement('region')->getValue() : null; $region = $region ? array_pop($region) : null; if ($apiversion == OPEN_BADGES_V2) { if ( $provider == backpack_api::PROVIDER_CANVAS_CREDENTIALS && isset($region) && $region != backpack_api::REGION_EMPTY ) { $mform->getElement('backpackweburl')->setValue( backpack_api::get_region_url($region), ); $mform->getElement('backpackapiurl')->setValue( backpack_api::get_region_api_url($region), ); if ($mform->getElement('includeauthdetailscanvas')->getValue()) { $mform->getElement('backpackemail')->setValue( $mform->getElement('backpackemailcanvas')->getValue(), ); $mform->getElement('password')->setValue( $mform->getElement('backpackpasswordcanvas')->getValue(), ); } } else if (is_null($provider) || $provider == backpack_api::PROVIDER_OTHER) { if ($mform->getElement('includeauthdetails')->getValue() == 0) { // Clear backpack issuer fields when authentication details checkbox is not checked. $mform->getElement('backpackemail')->setValue(''); $mform->getElement('password')->setValue(''); } } } else if ($apiversion == OPEN_BADGES_V2P1) { if (!empty($mform->getElement('backpackweburlv2p1')->getValue())) { $mform->getElement('backpackweburl')->setValue( $mform->getElement('backpackweburlv2p1')->getValue(), ); } // Clear backpack issuer fields when OBv2.1 is selected. $mform->getElement('includeauthdetails')->setValue(0); $mform->getElement('backpackemail')->setValue(''); $mform->getElement('password')->setValue(''); } } } #[\Override] public function set_data($backpack) { if ($backpack->apiversion == OPEN_BADGES_V2) { if (backpack_api::is_canvas_credentials_region($backpack->backpackweburl)) { // Calculate provider and region fields based on backpack URLs. $backpack->provider = backpack_api::PROVIDER_CANVAS_CREDENTIALS; $backpack->region = backpack_api::get_regionid_from_url($backpack->backpackweburl); $backpack->backpackweburl = ''; $backpack->backpackapiurl = ''; if (isset($backpack->backpackemail) && !empty($backpack->backpackemail)) { // Update Canvas Credentials fields. $backpack->includeauthdetailscanvas = 1; $backpack->backpackemailcanvas = $backpack->backpackemail; $backpack->backpackpasswordcanvas = $backpack->password; // Clear email and password fields for another providers. $backpack->includeauthdetails = 0; $backpack->backpackemail = ''; $backpack->password = ''; } } else { $backpack->provider = backpack_api::PROVIDER_OTHER; } } else if ($backpack->apiversion == OPEN_BADGES_V2P1) { $backpack->backpackweburlv2p1 = $backpack->backpackweburl; $backpack->backpackweburl = ''; $backpack->backpackapiurl = ''; } parent::set_data($backpack); } /** * Validate the data from the form. * * @param array $data form data * @param array $files form files * @return array An array of error messages. */ public function validation($data, $files) { $errors = parent::validation($data, $files); // Ensure backpackapiurl and backpackweburl are valid URLs. $isobv20 = isset($data['apiversion']) && $data['apiversion'] == OPEN_BADGES_V2; $isobv2p1 = isset($data['apiversion']) && $data['apiversion'] == OPEN_BADGES_V2P1; if ($isobv20) { $errors = array_merge($errors, $this->validate_obv20($data)); } else if ($isobv2p1) { $errors = array_merge($errors, $this->validate_obv2p1($data)); } // Check email and password are not empty when including auth details. if (!empty($data['includeauthdetails']) && empty($data['backpackemail'])) { $errors['backpackemail'] = get_string('err_required', 'form'); } if (!empty($data['includeauthdetails']) && empty($data['password'])) { $errors['password'] = get_string('err_required', 'form'); } return $errors; } /** * Validate the data for Open Badges v2.0. * * @param array $data Form data. * @return string[] An array of error messages. */ private function validate_obv20(array $data): array { $errors = []; $displaycanvasfields = backpack_api::display_canvas_credentials_fields(); if ( $displaycanvasfields && (!array_key_exists('provider', $data) || $data['provider'] == backpack_api::PROVIDER_EMPTY) ) { // Check provider is set. $errors['provider'] = get_string('err_required', 'form'); } else if ( $displaycanvasfields && ($data['provider'] == backpack_api::PROVIDER_CANVAS_CREDENTIALS) ) { // Check region is set. if (!array_key_exists('region', $data) || $data['region'] == backpack_api::REGION_EMPTY) { $errors['region'] = get_string('err_required', 'form'); } } else { if (empty($data['backpackweburl'])) { $errors['backpackweburl'] = get_string('err_required', 'form'); } else if (!preg_match('@^https?://.+@', $data['backpackweburl'])) { $errors['backpackweburl'] = get_string('invalidurl', 'badges'); } if (empty($data['backpackapiurl'])) { $errors['backpackapiurl'] = get_string('err_required', 'form'); } else if (!preg_match('@^https?://.+@', $data['backpackapiurl'])) { $errors['backpackapiurl'] = get_string('invalidurl', 'badges'); } } if ($displaycanvasfields) { if (!empty($data['includeauthdetailscanvas']) && empty($data['backpackemailcanvas'])) { $errors['backpackemailcanvas'] = get_string('err_required', 'form'); } if (!empty($data['includeauthdetailscanvas']) && empty($data['backpackpasswordcanvas'])) { $errors['backpackpasswordcanvas'] = get_string('err_required', 'form'); } } return $errors; } /** * Validate the data for Open Badges v2.1. * * @param array $data Form data. * @return string[] An array of error messages. */ private function validate_obv2p1(array $data): array { $errors = []; if (empty($data['backpackweburlv2p1'])) { $errors['backpackweburlv2p1'] = get_string('err_required', 'form'); } else if (!preg_match('@^https?://.+@', $data['backpackweburlv2p1'])) { $errors['backpackweburlv2p1'] = get_string('invalidurl', 'badges'); } return $errors; } /** * Add provider fields to the form. */ protected function add_provider_fields(): void { $mform = $this->_form; if (!backpack_api::display_canvas_credentials_fields()) { // If canvas credentials fields are not to be displayed, return early. return; } // Add an empty option at the start. $providers = backpack_api::get_providers(); $providers = [backpack_api::PROVIDER_EMPTY => ''] + $providers; $mform->addElement('select', 'provider', get_string('provider', 'core_badges'), $providers); $mform->setType('provider', PARAM_RAW); $mform->hideIf('provider', 'apiversion', 'ne', OPEN_BADGES_V2); // Add an empty option at the start. $regions = backpack_api::get_regions(); $regions = [backpack_api::REGION_EMPTY => ''] + array_column($regions, 'name'); $mform->addElement('select', 'region', get_string('region', 'core_badges'), $regions); $mform->setType('region', PARAM_RAW); $mform->hideIf('region', 'provider', 'ne', backpack_api::PROVIDER_CANVAS_CREDENTIALS); $mform->hideIf('region', 'apiversion', 'ne', OPEN_BADGES_V2); } /** * Add Canvas backpack specific issuer auth details. * * @param string|null $email The email addressed provided or null if it's new. */ protected function add_connect_issuer_canvas_fields(?string $email): void { $mform = $this->_form; if (!backpack_api::display_canvas_credentials_fields()) { // If canvas credentials fields are not to be displayed, return early. return; } $providers = backpack_api::get_providers(); $regions = backpack_api::get_regions(); if (empty($providers) || empty($regions)) { // If no providers or regions are available, return early. return; } // Checkbox and information to enable/disable issuer account. $mform->addElement('static', '', null, ''); $mform->addElement( 'advcheckbox', 'includeauthdetailscanvas', null, '' . get_string('includeauthdetailscanvas', 'core_badges') . ' ' . get_string('includeauthdetailscanvas_subtitle', 'core_badges'), ); if (!empty($backpack->backpackemail) || !empty($backpack->password)) { $mform->setDefault('includeauthdetailscanvas', 1); } $mform->addHelpButton('includeauthdetailscanvas', 'includeauthdetailscanvas', 'core_badges'); $mform->hideIf('includeauthdetailscanvas', 'apiversion', 'ne', OPEN_BADGES_V2); $mform->hideIf('includeauthdetailscanvas', 'region', 'eq', backpack_api::REGION_EMPTY); $mform->hideIf('includeauthdetailscanvas', 'provider', 'ne', backpack_api::PROVIDER_CANVAS_CREDENTIALS); $mform->addElement( 'static', 'includeauthdetailscanvasdesc', null, get_string('includeauthdetailscanvas_desc', 'core_badges'), ); $mform->hideIf('includeauthdetailscanvasdesc', 'includeauthdetailscanvas'); $mform->hideIf('includeauthdetailscanvasdesc', 'region', 'eq', backpack_api::REGION_EMPTY); $mform->hideIf('includeauthdetailscanvasdesc', 'provider', 'ne', backpack_api::PROVIDER_CANVAS_CREDENTIALS); $mform->hideIf('includeauthdetailscanvasdesc', 'apiversion', 'ne', OPEN_BADGES_V2); // Email. $mform->addElement('text', 'backpackemailcanvas', get_string('issueremail', 'core_badges')); $mform->setType('backpackemailcanvas', PARAM_EMAIL); $mform->setDefault('backpackemailcanvas', $email); $mform->hideIf('backpackemailcanvas', 'includeauthdetailscanvas'); $mform->hideIf('backpackemailcanvas', 'apiversion', 'ne', OPEN_BADGES_V2); $mform->hideIf('backpackemailcanvas', 'provider', 'ne', backpack_api::PROVIDER_CANVAS_CREDENTIALS); // Password. $mform->addElement('passwordunmask', 'backpackpasswordcanvas', get_string('password')); $mform->setType('backpackpasswordcanvas', PARAM_RAW); $mform->hideIf('backpackpasswordcanvas', 'includeauthdetailscanvas'); $mform->hideIf('backpackpasswordcanvas', 'apiversion', 'ne', OPEN_BADGES_V2); $mform->hideIf('backpackpasswordcanvas', 'provider', 'ne', backpack_api::PROVIDER_CANVAS_CREDENTIALS); } /** * Add generic backpack issuer auth details. * * @param \stdClass|null $backpack The backpack instance. * @param string|null $email The issuer email or null if it's new. */ protected function add_connect_issuer_fields(?\stdClass $backpack, ?string $email): void { $mform = $this->_form; // Checkbox and information to enable/disable issuer account. $mform->addElement( 'advcheckbox', 'includeauthdetails', null, '' . get_string('includeauthdetails', 'core_badges') . '', ); if ($backpack && (!empty($backpack->backpackemail) || !empty($backpack->password))) { $mform->setDefault('includeauthdetails', 1); } $mform->addHelpButton('includeauthdetails', 'includeauthdetails', 'core_badges'); $mform->hideIf('includeauthdetails', 'provider', 'eq', backpack_api::PROVIDER_CANVAS_CREDENTIALS); $mform->hideIf('includeauthdetails', 'apiversion', 'ne', OPEN_BADGES_V2); $mform->addElement('static', 'includeauthdetailsdesc', null, get_string('includeauthdetails_desc', 'core_badges')); $mform->hideIf('includeauthdetailsdesc', 'includeauthdetails'); $mform->hideIf('includeauthdetailsdesc', 'provider', 'eq', backpack_api::PROVIDER_CANVAS_CREDENTIALS); // Email and password fields. $this->add_auth_fields($email); } /** * Add backpack specific auth details. * * @param string|null $email The email addressed provided or null if it's new. * @param bool $includepassword Include the password field. Defaults to true * @throws \coding_exception */ protected function add_auth_fields(?string $email, bool $includepassword = true) { $mform = $this->_form; // Email. $mform->addElement('text', 'backpackemail', get_string('issueremail', 'core_badges')); $mform->setType('backpackemail', PARAM_EMAIL); $mform->setDefault('backpackemail', $email); $mform->hideIf('backpackemail', 'includeauthdetails'); $mform->hideIf('backpackemail', 'apiversion', 'ne', OPEN_BADGES_V2); $mform->hideIf('backpackemail', 'provider', 'eq', backpack_api::PROVIDER_CANVAS_CREDENTIALS); // Password. if ($includepassword) { $mform->addElement('passwordunmask', 'password', get_string('password')); $mform->setType('password', PARAM_RAW); } $mform->hideIf('password', 'includeauthdetails'); $mform->hideIf('password', 'apiversion', 'ne', OPEN_BADGES_V2); $mform->hideIf('password', 'provider', 'eq', backpack_api::PROVIDER_CANVAS_CREDENTIALS); } } PK\u cache_lock_form.phpnu[. namespace core_cache\form; use core_cache\config as cache_config; use moodleform; /** * Form to add a cache lock instance. * * All cache lock plugins that wish to have custom configuration should override * this form, and more explicitly the plugin_definition and plugin_validation methods. * * @package core_cache * @category cache * @copyright 2013 Sam Hemelryk * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class cache_lock_form extends moodleform { /** * Defines this form. */ final public function definition() { $plugin = $this->_customdata['lock']; $this->_form->addElement('hidden', 'action', 'newlockinstance'); $this->_form->setType('action', PARAM_ALPHANUMEXT); $this->_form->addElement('hidden', 'lock', $plugin); $this->_form->setType('lock', PARAM_COMPONENT); $this->_form->addElement('text', 'name', get_string('lockname', 'cache')); $this->_form->setType('name', PARAM_ALPHANUMEXT); $this->_form->addRule('name', get_string('required'), 'required'); $this->_form->addElement('static', 'namedesc', '', get_string('locknamedesc', 'cache')); $this->plugin_definition(); $this->add_action_buttons(); } /** * Validates this form. * * @param array $data * @param array $files * @return array */ final public function validation($data, $files) { $errors = parent::validation($data, $files); if (!isset($errors['name'])) { $config = cache_config::instance(); if (in_array($data['name'], array_keys($config->get_locks()))) { $errors['name'] = get_string('locknamenotunique', 'cache'); } } $errors = $this->plugin_validation($data, $files, $errors); return $errors; } /** * Plugin specific definition. */ public function plugin_definition() { // No custom validation going on here. } /** * Plugin specific validation. * * @param array $data * @param array $files * @param array $errors * @return array */ public function plugin_validation($data, $files, array $errors) { return $errors; } } // Alias this class to the old name. // This file will be autoloaded by the legacyclasses autoload system. // In future all uses of this class will be corrected and the legacy references will be removed. class_alias(cache_lock_form::class, \cache_lock_form::class); PK\Wmocachestore_addinstance_form.phpnu[. namespace core_cache\form; defined('MOODLE_INTERNAL') || die(); global $CFG; require_once($CFG->dirroot . '/lib/formslib.php'); use core_cache\administration_helper; use moodleform; /** * Add store instance form. * * @package core_cache * @category cache * @copyright 2012 Sam Hemelryk * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class cachestore_addinstance_form extends moodleform { #[\Override] final protected function definition() { $form = $this->_form; $store = $this->_customdata['store']; $plugin = $this->_customdata['plugin']; $locks = $this->_customdata['locks']; $form->addElement('hidden', 'plugin', $plugin); $form->setType('plugin', PARAM_PLUGIN); $form->addElement('hidden', 'editing', !empty($this->_customdata['store'])); $form->setType('editing', PARAM_BOOL); if (!$store) { $form->addElement('text', 'name', get_string('storename', 'cache')); $form->addHelpButton('name', 'storename', 'cache'); $form->addRule('name', get_string('required'), 'required'); $form->setType('name', PARAM_NOTAGS); } else { $form->addElement('hidden', 'name', $store); $form->addElement('static', 'name-value', get_string('storename', 'cache'), $store); $form->setType('name', PARAM_NOTAGS); } if (is_array($locks)) { $form->addElement('select', 'lock', get_string('locking', 'cache'), $locks); $form->addHelpButton('lock', 'locking', 'cache'); $form->setType('lock', PARAM_ALPHANUMEXT); } else { $form->addElement('hidden', 'lock', ''); $form->setType('lock', PARAM_ALPHANUMEXT); $form->addElement( 'static', 'lock-value', get_string('locking', 'cache'), '' . get_string('nativelocking', 'cache') . '' ); } if (method_exists($this, 'configuration_definition')) { $form->addElement('header', 'storeconfiguration', get_string('storeconfiguration', 'cache')); $this->configuration_definition(); } $this->add_action_buttons(); } #[\Override] public function validation($data, $files) { $errors = parent::validation($data, $files); if (!array_key_exists('name', $errors)) { if (!preg_match('#^[a-zA-Z0-9\-_ ]+$#', $data['name'])) { $errors['name'] = get_string('storenameinvalid', 'cache'); } else if (empty($this->_customdata['store'])) { $stores = administration_helper::get_store_instance_summaries(); if (array_key_exists($data['name'], $stores)) { $errors['name'] = get_string('storenamealreadyused', 'cache'); } } } if (method_exists($this, 'configuration_validation')) { $newerrors = $this->configuration_validation($data, $files, $errors); // We need to selectiviliy merge here. foreach ($newerrors as $element => $error) { if (!array_key_exists($element, $errors)) { $errors[$element] = $error; } } } return $errors; } } // Alias this class to the old name. // This file will be autoloaded by the legacyclasses autoload system. // In future all uses of this class will be corrected and the legacy references will be removed. class_alias(cachestore_addinstance_form::class, \cachestore_addinstance_form::class); PK\P cache_mode_mappings_form.phpnu[. namespace core_cache\form; use core_cache\store; use moodleform; /** * Form to set the mappings for a mode. * * @package core_cache * @category cache * @copyright 2012 Sam Hemelryk * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class cache_mode_mappings_form extends moodleform { /** * The definition of the form */ protected function definition() { $form = $this->_form; $stores = $this->_customdata; $options = [ store::MODE_APPLICATION => [], store::MODE_SESSION => [], store::MODE_REQUEST => [], ]; foreach ($stores as $storename => $store) { foreach ($store['modes'] as $mode => $enabled) { if ($enabled && ($mode !== store::MODE_SESSION || $store['supports']['searchable'])) { if (empty($store['default'])) { $options[$mode][$storename] = $store['name']; } else { $options[$mode][$storename] = get_string('store_' . $store['name'], 'cache'); } } } } $form->addElement('hidden', 'action', 'editmodemappings'); $form->setType('action', PARAM_ALPHA); foreach ($options as $mode => $optionset) { $form->addElement('select', 'mode_' . $mode, get_string('mode_' . $mode, 'cache'), $optionset); } $this->add_action_buttons(); } } // Alias this class to the old name. // This file will be autoloaded by the legacyclasses autoload system. // In future all uses of this class will be corrected and the legacy references will be removed. class_alias(cache_mode_mappings_form::class, \cache_mode_mappings_form::class); PK\>N/a!cache_definition_sharing_form.phpnu[. namespace core_cache\form; use core_cache\administration_helper; use core_cache\definition; use moodleform; /** * Form to set definition sharing option * * @package core_cache * @category cache * @copyright 2013 Sam Hemelryk * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class cache_definition_sharing_form extends moodleform { /** * The definition of the form */ final protected function definition() { $definition = $this->_customdata['definition']; $sharingoptions = $this->_customdata['sharingoptions']; $form = $this->_form; $form->addElement('hidden', 'definition', $definition); $form->setType('definition', PARAM_SAFEPATH); $form->addElement('hidden', 'action', 'editdefinitionsharing'); $form->setType('action', PARAM_ALPHA); // We use a group here for validation. $count = 0; $group = []; foreach ($sharingoptions as $value => $text) { $count++; $group[] = $form->createElement('checkbox', $value, null, $text); } $form->addGroup($group, 'sharing', get_string('sharing', 'cache'), '
'); $form->setType('sharing', PARAM_INT); $form->addElement('text', 'userinputsharingkey', get_string('userinputsharingkey', 'cache')); $form->addHelpButton('userinputsharingkey', 'userinputsharingkey', 'cache'); $form->disabledIf('userinputsharingkey', 'sharing[' . definition::SHARING_INPUT . ']', 'notchecked'); $form->setType('userinputsharingkey', PARAM_ALPHANUMEXT); $values = array_keys($sharingoptions); if (in_array(definition::SHARING_ALL, $values)) { // If you share with all thenthe other options don't really make sense. foreach ($values as $value) { $form->disabledIf('sharing[' . $value . ']', 'sharing[' . definition::SHARING_ALL . ']', 'checked'); } $form->disabledIf('userinputsharingkey', 'sharing[' . definition::SHARING_ALL . ']', 'checked'); } $this->add_action_buttons(); } /** * Sets the data for this form. * * @param array $data */ public function set_data($data) { if (!isset($data['sharing'])) { // Set the default value here. mforms doesn't handle defaults very nicely. $data['sharing'] = administration_helper::get_definition_sharing_options(definition::SHARING_DEFAULT); } parent::set_data($data); } /** * Validates this form * * @param array $data * @param array $files * @return array */ public function validation($data, $files) { $errors = parent::validation($data, $files); if (count($errors) === 0 && !isset($data['sharing'])) { // They must select at least one sharing option. $errors['sharing'] = get_string('sharingrequired', 'cache'); } return $errors; } } // Alias this class to the old name. // This file will be autoloaded by the legacyclasses autoload system. // In future all uses of this class will be corrected and the legacy references will be removed. class_alias(cache_definition_sharing_form::class, \cache_definition_sharing_form::class); PK\y"__"cache_definition_mappings_form.phpnu[. namespace core_cache\form; use core_cache\administration_helper; use core_cache\store; use html_writer; use moodleform; /** * Form to set definition mappings * * @package core_cache * @category cache * @copyright 2012 Sam Hemelryk * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class cache_definition_mappings_form extends moodleform { /** * The definition of the form */ final protected function definition() { global $OUTPUT; $definition = $this->_customdata['definition']; $form = $this->_form; [$component, $area] = explode('/', $definition, 2); [$currentstores, $storeoptions, $defaults] = administration_helper::get_definition_store_options($component, $area); $storedata = administration_helper::get_definition_summaries(); if ($storedata[$definition]['mode'] != store::MODE_REQUEST) { if (isset($storedata[$definition]['canuselocalstore']) && $storedata[$definition]['canuselocalstore']) { $form->addElement('html', $OUTPUT->notification(get_string('localstorenotification', 'cache'), 'notifymessage')); } else { $form->addElement('html', $OUTPUT->notification(get_string('sharedstorenotification', 'cache'), 'notifymessage')); } } $form->addElement('hidden', 'definition', $definition); $form->setType('definition', PARAM_SAFEPATH); $form->addElement('hidden', 'action', 'editdefinitionmapping'); $form->setType('action', PARAM_ALPHA); $requiredoptions = max(3, count($currentstores) + 1); $requiredoptions = min($requiredoptions, count($storeoptions)); $options = ['' => get_string('none')]; foreach ($storeoptions as $option => $def) { $options[$option] = $option; if ($def['default']) { $options[$option] .= ' ' . get_string('mappingdefault', 'cache'); } } for ($i = 0; $i < $requiredoptions; $i++) { $title = '...'; if ($i === 0) { $title = get_string('mappingprimary', 'cache'); } else if ($i === $requiredoptions - 1) { $title = get_string('mappingfinal', 'cache'); } $form->addElement('select', 'mappings[' . $i . ']', $title, $options); } $i = 0; foreach ($currentstores as $store => $def) { $form->setDefault('mappings[' . $i . ']', $store); $i++; } if (!empty($defaults)) { $form->addElement( 'static', 'defaults', get_string('defaultmappings', 'cache'), html_writer::tag('strong', join(', ', $defaults)) ); $form->addHelpButton('defaults', 'defaultmappings', 'cache'); } $this->add_action_buttons(); } } // Alias this class to the old name. // This file will be autoloaded by the legacyclasses autoload system. // In future all uses of this class will be corrected and the legacy references will be removed. class_alias(cache_definition_mappings_form::class, \cache_definition_mappings_form::class); PK˥\Ynadd_random_form.phpnu[. namespace mod_quiz\form; use core\check\performance\debugging; use core_tag_tag; use moodleform; defined('MOODLE_INTERNAL') || die(); require_once($CFG->libdir.'/formslib.php'); /** * The add random questions form. * * @package mod_quiz * @copyright 1999 onwards Martin Dougiamas and others {@link http://moodle.com} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @deprecated Moodle 4.3 MDL-72321. This form is new generated in a modal with mod_quiz/add_random_question_form.mustache * @todo Final deprecation in Moodle 4.7 MDL-78091 */ class add_random_form extends moodleform { /** * Deprecated. * * @return void * @deprecated Moodle 4.3 MDL-72321 * @todo Final deprecation in Moodle 4.7 MDL-78091 */ protected function definition() { debugging( 'add_random_form is deprecated. Please use mod_quiz/add_random_question_form.mustache instead.', DEBUG_DEVELOPER ); global $OUTPUT, $PAGE, $CFG; $mform = $this->_form; $mform->setDisableShortforms(); $contexts = $this->_customdata['contexts']; $usablecontexts = $contexts->having_cap('moodle/question:useall'); // Random from existing category section. $mform->addElement('header', 'existingcategoryheader', get_string('randomfromexistingcategory', 'quiz')); $mform->addElement('questioncategory', 'category', get_string('category'), ['contexts' => $usablecontexts, 'top' => true]); $mform->setDefault('category', $this->_customdata['cat']); $mform->addElement('checkbox', 'includesubcategories', '', get_string('recurse', 'quiz')); $tops = question_get_top_categories_for_contexts(array_column($contexts->all(), 'id')); $mform->hideIf('includesubcategories', 'category', 'in', $tops); if ($CFG->usetags) { $tagstrings = []; $tags = core_tag_tag::get_tags_by_area_in_contexts('core_question', 'question', $usablecontexts); foreach ($tags as $tag) { $tagstrings["{$tag->id},{$tag->name}"] = $tag->name; } $options = [ 'multiple' => true, 'noselectionstring' => get_string('anytags', 'quiz'), ]; $mform->addElement('autocomplete', 'fromtags', get_string('randomquestiontags', 'mod_quiz'), $tagstrings, $options); $mform->addHelpButton('fromtags', 'randomquestiontags', 'mod_quiz'); } // TODO: in the past, the drop-down used to only show sensible choices for // number of questions to add. That is, if the currently selected filter // only matched 9 questions (not already in the quiz), then the drop-down would // only offer choices 1..9. This nice UI hint got lost when the UI became Ajax-y. // We should add it back. $mform->addElement('select', 'numbertoadd', get_string('randomnumber', 'quiz'), $this->get_number_of_questions_to_add_choices()); $previewhtml = $OUTPUT->render_from_template('mod_quiz/random_question_form_preview', []); $mform->addElement('html', $previewhtml); $mform->addElement('submit', 'existingcategory', get_string('addrandomquestion', 'quiz')); // If the manage categories plugins is enabled, add the elements to create a new category in the form. if (\core\plugininfo\qbank::is_plugin_enabled(\qbank_managecategories\helper::PLUGINNAME)) { // Random from a new category section. $mform->addElement('header', 'newcategoryheader', get_string('randomquestionusinganewcategory', 'quiz')); $mform->addElement('text', 'name', get_string('name'), 'maxlength="254" size="50"'); $mform->setType('name', PARAM_TEXT); $mform->addElement('questioncategory', 'parent', get_string('parentcategory', 'question'), ['contexts' => $usablecontexts, 'top' => true]); $mform->addHelpButton('parent', 'parentcategory', 'question'); $mform->addElement('submit', 'newcategory', get_string('createcategoryandaddrandomquestion', 'quiz')); } // Cancel button. $mform->addElement('cancel'); $mform->closeHeaderBefore('cancel'); $mform->addElement('hidden', 'addonpage', 0, 'id="rform_qpage"'); $mform->setType('addonpage', PARAM_SEQUENCE); $mform->addElement('hidden', 'cmid', 0); $mform->setType('cmid', PARAM_INT); $mform->addElement('hidden', 'returnurl', 0); $mform->setType('returnurl', PARAM_LOCALURL); // Add the javascript required to enhance this mform. $PAGE->requires->js_call_amd('mod_quiz/add_random_form', 'init', [ $mform->getAttribute('id'), $contexts->lowest()->id, $tops, $CFG->usetags ]); } /** * Deprecated. * * @param array $fromform * @param array $files * @return array * @deprecated Moodle 4.3 MDL-72321 * @todo Final deprecation in Moodle 4.7 MDL-78091 */ public function validation($fromform, $files) { debugging( 'add_random_form is deprecated. Please use mod_quiz/add_random_question_form.mustache instead.', DEBUG_DEVELOPER ); $errors = parent::validation($fromform, $files); if (!empty($fromform['newcategory']) && trim($fromform['name']) == '') { $errors['name'] = get_string('categorynamecantbeblank', 'question'); } return $errors; } /** * Return an arbitrary array for the dropdown menu * * @param int $maxrand * @return array of integers [1, 2, ..., 100] (or to the smaller of $maxrand and 100.) */ private function get_number_of_questions_to_add_choices($maxrand = 100) { $randomcount = []; for ($i = 1; $i <= min(100, $maxrand); $i++) { $randomcount[$i] = $i; } return $randomcount; } } PK˥\ *preflight_check_form.phpnu[. namespace mod_quiz\form; use moodleform; defined('MOODLE_INTERNAL') || die(); require_once($CFG->libdir.'/formslib.php'); /** * A form that limits student's access to attempt a quiz. * * @package mod_quiz * @copyright 2011 The Open University * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class preflight_check_form extends moodleform { protected function definition() { $mform = $this->_form; $this->_form->updateAttributes(['id' => 'mod_quiz_preflight_form']); foreach ($this->_customdata['hidden'] as $name => $value) { if ($name === 'sesskey') { continue; } $mform->addElement('hidden', $name, $value); $mform->setType($name, PARAM_INT); } foreach ($this->_customdata['rules'] as $rule) { if ($rule->is_preflight_check_required($this->_customdata['attemptid'])) { $rule->add_preflight_check_form_fields($this, $mform, $this->_customdata['attemptid']); } } $this->add_action_buttons(true, get_string('startattempt', 'quiz')); $this->set_display_vertical(); $mform->setDisableShortforms(); } public function validation($data, $files): array { $errors = parent::validation($data, $files); $accessmanager = $this->_customdata['quizobj']->get_access_manager(time()); return array_merge($errors, $accessmanager->validate_preflight_check( $data, $files, $this->_customdata['attemptid'])); } } PK˥\b//edit_override_form.phpnu[. namespace mod_quiz\form; use cm_info; use context; use context_module; use mod_quiz_mod_form; use moodle_url; use moodleform; use stdClass; defined('MOODLE_INTERNAL') || die(); require_once($CFG->libdir . '/formslib.php'); require_once($CFG->dirroot . '/mod/quiz/mod_form.php'); /** * Form for editing quiz settings overrides. * * @package mod_quiz * @copyright 2010 Matt Petro * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class edit_override_form extends moodleform { /** @var cm_info course module object. */ protected $cm; /** @var stdClass the quiz settings object. */ protected $quiz; /** @var context_module the quiz context. */ protected $context; /** @var bool editing group override (true) or user override (false). */ protected $groupmode; /** @var int groupid, if provided. */ protected $groupid; /** @var int userid, if provided. */ protected $userid; /** @var int overrideid, if provided. */ protected int $overrideid; /** * Constructor. * * @param moodle_url $submiturl the form action URL. * @param cm_info $cm course module object. * @param stdClass $quiz the quiz settings object. * @param context_module $context the quiz context. * @param bool $groupmode editing group override (true) or user override (false). * @param stdClass|null $override the override being edited, if it already exists. */ public function __construct(moodle_url $submiturl, cm_info $cm, stdClass $quiz, context_module $context, bool $groupmode, ?stdClass $override) { $this->cm = $cm; $this->quiz = $quiz; $this->context = $context; $this->groupmode = $groupmode; $this->groupid = empty($override->groupid) ? 0 : $override->groupid; $this->userid = empty($override->userid) ? 0 : $override->userid; $this->overrideid = $override->id ?? 0; parent::__construct($submiturl); } protected function definition() { global $DB; $cm = $this->cm; $mform = $this->_form; $mform->addElement('header', 'override', get_string('override', 'quiz')); $quizgroupmode = groups_get_activity_groupmode($cm); $accessallgroups = ($quizgroupmode == NOGROUPS) || has_capability('moodle/site:accessallgroups', $this->context); if ($this->groupmode) { // Group override. if ($this->groupid) { // There is already a groupid, so freeze the selector. $groupchoices = [ $this->groupid => format_string(groups_get_group_name($this->groupid), true, ['context' => $this->context]), ]; $mform->addElement('select', 'groupid', get_string('overridegroup', 'quiz'), $groupchoices); $mform->freeze('groupid'); } else { // Prepare the list of groups. // Only include the groups the current can access. $groups = $accessallgroups ? groups_get_all_groups($cm->course) : groups_get_activity_allowed_groups($cm); if (empty($groups)) { // Generate an error. $link = new moodle_url('/mod/quiz/overrides.php', ['cmid' => $cm->id]); throw new \moodle_exception('groupsnone', 'quiz', $link); } $groupchoices = []; foreach ($groups as $group) { if ($group->visibility != GROUPS_VISIBILITY_NONE) { $groupchoices[$group->id] = format_string($group->name, true, ['context' => $this->context]); } } unset($groups); if (count($groupchoices) == 0) { $groupchoices[0] = get_string('none'); } $mform->addElement('select', 'groupid', get_string('overridegroup', 'quiz'), $groupchoices); $mform->addRule('groupid', get_string('required'), 'required', null, 'client'); } } else { // User override. $userfieldsapi = \core_user\fields::for_identity($this->context)->with_userpic()->with_name(); $extrauserfields = $userfieldsapi->get_required_fields([\core_user\fields::PURPOSE_IDENTITY]); if ($this->userid) { // There is already a userid, so freeze the selector. $user = $DB->get_record('user', ['id' => $this->userid]); profile_load_custom_fields($user); $userchoices = []; $userchoices[$this->userid] = self::display_user_name($user, $extrauserfields); $mform->addElement('select', 'userid', get_string('overrideuser', 'quiz'), $userchoices); $mform->freeze('userid'); } else { // Prepare the list of users. $groupids = 0; if (!$accessallgroups) { $groups = groups_get_activity_allowed_groups($cm); $groupids = array_keys($groups); } $enrolledjoin = get_enrolled_with_capabilities_join( $this->context, '', 'mod/quiz:attempt', $groupids, true); $userfieldsql = $userfieldsapi->get_sql('u', true, '', '', false); list($sort, $sortparams) = users_order_by_sql('u', null, $this->context, $userfieldsql->mappings); $users = $DB->get_records_sql(" SELECT DISTINCT $userfieldsql->selects FROM {user} u $enrolledjoin->joins $userfieldsql->joins LEFT JOIN {quiz_overrides} existingoverride ON existingoverride.userid = u.id AND existingoverride.quiz = :quizid WHERE existingoverride.id IS NULL AND $enrolledjoin->wheres ORDER BY $sort ", array_merge(['quizid' => $this->quiz->id], $userfieldsql->params, $enrolledjoin->params, $sortparams)); // Filter users based on any fixed restrictions (groups, profile). $info = new \core_availability\info_module($cm); $users = $info->filter_user_list($users); if (empty($users)) { // Generate an error. $link = new moodle_url('/mod/quiz/overrides.php', ['cmid' => $cm->id]); throw new \moodle_exception('usersnone', 'quiz', $link); } $userchoices = []; foreach ($users as $id => $user) { $userchoices[$id] = self::display_user_name($user, $extrauserfields); } unset($users); $mform->addElement('searchableselector', 'userid', get_string('overrideuser', 'quiz'), $userchoices); $mform->addRule('userid', get_string('required'), 'required', null, 'client'); } } // Password. // This field has to be above the date and timelimit fields, // otherwise browsers will clear it when those fields are changed. $mform->addElement('passwordunmask', 'password', get_string('requirepassword', 'quiz')); $mform->setType('password', PARAM_TEXT); $mform->addHelpButton('password', 'requirepassword', 'quiz'); $mform->setDefault('password', $this->quiz->password); // Open and close dates. $mform->addElement('date_time_selector', 'timeopen', get_string('quizopen', 'quiz'), mod_quiz_mod_form::$datefieldoptions); $mform->setDefault('timeopen', $this->quiz->timeopen); $mform->addElement('date_time_selector', 'timeclose', get_string('quizclose', 'quiz'), mod_quiz_mod_form::$datefieldoptions); $mform->setDefault('timeclose', $this->quiz->timeclose); // Time limit. $mform->addElement('duration', 'timelimit', get_string('timelimit', 'quiz'), ['optional' => true]); $mform->addHelpButton('timelimit', 'timelimit', 'quiz'); $mform->setDefault('timelimit', $this->quiz->timelimit); // Number of attempts. $attemptoptions = ['0' => get_string('unlimited')]; for ($i = 1; $i <= QUIZ_MAX_ATTEMPT_OPTION; $i++) { $attemptoptions[$i] = $i; } $mform->addElement('select', 'attempts', get_string('attemptsallowed', 'quiz'), $attemptoptions); $mform->addHelpButton('attempts', 'attempts', 'quiz'); $mform->setDefault('attempts', $this->quiz->attempts); // Submit buttons. $mform->addElement('submit', 'resetbutton', get_string('reverttodefaults', 'quiz')); $buttonarray = []; $buttonarray[] = $mform->createElement('submit', 'submitbutton', get_string('save', 'quiz')); $buttonarray[] = $mform->createElement('submit', 'againbutton', get_string('saveoverrideandstay', 'quiz')); $buttonarray[] = $mform->createElement('cancel'); $mform->addGroup($buttonarray, 'buttonbar', '', [' '], false); $mform->closeHeaderBefore('buttonbar'); } /** * Get a user's name and identity ready to display. * * @param stdClass $user a user object. * @param array $extrauserfields (identity fields in user table only from the user_fields API) * @return string User's name, with extra info, for display. */ public static function display_user_name(stdClass $user, array $extrauserfields): string { $username = fullname($user); $namefields = []; foreach ($extrauserfields as $field) { if (isset($user->$field) && $user->$field !== '') { $namefields[] = s($user->$field); } else if (strpos($field, 'profile_field_') === 0) { $field = substr($field, 14); if (isset($user->profile[$field]) && $user->profile[$field] !== '') { $namefields[] = s($user->profile[$field]); } } } if ($namefields) { $username .= ' (' . implode(', ', $namefields) . ')'; } return $username; } /** * Validate the data from the form. * * @param array $data form data * @param array $files form files * @return array An array of error messages, where the key is is the mform element name and the value is the error. */ public function validation($data, $files): array { $errors = parent::validation($data, $files); $data['id'] = $this->overrideid; $data['quiz'] = $this->quiz->id; $manager = new \mod_quiz\local\override_manager($this->quiz, $this->context); $errors = array_merge($errors, $manager->validate_data($data)); // Any 'general' errors we merge with the group/user selector element. if (!empty($errors['general'])) { if ($this->groupmode) { $errors['groupid'] = $errors['groupid'] ?? "" . $errors['general']; } else { $errors['userid'] = $errors['userid'] ?? "" . $errors['general']; } } return $errors; } } PK\Drrnumerrors/db/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_numerrors * @copyright 2010 David Mudrak * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Post installation procedure */ function xmldb_workshopform_numerrors_install() { global $CFG, $DB; } PK\Tഹnumerrors/db/upgrade.phpnu[. /** * Keeps track of upgrades to the workshop numerrors grading strategy * * @package workshopform_numerrors * @copyright 2010 David Mudrak * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Performs upgrade of the database structure and data * * @param int $oldversion the version we are upgrading from * @return bool true */ function xmldb_workshopform_numerrors_upgrade($oldversion) { // Automatically generated Moodle v4.2.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.3.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.4.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v4.5.0 release upgrade line. // Put any upgrade step following this. // Automatically generated Moodle v5.0.0 release upgrade line. // Put any upgrade step following this. return true; } PK\@# numerrors/db/install.xmlnu[
PK\q,numerrors/lang/en/workshopform_numerrors.phpnu[. /** * Strings for component 'workshopform_numerrors', language 'en', branch 'MOODLE_20_STABLE' * * @package workshopform_numerrors * @copyright 2009 David Mudrak * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['addmoredimensions'] = 'Blanks for {$a} more assertions'; $string['configgrade0'] = 'The default word describing the negative assessment of an assertion.'; $string['configgrade1'] = 'The default word describing the positive assessment of an assertion.'; $string['dimensioncommentfor'] = 'Comment for {$a}'; $string['dimensiondescription'] = 'Description'; $string['dimensiongrade'] = 'Grade'; $string['dimensionnumber'] = 'Assertion {$a}'; $string['dimensionweight'] = 'Weight'; $string['grademapping'] = 'Grade mapping table'; $string['grade0'] = 'Word for the error'; $string['grade0default'] = 'No'; $string['grade1'] = 'Word for the success'; $string['grade1default'] = 'Yes'; $string['maperror'] = 'Weighted number of errors is less than or equals'; $string['mapgrade'] = 'Grade for submission'; $string['pluginname'] = 'Number of errors'; $string['privacy:metadata'] = 'The Number of errors plugin only stores the details of the assessment form. Actual personal data of how the form has been filled are stored by the Workshop module itself and are attached to exported assessments.'; $string['privacy:export:metadata:map'] = 'If the weighted number of errors reaches {$a->nonegative} then the grade is {$a->grade} percents.'; PK\'G [[Knumerrors/backup/moodle2/restore_workshopform_numerrors_subplugin.class.phpnu[. /** * @package workshopform_numerrors * @copyright 2010 onwards David Mudrak * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); /** * restore subplugin class that provides the necessary information * needed to restore one workshopform_numerrors subplugin. */ class restore_workshopform_numerrors_subplugin extends restore_subplugin { //////////////////////////////////////////////////////////////////////////// // mappings of XML paths to the processable methods //////////////////////////////////////////////////////////////////////////// /** * Returns the paths to be handled by the subplugin at workshop level */ protected function define_workshop_subplugin_structure() { $paths = array(); $elename = $this->get_namefor('map'); $elepath = $this->get_pathfor('/workshopform_numerrors_map'); // we used get_recommended_name() so this works $paths[] = new restore_path_element($elename, $elepath); $elename = $this->get_namefor('dimension'); $elepath = $this->get_pathfor('/workshopform_numerrors_dimension'); // we used get_recommended_name() so this works $paths[] = new restore_path_element($elename, $elepath); return $paths; // And we return the interesting paths } /** * Returns the paths to be handled by the subplugin at referenceassessment level */ protected function define_referenceassessment_subplugin_structure() { $paths = array(); $elename = $this->get_namefor('referencegrade'); $elepath = $this->get_pathfor('/workshopform_numerrors_referencegrade'); // we used get_recommended_name() so this works $paths[] = new restore_path_element($elename, $elepath); return $paths; // And we return the interesting paths } /** * Returns the paths to be handled by the subplugin at exampleassessment level */ protected function define_exampleassessment_subplugin_structure() { $paths = array(); $elename = $this->get_namefor('examplegrade'); $elepath = $this->get_pathfor('/workshopform_numerrors_examplegrade'); // we used get_recommended_name() so this works $paths[] = new restore_path_element($elename, $elepath); return $paths; // And we return the interesting paths } /** * Returns the paths to be handled by the subplugin at assessment level */ protected function define_assessment_subplugin_structure() { $paths = array(); $elename = $this->get_namefor('grade'); $elepath = $this->get_pathfor('/workshopform_numerrors_grade'); // we used get_recommended_name() so this works $paths[] = new restore_path_element($elename, $elepath); return $paths; // And we return the interesting paths } //////////////////////////////////////////////////////////////////////////// // defined path elements are dispatched to the following methods //////////////////////////////////////////////////////////////////////////// /** * Processes the workshopform_numerrors_map element */ public function process_workshopform_numerrors_map($data) { global $DB; $data = (object)$data; $data->workshopid = $this->get_new_parentid('workshop'); $DB->insert_record('workshopform_numerrors_map', $data); } /** * Processes the workshopform_numerrors_dimension element */ public function process_workshopform_numerrors_dimension($data) { global $DB; $data = (object)$data; $oldid = $data->id; $data->workshopid = $this->get_new_parentid('workshop'); $newitemid = $DB->insert_record('workshopform_numerrors', $data); $this->set_mapping($this->get_namefor('dimension'), $oldid, $newitemid, true); // Process files for this workshopform_numerrors->id only $this->add_related_files('workshopform_numerrors', 'description', $this->get_namefor('dimension'), null, $oldid); } /** * Processes the workshopform_numerrors_referencegrade element */ public function process_workshopform_numerrors_referencegrade($data) { $this->process_dimension_grades_structure('workshop_referenceassessment', $data); } /** * Processes the workshopform_numerrors_examplegrade element */ public function process_workshopform_numerrors_examplegrade($data) { $this->process_dimension_grades_structure('workshop_exampleassessment', $data); } /** * Processes the workshopform_numerrors_grade element */ public function process_workshopform_numerrors_grade($data) { $this->process_dimension_grades_structure('workshop_assessment', $data); } //////////////////////////////////////////////////////////////////////////// // internal private methods //////////////////////////////////////////////////////////////////////////// /** * Process the dimension grades linked with the given type of assessment * * Populates the workshop_grades table with new records mapped to the restored * instances of assessments. * * @param mixed $elementname the name of the assessment element * @param array $data parsed xml data */ private function process_dimension_grades_structure($elementname, $data) { global $DB; $data = (object)$data; $oldid = $data->id; $data->assessmentid = $this->get_new_parentid($elementname); $data->strategy = 'numerrors'; $data->dimensionid = $this->get_mappingid($this->get_namefor('dimension'), $data->dimensionid); $DB->insert_record('workshop_grades', $data); } } PK\LooJnumerrors/backup/moodle2/backup_workshopform_numerrors_subplugin.class.phpnu[. /** * @package workshopform_numerrors * @copyright 2010 onwards David Mudrak * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); /** * Provides the information to backup numerrors grading strategy information */ class backup_workshopform_numerrors_subplugin extends backup_subplugin { /** * Returns the assessment form definition to attach to 'workshop' XML element */ protected function define_workshop_subplugin_structure() { // XML nodes declaration $subplugin = $this->get_subplugin_element(); // virtual optigroup element $subpluginwrapper = new backup_nested_element($this->get_recommended_name()); $subpluginmap = new backup_nested_element('workshopform_numerrors_map', array('id'), array( 'nonegative', 'grade')); $subplugindimension = new backup_nested_element('workshopform_numerrors_dimension', array('id'), array( 'sort', 'description', 'descriptionformat', 'grade0', 'grade1', 'weight')); // connect XML elements into the tree $subplugin->add_child($subpluginwrapper); $subpluginwrapper->add_child($subpluginmap); $subpluginwrapper->add_child($subplugindimension); // set source to populate the data $subpluginmap->set_source_table('workshopform_numerrors_map', array('workshopid' => backup::VAR_ACTIVITYID)); $subplugindimension->set_source_table('workshopform_numerrors', array('workshopid' => backup::VAR_ACTIVITYID)); // file annotations $subplugindimension->annotate_files('workshopform_numerrors', 'description', 'id'); return $subplugin; } /** * Returns the dimension grades to attach to 'referenceassessment' XML element */ protected function define_referenceassessment_subplugin_structure() { return $this->dimension_grades_structure('workshopform_numerrors_referencegrade'); } /** * Returns the dimension grades to attach to 'exampleassessment' XML element */ protected function define_exampleassessment_subplugin_structure() { return $this->dimension_grades_structure('workshopform_numerrors_examplegrade'); } /** * Returns the dimension grades to attach to 'assessment' XML element */ protected function define_assessment_subplugin_structure() { return $this->dimension_grades_structure('workshopform_numerrors_grade'); } //////////////////////////////////////////////////////////////////////////// // internal private methods //////////////////////////////////////////////////////////////////////////// /** * Returns the structure of dimension grades * * @param string forst parameter of {@link backup_nested_element} constructor */ private function dimension_grades_structure($elementname) { // create XML elements $subplugin = $this->get_subplugin_element(); // virtual optigroup element $subpluginwrapper = new backup_nested_element($this->get_recommended_name()); $subplugingrade = new backup_nested_element($elementname, array('id'), array( 'dimensionid', 'grade', 'peercomment', 'peercommentformat')); // connect XML elements into the tree $subplugin->add_child($subpluginwrapper); $subpluginwrapper->add_child($subplugingrade); // set source to populate the data $subplugingrade->set_source_sql( "SELECT id, dimensionid, grade, peercomment, peercommentformat FROM {workshop_grades} WHERE strategy = 'numerrors' AND assessmentid = ?", array(backup::VAR_PARENTID)); return $subplugin; } } PK\") numerrors/backup/moodle1/lib.phpnu[. /** * Provides support for the conversion of moodle1 backup to the moodle2 format * * @package workshopform_numerrors * @copyright 2011 David Mudrak * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); require_once($CFG->libdir.'/gradelib.php'); // grade_floatval() called here /** * Conversion handler for the numerrors grading strategy data */ class moodle1_workshopform_numerrors_handler extends moodle1_workshopform_handler { /** @var array */ protected $mappings = array(); /** @var array */ protected $dimensions = array(); /** * New workshop instance is being processed */ public function on_elements_start() { $this->mappings = array(); $this->dimensions = array(); } /** * Converts into and stores it for later writing * * @param array $data legacy element data * @param array $raw raw element data * * @return array to be written to workshop.xml */ public function process_legacy_element(array $data, array $raw) { $workshop = $this->parenthandler->get_current_workshop(); $mapping = array(); $mapping['id'] = $data['id']; $mapping['nonegative'] = $data['elementno']; if ($workshop['grade'] == 0 or $data['maxscore'] == 0) { $mapping['grade'] = 0; } else { $mapping['grade'] = grade_floatval($data['maxscore'] / $workshop['grade'] * 100); } $this->mappings[] = $mapping; $converted = null; if (trim($data['description']) and $data['description'] <> '@@ GRADE_MAPPING_ELEMENT @@') { // prepare a fake record and re-use the upgrade logic $fakerecord = (object)$data; $converted = (array)workshopform_numerrors_upgrade_element($fakerecord, 12345678); unset($converted['workshopid']); $converted['id'] = $data['id']; $this->dimensions[] = $converted; } return $converted; } /** * Writes gathered mappings and dimensions */ public function on_elements_end() { foreach ($this->mappings as $mapping) { $this->write_xml('workshopform_numerrors_map', $mapping, array('/workshopform_numerrors_map/id')); } foreach ($this->dimensions as $dimension) { $this->write_xml('workshopform_numerrors_dimension', $dimension, array('/workshopform_numerrors_dimension/id')); } } } /** * Transforms a given record from workshop_elements_old into an object to be saved into workshopform_numerrors * * @param stdClass $old legacy record from workshop_elements_old * @param int $newworkshopid id of the new workshop instance that replaced the previous one * @return stdclass to be saved in workshopform_numerrors */ function workshopform_numerrors_upgrade_element(stdclass $old, $newworkshopid) { $new = new stdclass(); $new->workshopid = $newworkshopid; $new->sort = $old->elementno; $new->description = $old->description; $new->descriptionformat = FORMAT_HTML; $new->grade0 = get_string('grade0default', 'workshopform_numerrors'); $new->grade1 = get_string('grade1default', 'workshopform_numerrors'); // calculate new weight of the element. Negative weights are not supported any more and // are replaced with weight = 0. Legacy workshop did not store the raw weight but the index // in the array of weights (see $WORKSHOP_EWEIGHTS in workshop 1.x) // workshop 2.0 uses integer weights only (0-16) so all previous weights are multiplied by 4. switch ($old->weight) { case 8: $new->weight = 1; break; case 9: $new->weight = 2; break; case 10: $new->weight = 3; break; case 11: $new->weight = 4; break; case 12: $new->weight = 6; break; case 13: $new->weight = 8; break; case 14: $new->weight = 16; break; default: $new->weight = 0; } return $new; } PK\6vnumerrors/edit_form.phpnu[. /** * This file defines an mform to edit "Number of errors" grading strategy forms. * * @package workshopform_numerrors * @copyright 2009 David Mudrak * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); require_once(__DIR__ . '/../../lib.php'); // module library require_once(__DIR__ . '/../edit_form.php'); // parent class definition /** * Class for editing "Number of errors" grading strategy forms. * * @uses moodleform */ class workshop_edit_numerrors_strategy_form extends workshop_edit_strategy_form { /** * Define the elements to be displayed at the form * * Called by the parent::definition() * * @return void */ protected function definition_inner(&$mform) { $plugindefaults = get_config('workshopform_numerrors'); $nodimensions = $this->_customdata['nodimensions']; // number of currently filled dimensions $norepeats = $this->_customdata['norepeats']; // number of dimensions to display $descriptionopts = $this->_customdata['descriptionopts']; // wysiwyg fields options $current = $this->_customdata['current']; // current data to be set $mform->addElement('hidden', 'norepeats', $norepeats); $mform->setType('norepeats', PARAM_INT); // value not to be overridden by submitted value $mform->setConstants(array('norepeats' => $norepeats)); for ($i = 0; $i < $norepeats; $i++) { $mform->addElement('header', 'dimension'.$i, get_string('dimensionnumber', 'workshopform_numerrors', $i+1)); $mform->addElement('hidden', 'dimensionid__idx_'.$i); // the id in workshop_forms $mform->setType('dimensionid__idx_'.$i, PARAM_INT); $mform->addElement('editor', 'description__idx_'.$i.'_editor', get_string('dimensiondescription', 'workshopform_numerrors'), '', $descriptionopts); $mform->addElement('text', 'grade0__idx_'.$i, get_string('grade0', 'workshopform_numerrors'), array('size'=>'15')); $mform->setDefault('grade0__idx_'.$i, $plugindefaults->grade0); $mform->setType('grade0__idx_'.$i, PARAM_TEXT); $mform->addElement('text', 'grade1__idx_'.$i, get_string('grade1', 'workshopform_numerrors'), array('size'=>'15')); $mform->setDefault('grade1__idx_'.$i, $plugindefaults->grade1); $mform->setType('grade1__idx_'.$i, PARAM_TEXT); $mform->addElement('select', 'weight__idx_'.$i, get_string('dimensionweight', 'workshopform_numerrors'), workshop::available_dimension_weights_list()); $mform->setDefault('weight__idx_'.$i, 1); } $mform->addElement('header', 'mappingheader', get_string('grademapping', 'workshopform_numerrors')); $mform->addElement('static', 'mappinginfo', get_string('maperror', 'workshopform_numerrors'), get_string('mapgrade', 'workshopform_numerrors')); // get the total weight of all items == maximum weighted number of errors $totalweight = 0; for ($i = 0; $i < $norepeats; $i++) { if (!empty($current->{'weight__idx_'.$i})) { $totalweight += $current->{'weight__idx_'.$i}; } } $totalweight = max($totalweight, $nodimensions); $percents = array(); $percents[''] = ''; for ($i = 100; $i >= 0; $i--) { $percents[$i] = get_string('percents', 'moodle', $i); } $mform->addElement('static', 'mappingzero', 0, get_string('percents', 'moodle', 100)); for ($i = 1; $i <= $totalweight; $i++) { $selects = array(); $selects[] = $mform->createElement('select', 'map__idx_'.$i, $i, $percents); $selects[] = $mform->createElement('static', 'mapdefault__idx_'.$i, '', get_string('percents', 'moodle', floor(100 - $i * 100 / $totalweight))); $mform->addGroup($selects, 'grademapping'.$i, $i, array(' '), false); $mform->setDefault('map__idx_'.$i, ''); } $mform->registerNoSubmitButton('noadddims'); $mform->addElement('submit', 'noadddims', get_string('addmoredimensions', 'workshopform_numerrors', workshop_numerrors_strategy::ADDDIMS)); $mform->closeHeaderBefore('noadddims'); $this->set_data($current); } } PK\hDbbnumerrors/lib.phpnu[. /** * This file defines a class with "Number of errors" grading strategy logic * * @package workshopform_numerrors * @copyright 2009 David Mudrak * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); require_once(__DIR__ . '/../lib.php'); // Interface definition. require_once($CFG->libdir . '/gradelib.php'); // To handle float vs decimal issues. /** * Server workshop files * * @category files * @param stdClass $course course object * @param stdClass $cm course module object * @param stdClass $context context object * @param string $filearea file area * @param array $args extra arguments * @param bool $forcedownload whether or not force download * @param array $options additional options affecting the file serving * @return bool */ function workshopform_numerrors_pluginfile($course, $cm, $context, $filearea, array $args, $forcedownload, array $options=array()) { global $DB; if ($context->contextlevel != CONTEXT_MODULE) { return false; } require_login($course, true, $cm); if ($filearea !== 'description') { return false; } $itemid = (int)array_shift($args); // the id of the assessment form dimension if (!$workshop = $DB->get_record('workshop', array('id' => $cm->instance))) { send_file_not_found(); } if (!$dimension = $DB->get_record('workshopform_numerrors', array('id' => $itemid ,'workshopid' => $workshop->id))) { send_file_not_found(); } // TODO now make sure the user is allowed to see the file // (media embedded into the dimension description) $fs = get_file_storage(); $relativepath = implode('/', $args); $fullpath = "/$context->id/workshopform_numerrors/$filearea/$itemid/$relativepath"; if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) { return false; } // finally send the file send_stored_file($file, 0, 0, $forcedownload, $options); } /** * "Number of errors" grading strategy logic. */ class workshop_numerrors_strategy implements workshop_strategy { /** @var default number of dimensions to show */ const MINDIMS = 3; /** @var number of dimensions to add */ const ADDDIMS = 2; /** @var workshop the parent workshop instance */ protected $workshop; /** @var array definition of the assessment form fields */ protected $dimensions = null; /** @var array mapping of the number of errors to a grade */ protected $mappings = null; /** @var array options for dimension description fields */ protected $descriptionopts; /** * Constructor * * @param workshop $workshop The workshop instance record * @return void */ public function __construct(workshop $workshop) { $this->workshop = $workshop; $this->dimensions = $this->load_fields(); $this->mappings = $this->load_mappings(); $this->descriptionopts = array('trusttext' => true, 'subdirs' => false, 'maxfiles' => -1); } /** * Factory method returning an instance of an assessment form editor class * * @param $actionurl URL of form handler, defaults to auto detect the current url */ public function get_edit_strategy_form($actionurl=null) { global $CFG; // needed because the included files use it global $PAGE; require_once(__DIR__ . '/edit_form.php'); $fields = $this->prepare_form_fields($this->dimensions, $this->mappings); $nodimensions = count($this->dimensions); $norepeatsdefault = max($nodimensions + self::ADDDIMS, self::MINDIMS); $norepeats = optional_param('norepeats', $norepeatsdefault, PARAM_INT); // number of dimensions $noadddims = optional_param('noadddims', '', PARAM_ALPHA); // shall we add more? if ($noadddims) { $norepeats += self::ADDDIMS; } // Append editor context to editor options, giving preference to existing context. $this->descriptionopts = array_merge(array('context' => $PAGE->context), $this->descriptionopts); // prepare the embeded files for ($i = 0; $i < $nodimensions; $i++) { // prepare all editor elements $fields = file_prepare_standard_editor($fields, 'description__idx_'.$i, $this->descriptionopts, $PAGE->context, 'workshopform_numerrors', 'description', $fields->{'dimensionid__idx_'.$i}); } $customdata = array(); $customdata['workshop'] = $this->workshop; $customdata['strategy'] = $this; $customdata['norepeats'] = $norepeats; $customdata['nodimensions'] = $nodimensions; $customdata['descriptionopts'] = $this->descriptionopts; $customdata['current'] = $fields; $attributes = array('class' => 'editstrategyform'); return new workshop_edit_numerrors_strategy_form($actionurl, $customdata, 'post', '', $attributes); } /** * Save the assessment dimensions into database * * Saves data into the main strategy form table. If the record->id is null or zero, * new record is created. If the record->id is not empty, the existing record is updated. Records with * empty 'description' field are removed from database. * The passed data object are the raw data returned by the get_data(). * * @uses $DB * @param stdClass $data Raw data returned by the dimension editor form * @return void */ public function save_edit_strategy_form(stdclass $data) { global $DB, $PAGE; $workshopid = $data->workshopid; $norepeats = $data->norepeats; $data = $this->prepare_database_fields($data); $records = $data->numerrors; // data to be saved into {workshopform_numerrors} $mappings = $data->mappings; // data to be saved into {workshopform_numerrors_map} $todelete = array(); // dimension ids to be deleted $maxnonegative = 0; // maximum number of (weighted) negative responses for ($i=0; $i < $norepeats; $i++) { $record = $records[$i]; if (0 == strlen(trim($record->description_editor['text']))) { if (!empty($record->id)) { // existing dimension record with empty description - to be deleted $todelete[] = $record->id; } continue; } if (empty($record->id)) { // new field $record->id = $DB->insert_record('workshopform_numerrors', $record); } else { // exiting field $DB->update_record('workshopform_numerrors', $record); } $maxnonegative += $record->weight; // re-save with correct path to embeded media files $record = file_postupdate_standard_editor($record, 'description', $this->descriptionopts, $PAGE->context, 'workshopform_numerrors', 'description', $record->id); $DB->update_record('workshopform_numerrors', $record); } $this->delete_dimensions($todelete); // re-save the mappings $todelete = array(); foreach ($data->mappings as $nonegative => $grade) { if (is_null($grade)) { // no grade set for this number of negative responses $todelete[] = $nonegative; continue; } if (isset($this->mappings[$nonegative])) { $DB->set_field('workshopform_numerrors_map', 'grade', $grade, array('workshopid' => $this->workshop->id, 'nonegative' => $nonegative)); } else { $DB->insert_record('workshopform_numerrors_map', (object)array('workshopid' => $this->workshop->id, 'nonegative' => $nonegative, 'grade' => $grade)); } } // clear mappings that are not valid any more if (!empty($todelete)) { list($insql, $params) = $DB->get_in_or_equal($todelete, SQL_PARAMS_NAMED); $insql = "nonegative $insql OR "; } else { $insql = ''; } $sql = "DELETE FROM {workshopform_numerrors_map} WHERE (($insql nonegative > :maxnonegative) AND (workshopid = :workshopid))"; $params['maxnonegative'] = $maxnonegative; $params['workshopid'] = $this->workshop->id; $DB->execute($sql, $params); } /** * Factory method returning an instance of an assessment form * * @param moodle_url $actionurl URL of form handler, defaults to auto detect the current url * @param string $mode Mode to open the form in: preview/assessment * @param stdClass $assessment * @param bool $editable * @param array $options */ public function get_assessment_form(?moodle_url $actionurl=null, $mode='preview', ?stdclass $assessment=null, $editable=true, $options=array()) { global $CFG; // needed because the included files use it global $PAGE; global $DB; require_once(__DIR__ . '/assessment_form.php'); $fields = $this->prepare_form_fields($this->dimensions, $this->mappings); $nodimensions = count($this->dimensions); // rewrite URLs to the embeded files for ($i = 0; $i < $nodimensions; $i++) { $fields->{'description__idx_'.$i} = file_rewrite_pluginfile_urls($fields->{'description__idx_'.$i}, 'pluginfile.php', $PAGE->context->id, 'workshopform_numerrors', 'description', $fields->{'dimensionid__idx_'.$i}); } if ('assessment' === $mode and !empty($assessment)) { // load the previously saved assessment data $grades = $this->get_current_assessment_data($assessment); $current = new stdclass(); for ($i = 0; $i < $nodimensions; $i++) { $dimid = $fields->{'dimensionid__idx_'.$i}; if (isset($grades[$dimid])) { $current->{'gradeid__idx_'.$i} = $grades[$dimid]->id; $current->{'grade__idx_'.$i} = ($grades[$dimid]->grade == 0 ? -1 : 1); $current->{'peercomment__idx_'.$i} = $grades[$dimid]->peercomment; } } } // set up the required custom data common for all strategies $customdata['workshop'] = $this->workshop; $customdata['strategy'] = $this; $customdata['mode'] = $mode; $customdata['options'] = $options; // set up strategy-specific custom data $customdata['nodims'] = $nodimensions; $customdata['fields'] = $fields; $customdata['current'] = isset($current) ? $current : null; $attributes = array('class' => 'assessmentform numerrors'); return new workshop_numerrors_assessment_form($actionurl, $customdata, 'post', '', $attributes, $editable); } /** * Saves the filled assessment * * This method processes data submitted using the form returned by {@link get_assessment_form()} * * @param stdClass $assessment Assessment being filled * @param stdClass $data Raw data as returned by the assessment form * @return float|null Raw grade (from 0.00000 to 100.00000) for submission as suggested by the peer */ public function save_assessment(stdclass $assessment, stdclass $data) { global $DB; if (!isset($data->nodims)) { throw new coding_exception('You did not send me the number of assessment dimensions to process'); } for ($i = 0; $i < $data->nodims; $i++) { $grade = new stdclass(); $grade->id = $data->{'gradeid__idx_' . $i}; $grade->assessmentid = $assessment->id; $grade->strategy = 'numerrors'; $grade->dimensionid = $data->{'dimensionid__idx_' . $i}; $grade->grade = ($data->{'grade__idx_' . $i} <= 0 ? 0 : 1); $grade->peercomment = $data->{'peercomment__idx_' . $i}; $grade->peercommentformat = FORMAT_HTML; if (empty($grade->id)) { // new grade $grade->id = $DB->insert_record('workshop_grades', $grade); } else { // updated grade $DB->update_record('workshop_grades', $grade); } } return $this->update_peer_grade($assessment); } /** * Has the assessment form been defined and is ready to be used by the reviewers? * * @return boolean */ public function form_ready() { if (count($this->dimensions) > 0) { return true; } return false; } /** * @see parent::get_assessments_recordset() */ public function get_assessments_recordset($restrict=null) { global $DB; $sql = 'SELECT s.id AS submissionid, a.id AS assessmentid, a.weight AS assessmentweight, a.reviewerid, a.gradinggrade, g.dimensionid, g.grade FROM {workshop_submissions} s JOIN {workshop_assessments} a ON (a.submissionid = s.id) JOIN {workshop_grades} g ON (g.assessmentid = a.id AND g.strategy = :strategy) WHERE s.example=0 AND s.workshopid=:workshopid'; // to be cont. $params = array('workshopid' => $this->workshop->id, 'strategy' => $this->workshop->strategy); if (is_null($restrict)) { // update all users - no more conditions } elseif (!empty($restrict)) { list($usql, $uparams) = $DB->get_in_or_equal($restrict, SQL_PARAMS_NAMED); $sql .= " AND a.reviewerid $usql"; $params = array_merge($params, $uparams); } else { throw new coding_exception('Empty value is not a valid parameter here'); } $sql .= ' ORDER BY s.id'; // this is important for bulk processing return $DB->get_recordset_sql($sql, $params); } /** * @see parent::get_dimensions_info() */ public function get_dimensions_info() { global $DB; $params = array('workshopid' => $this->workshop->id); $dimrecords = $DB->get_records('workshopform_numerrors', array('workshopid' => $this->workshop->id), 'sort', 'id,weight'); foreach ($dimrecords as $dimid => $dimrecord) { $dimrecords[$dimid]->min = 0; $dimrecords[$dimid]->max = 1; } return $dimrecords; } /** * Is a given scale used by the instance of workshop? * * This grading strategy does not use scales. * * @param int $scaleid id of the scale to check * @param int|null $workshopid id of workshop instance to check, checks all in case of null * @return bool */ public static function scale_used($scaleid, $workshopid=null) { return false; } /** * Delete all data related to a given workshop module instance * * @see workshop_delete_instance() * @param int $workshopid id of the workshop module instance being deleted * @return void */ public static function delete_instance($workshopid) { global $DB; $DB->delete_records('workshopform_numerrors', array('workshopid' => $workshopid)); $DB->delete_records('workshopform_numerrors_map', array('workshopid' => $workshopid)); } //////////////////////////////////////////////////////////////////////////////// // Internal methods // //////////////////////////////////////////////////////////////////////////////// /** * Loads the fields of the assessment form currently used in this workshop * * @return array definition of assessment dimensions */ protected function load_fields() { global $DB; $sql = 'SELECT * FROM {workshopform_numerrors} WHERE workshopid = :workshopid ORDER BY sort'; $params = array('workshopid' => $this->workshop->id); return $DB->get_records_sql($sql, $params); } /** * Loads the mappings of the number of errors to the grade * * @return array of records */ protected function load_mappings() { global $DB; return $DB->get_records('workshopform_numerrors_map', array('workshopid' => $this->workshop->id), 'nonegative', 'nonegative,grade'); // we can use nonegative as key here as it must be unique within workshop } /** * Prepares the database data to be used by the mform * * @param array $dims Array of raw dimension records as returned by {@link load_fields()} * @param array $maps Array of raw mapping records as returned by {@link load_mappings()} * @return array Array of fields data to be used by the mform set_data */ protected function prepare_form_fields(array $dims, array $maps) { $formdata = new stdclass(); $key = 0; foreach ($dims as $dimension) { $formdata->{'dimensionid__idx_' . $key} = $dimension->id; $formdata->{'description__idx_' . $key} = $dimension->description; $formdata->{'description__idx_' . $key.'format'} = $dimension->descriptionformat; $formdata->{'grade0__idx_' . $key} = $dimension->grade0; $formdata->{'grade1__idx_' . $key} = $dimension->grade1; $formdata->{'weight__idx_' . $key} = $dimension->weight; $key++; } foreach ($maps as $nonegative => $map) { $formdata->{'map__idx_' . $nonegative} = $map->grade; } return $formdata; } /** * Deletes dimensions and removes embedded media from its descriptions * * todo we may check that there are no assessments done using these dimensions and probably remove them * * @param array $ids list to delete * @return void */ protected function delete_dimensions(array $ids) { global $DB, $PAGE; $fs = get_file_storage(); foreach ($ids as $id) { $fs->delete_area_files($PAGE->context->id, 'workshopform_numerrors', 'description', $id); } $DB->delete_records_list('workshopform_numerrors', 'id', $ids); } /** * Prepares data returned by {@link workshop_edit_numerrors_strategy_form} so they can be saved into database * * It automatically adds some columns into every record. The sorting is * done by the order of the returned array and starts with 1. * Called internally from {@link save_edit_strategy_form()} only. Could be private but * keeping protected for unit testing purposes. * * @param stdClass $raw Raw data returned by mform * @return array Array of objects to be inserted/updated in DB */ protected function prepare_database_fields(stdclass $raw) { global $PAGE; $cook = new stdclass(); // to be returned $cook->numerrors = array(); // to be stored in {workshopform_numerrors} $cook->mappings = array(); // to be stored in {workshopform_numerrors_map} for ($i = 0; $i < $raw->norepeats; $i++) { $cook->numerrors[$i] = new stdclass(); $cook->numerrors[$i]->id = $raw->{'dimensionid__idx_'.$i}; $cook->numerrors[$i]->workshopid = $this->workshop->id; $cook->numerrors[$i]->sort = $i + 1; $cook->numerrors[$i]->description_editor = $raw->{'description__idx_'.$i.'_editor'}; $cook->numerrors[$i]->grade0 = $raw->{'grade0__idx_'.$i}; $cook->numerrors[$i]->grade1 = $raw->{'grade1__idx_'.$i}; $cook->numerrors[$i]->weight = $raw->{'weight__idx_'.$i}; } $i = 1; while (isset($raw->{'map__idx_'.$i})) { if (is_numeric($raw->{'map__idx_'.$i})) { $cook->mappings[$i] = $raw->{'map__idx_'.$i}; // should be a value from 0 to 100 } else { $cook->mappings[$i] = null; // the user did not set anything } $i++; } return $cook; } /** * Returns the list of current grades filled by the reviewer * * @param stdClass $assessment Assessment record * @return array of filtered records from the table workshop_grades */ protected function get_current_assessment_data(stdclass $assessment) { global $DB; if (empty($this->dimensions)) { return array(); } list($dimsql, $dimparams) = $DB->get_in_or_equal(array_keys($this->dimensions), SQL_PARAMS_NAMED); // beware! the caller may rely on the returned array is indexed by dimensionid $sql = "SELECT dimensionid, wg.* FROM {workshop_grades} wg WHERE assessmentid = :assessmentid AND strategy= :strategy AND dimensionid $dimsql"; $params = array('assessmentid' => $assessment->id, 'strategy' => 'numerrors'); $params = array_merge($params, $dimparams); return $DB->get_records_sql($sql, $params); } /** * Aggregates the assessment form data and sets the grade for the submission given by the peer * * @param stdClass $assessment Assessment record * @return float|null Raw grade (0.00000 to 100.00000) for submission as suggested by the peer */ protected function update_peer_grade(stdclass $assessment) { $grades = $this->get_current_assessment_data($assessment); $suggested = $this->calculate_peer_grade($grades); if (!is_null($suggested)) { $this->workshop->set_peer_grade($assessment->id, $suggested); } return $suggested; } /** * Calculates the aggregated grade given by the reviewer * * @param array $grades Grade records as returned by {@link get_current_assessment_data} * @return float|null Raw grade (0.00000 to 100.00000) for submission as suggested by the peer */ protected function calculate_peer_grade(array $grades) { if (empty($grades)) { return null; } $sumerrors = 0; // sum of the weighted errors (i.e. the negative responses) foreach ($grades as $grade) { if (grade_floats_different($grade->grade, 1.00000)) { // negative reviewer's response $sumerrors += $this->dimensions[$grade->dimensionid]->weight; } } return $this->errors_to_grade($sumerrors); } /** * Returns a grade 0.00000 to 100.00000 for the given number of errors * * This is where we use the mapping table defined by the teacher. If a grade for the given * number of errors (negative assertions) is not defined, the most recently defined one is used. * Example of the defined mapping: * Number of errors | Grade * 0 | 100% (always) * 1 | - (not defined) * 2 | 80% * 3 | 60% * 4 | - * 5 | 30% * 6 | 0% * With this mapping, one error is mapped to 100% grade and 4 errors is mapped to 60%. * * @param mixed $numerrors Number of errors * @return float Raw grade (0.00000 to 100.00000) for the given number of negative assertions */ protected function errors_to_grade($numerrors) { $grade = 100.00000; for ($i = 1; $i <= $numerrors; $i++) { if (isset($this->mappings[$i])) { $grade = $this->mappings[$i]->grade; } } if ($grade > 100.00000) { $grade = 100.00000; } if ($grade < 0.00000) { $grade = 0.00000; } return grade_floatval($grade); } } PK\]numerrors/version.phpnu[. /** * Defines the version of workshop Number of errors grading strategy subplugin * * This code fragment is called by moodle_needs_upgrading() and * /admin/index.php * * @package workshopform_numerrors * @copyright 2009 David Mudrak * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2025041400; $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'workshopform_numerrors'; PK\h_Tnumerrors/assessment_form.phpnu[. /** * This file defines an mform to assess a submission by numerrors grading strategy * * @package workshopform_numerrors * @copyright 2009 David Mudrak * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); require_once(__DIR__ . '/../assessment_form.php'); // Parent class definition. /** * Class representing a form for assessing submissions by numerrors grading strategy * * @uses moodleform */ class workshop_numerrors_assessment_form extends workshop_assessment_form { /** * Define the elements to be displayed at the form * * Called by the parent::definition() * * @return void */ protected function definition_inner(&$mform) { $fields = $this->_customdata['fields']; $current = $this->_customdata['current']; $nodims = $this->_customdata['nodims']; // number of assessment dimensions $mform->addElement('hidden', 'nodims', $nodims); $mform->setType('nodims', PARAM_INT); for ($i = 0; $i < $nodims; $i++) { // dimension header $dimtitle = get_string('dimensionnumber', 'workshopform_numerrors', $i+1); $mform->addElement('header', "dimensionhdr__idx_$i", $dimtitle); // dimension id $mform->addElement('hidden', 'dimensionid__idx_'.$i, $fields->{'dimensionid__idx_'.$i}); $mform->setType('dimensionid__idx_'.$i, PARAM_INT); // grade id $mform->addElement('hidden', 'gradeid__idx_'.$i); // value set by set_data() later $mform->setType('gradeid__idx_'.$i, PARAM_INT); // dimension description $desc = '
'."\n"; $desc .= format_text($fields->{'description__idx_'.$i}, $fields->{'description__idx_'.$i.'format'}); $desc .= "\n
"; $mform->addElement('html', $desc); // evaluation of the assertion $label = get_string('dimensiongrade', 'workshopform_numerrors'); $mform->addGroup(array( $mform->createElement('radio', 'grade__idx_' . $i, '', $fields->{'grade0__idx_'.$i}, -1), $mform->createElement('radio', 'grade__idx_' . $i, '', $fields->{'grade1__idx_'.$i}, 1), ), 'group_grade__idx_' . $i, get_string('yourassessmentfor', 'workshop', $dimtitle), '
', false); $mform->addRule('group_grade__idx_' . $i, get_string('required'), 'required'); // comment $label = get_string('dimensioncommentfor', 'workshopform_numerrors', $dimtitle); $mform->addElement('textarea', 'peercomment__idx_' . $i, $label, array('cols' => 60, 'rows' => 5)); } $this->set_data($current); } } PK\jBBB&numerrors/classes/privacy/provider.phpnu[. /** * Provides the class {@link workshopform_numerrors\privacy\provider} * * @package workshopform_numerrors * @category privacy * @copyright 2018 David Mudrák * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace workshopform_numerrors\privacy; use core_privacy\local\request\writer; defined('MOODLE_INTERNAL') || die(); /** * Privacy API implementation for the Number of errors strategy. * * @copyright 2018 David Mudrák * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\null_provider, \mod_workshop\privacy\workshopform_provider { /** * Explain that this plugin stores no personal data. * * @return string */ public static function get_reason(): string { return 'privacy:metadata'; } /** * Return details of the filled assessment form. * * @param stdClass $user User we are exporting data for * @param context $context The workshop activity context * @param array $subcontext Subcontext within the context to export to * @param int $assessmentid ID of the assessment */ public static function export_assessment_form(\stdClass $user, \context $context, array $subcontext, int $assessmentid) { global $DB; if ($context->contextlevel != CONTEXT_MODULE) { throw new \coding_exception('Unexpected context provided'); } $sql = "SELECT dim.id, dim.workshopid, dim.description, dim.descriptionformat, dim.grade0, dim.grade1, dim.weight, wg.grade, wg.peercomment, wg.peercommentformat FROM {course_modules} cm JOIN {context} ctx ON ctx.contextlevel = :contextlevel AND ctx.instanceid = cm.id JOIN {workshop} w ON cm.instance = w.id JOIN {workshopform_numerrors} dim ON dim.workshopid = w.id LEFT JOIN {workshop_grades} wg ON wg.strategy = :strategy AND wg.dimensionid = dim.id AND wg.assessmentid = :assessmentid WHERE ctx.id = :contextid ORDER BY dim.sort"; $params = [ 'strategy' => 'numerrors', 'contextlevel' => CONTEXT_MODULE, 'contextid' => $context->id, 'assessmentid' => $assessmentid, ]; $writer = \core_privacy\local\request\writer::with_context($context); $data = []; $workshopid = null; $hasdata = false; $dimensionids = []; foreach ($DB->get_records_sql($sql, $params) as $record) { if ($record->grade !== null) { $hasdata = true; } $record->description = $writer->rewrite_pluginfile_urls($subcontext, 'workshopform_numerrors', 'description', $record->id, $record->description); $workshopid = $record->workshopid; $dimensionids[] = $record->id; unset($record->id); unset($record->workshopid); $data[] = $record; } if ($hasdata) { $writer->export_data($subcontext, (object)['assertions' => $data]); foreach ($dimensionids as $dimensionid) { $writer->export_area_files($subcontext, 'workshopform_numerrors', 'description', $dimensionid); } foreach ($DB->get_records('workshopform_numerrors_map', ['workshopid' => $workshopid], 'nonegative', 'id, nonegative, grade') as $mapping) { $writer->export_metadata($subcontext, 'map_'.$mapping->nonegative.'_errors', $mapping->grade, get_string('privacy:export:metadata:map', 'workshopform_numerrors', [ 'nonegative' => $mapping->nonegative, 'grade' => $mapping->grade ]) ); } } } } PK\Ѵj)numerrors/tests/privacy/provider_test.phpnu[. /** * Provides the {@see workshopform_numerrors\privacy\provider_test} class. * * @package workshopform_numerrors * @category test * @copyright 2018 David Mudrák * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace workshopform_numerrors\privacy; defined('MOODLE_INTERNAL') || die(); global $CFG; use core_privacy\local\request\writer; use core_privacy\tests\provider_testcase; /** * Unit tests for the privacy API implementation. * * @copyright 2018 David Mudrák * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class provider_test extends provider_testcase { /** @var \testing_data_generator data generator. */ protected $generator; /** @var \mod_workshop_generator workshop generator. */ protected $workshopgenerator; /** @var \stdClass course data. */ protected $course1; /** @var \stdClass student data. */ protected $student1; /** @var \stdClass student data. */ protected $student2; /** @var \stdClass first workshop in course1 */ protected $workshop11; /** @var int ID of the submission in workshop11 by student1 */ protected $submission111; /** @var int ID of the assessment of submission111 by student2 */ protected $assessment1112; /** @var bool|int true or new id */ protected $dim1; /** @var bool|int true or new id */ protected $dim2; /** * Test {@link workshopform_numerrors\privacy\provider::export_assessment_form()} implementation. */ public function test_export_assessment_form(): void { global $DB; $this->resetAfterTest(); $this->setAdminUser(); $this->generator = $this->getDataGenerator(); $this->workshopgenerator = $this->generator->get_plugin_generator('mod_workshop'); $this->course1 = $this->generator->create_course(); $this->workshop11 = $this->generator->create_module('workshop', [ 'course' => $this->course1, 'name' => 'Workshop11', ]); $DB->set_field('workshop', 'phase', 50, ['id' => $this->workshop11->id]); $this->dim1 = $DB->insert_record('workshopform_numerrors', [ 'workshopid' => $this->workshop11->id, 'sort' => 1, 'description' => 'Assertion 1 description', 'descriptionformat' => FORMAT_MARKDOWN, 'descriptiontrust' => 0, 'grade0' => 'No', 'grade1' => 'Yes', 'weight' => 1, ]); $this->dim2 = $DB->insert_record('workshopform_numerrors', [ 'workshopid' => $this->workshop11->id, 'sort' => 2, 'description' => 'Assertion 2 description', 'descriptionformat' => FORMAT_MARKDOWN, 'descriptiontrust' => 0, 'grade0' => 'Missing', 'grade1' => 'Present', 'weight' => 1, ]); $this->student1 = $this->generator->create_user(); $this->student2 = $this->generator->create_user(); $this->submission111 = $this->workshopgenerator->create_submission($this->workshop11->id, $this->student1->id); $this->assessment1112 = $this->workshopgenerator->create_assessment($this->submission111, $this->student2->id, [ 'grade' => 92, ]); $DB->insert_record('workshop_grades', [ 'assessmentid' => $this->assessment1112, 'strategy' => 'numerrors', 'dimensionid' => $this->dim1, 'grade' => 1, 'peercomment' => 'Awesome', 'peercommentformat' => FORMAT_PLAIN, ]); $DB->insert_record('workshop_grades', [ 'assessmentid' => $this->assessment1112, 'strategy' => 'numerrors', 'dimensionid' => $this->dim2, 'grade' => 0, 'peercomment' => 'Missing', 'peercommentformat' => FORMAT_PLAIN, ]); $contextlist = new \core_privacy\local\request\approved_contextlist($this->student2, 'mod_workshop', [ \context_module::instance($this->workshop11->cmid)->id, ]); \mod_workshop\privacy\provider::export_user_data($contextlist); $writer = writer::with_context(\context_module::instance($this->workshop11->cmid)); $form = $writer->get_data([ get_string('myassessments', 'mod_workshop'), $this->assessment1112, get_string('assessmentform', 'mod_workshop'), get_string('pluginname', 'workshopform_numerrors'), ]); $this->assertEquals('Assertion 1 description', $form->assertions[0]->description); $this->assertEquals(0, $form->assertions[1]->grade); $this->assertEquals('Missing', $form->assertions[1]->peercomment); } } PK\lt((numerrors/tests/lib_test.phpnu[. /** * Unit tests for Number of errors grading logic * * @package workshopform_numerrors * @category test * @copyright 2009 David Mudrak * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace workshopform_numerrors; use workshop; use workshop_numerrors_strategy; defined('MOODLE_INTERNAL') || die(); // Include the code to test global $CFG; require_once($CFG->dirroot . '/mod/workshop/locallib.php'); require_once($CFG->dirroot . '/mod/workshop/form/numerrors/lib.php'); /** * Unit tests for Number of errors grading lib.php */ final class lib_test extends \advanced_testcase { /** workshop instance emulation */ protected $workshop; /** instance of the strategy logic class being tested */ protected $strategy; /** * Setup testing environment */ protected function setUp(): void { parent::setUp(); $this->resetAfterTest(); $this->setAdminUser(); $course = $this->getDataGenerator()->create_course(); $workshop = $this->getDataGenerator()->create_module('workshop', array('strategy' => 'numerrors', 'course' => $course)); $cm = get_fast_modinfo($course)->instances['workshop'][$workshop->id]; $this->workshop = new workshop($workshop, $cm, $course); $this->strategy = new testable_workshop_numerrors_strategy($this->workshop); } protected function tearDown(): void { $this->workshop = null; $this->strategy = null; parent::tearDown(); } public function test_calculate_peer_grade_null_grade(): void { // fixture set-up $this->strategy->dimensions = array(); $this->strategy->mappings = array(); $grades = array(); // exercise SUT $suggested = $this->strategy->calculate_peer_grade($grades); // validate $this->assertNull($suggested); } public function test_calculate_peer_grade_no_error(): void { // fixture set-up $this->strategy->dimensions = array(); $this->strategy->dimensions[108] = (object)array('weight' => '1'); $this->strategy->dimensions[109] = (object)array('weight' => '1'); $this->strategy->dimensions[111] = (object)array('weight' => '1'); $this->strategy->mappings = array(); $grades = array(); $grades[] = (object)array('dimensionid' => 108, 'grade' => '1.00000'); $grades[] = (object)array('dimensionid' => 111, 'grade' => '1.00000'); $grades[] = (object)array('dimensionid' => 109, 'grade' => '1.00000'); // exercise SUT $suggested = $this->strategy->calculate_peer_grade($grades); // validate $this->assertEquals($suggested, 100.00000); } public function test_calculate_peer_grade_one_error(): void { // fixture set-up $this->strategy->dimensions = array(); $this->strategy->dimensions[108] = (object)array('weight' => '1'); $this->strategy->dimensions[109] = (object)array('weight' => '1'); $this->strategy->dimensions[111] = (object)array('weight' => '1'); $this->strategy->mappings = array( 1 => (object)array('grade' => '80.00000'), 2 => (object)array('grade' => '60.00000'), ); $grades = array(); $grades[] = (object)array('dimensionid' => 108, 'grade' => '1.00000'); $grades[] = (object)array('dimensionid' => 111, 'grade' => '0.00000'); $grades[] = (object)array('dimensionid' => 109, 'grade' => '1.00000'); // exercise SUT $suggested = $this->strategy->calculate_peer_grade($grades); // validate $this->assertEquals($suggested, 80.00000); } public function test_calculate_peer_grade_three_errors_same_weight_a(): void { // fixture set-up $this->strategy->dimensions = array(); $this->strategy->dimensions[108] = (object)array('weight' => '1.00000'); $this->strategy->dimensions[109] = (object)array('weight' => '1.00000'); $this->strategy->dimensions[111] = (object)array('weight' => '1.00000'); $this->strategy->mappings = array( 1 => (object)array('grade' => '80.00000'), 2 => (object)array('grade' => '60.00000'), 3 => (object)array('grade' => '10.00000'), ); $grades = array(); $grades[] = (object)array('dimensionid' => 108, 'grade' => '0.00000'); $grades[] = (object)array('dimensionid' => 111, 'grade' => '0.00000'); $grades[] = (object)array('dimensionid' => 109, 'grade' => '0.00000'); // exercise SUT $suggested = $this->strategy->calculate_peer_grade($grades); // validate $this->assertEquals($suggested, 10.00000); } public function test_calculate_peer_grade_three_errors_same_weight_b(): void { // fixture set-up $this->strategy->dimensions = array(); $this->strategy->dimensions[108] = (object)array('weight' => '1.00000'); $this->strategy->dimensions[109] = (object)array('weight' => '1.00000'); $this->strategy->dimensions[111] = (object)array('weight' => '1.00000'); $this->strategy->mappings = array( 1 => (object)array('grade' => '80.00000'), 2 => (object)array('grade' => '60.00000'), 3 => (object)array('grade' => '0.00000'), ); $grades = array(); $grades[] = (object)array('dimensionid' => 108, 'grade' => '0.00000'); $grades[] = (object)array('dimensionid' => 111, 'grade' => '0.00000'); $grades[] = (object)array('dimensionid' => 109, 'grade' => '0.00000'); // exercise SUT $suggested = $this->strategy->calculate_peer_grade($grades); // validate $this->assertEquals($suggested, 0.00000); } public function test_calculate_peer_grade_one_error_weighted(): void { // fixture set-up $this->strategy->dimensions = array(); $this->strategy->dimensions[108] = (object)array('weight' => '1'); $this->strategy->dimensions[109] = (object)array('weight' => '2'); $this->strategy->dimensions[111] = (object)array('weight' => '0'); $this->strategy->mappings = array( 1 => (object)array('grade' => '66.00000'), 2 => (object)array('grade' => '33.00000'), 3 => (object)array('grade' => '0.00000'), ); $grades = array(); $grades[] = (object)array('dimensionid' => 108, 'grade' => '1.00000'); $grades[] = (object)array('dimensionid' => 111, 'grade' => '1.00000'); $grades[] = (object)array('dimensionid' => 109, 'grade' => '0.00000'); // exercise SUT $suggested = $this->strategy->calculate_peer_grade($grades); // validate $this->assertEquals($suggested, 33.00000); } public function test_calculate_peer_grade_zero_weight(): void { // fixture set-up $this->strategy->dimensions = array(); $this->strategy->dimensions[108] = (object)array('weight' => '1'); $this->strategy->dimensions[109] = (object)array('weight' => '2'); $this->strategy->dimensions[111] = (object)array('weight' => '0'); $this->strategy->mappings = array( 1 => (object)array('grade' => '66.00000'), 2 => (object)array('grade' => '33.00000'), 3 => (object)array('grade' => '0.00000'), ); $grades = array(); $grades[] = (object)array('dimensionid' => 108, 'grade' => '1.00000'); $grades[] = (object)array('dimensionid' => 111, 'grade' => '0.00000'); $grades[] = (object)array('dimensionid' => 109, 'grade' => '1.00000'); // exercise SUT $suggested = $this->strategy->calculate_peer_grade($grades); // validate $this->assertEquals($suggested, 100.00000); } public function test_calculate_peer_grade_sum_weight(): void { // fixture set-up $this->strategy->dimensions = array(); $this->strategy->dimensions[108] = (object)array('weight' => '1'); $this->strategy->dimensions[109] = (object)array('weight' => '2'); $this->strategy->dimensions[111] = (object)array('weight' => '3'); $this->strategy->mappings = array( 1 => (object)array('grade' => '90.00000'), 2 => (object)array('grade' => '80.00000'), 3 => (object)array('grade' => '70.00000'), 4 => (object)array('grade' => '60.00000'), 5 => (object)array('grade' => '30.00000'), 6 => (object)array('grade' => '5.00000'), 7 => (object)array('grade' => '0.00000'), ); $grades = array(); $grades[] = (object)array('dimensionid' => 108, 'grade' => '0.00000'); $grades[] = (object)array('dimensionid' => 111, 'grade' => '0.00000'); $grades[] = (object)array('dimensionid' => 109, 'grade' => '0.00000'); // exercise SUT $suggested = $this->strategy->calculate_peer_grade($grades); // validate $this->assertEquals($suggested, 5.00000); } } /** * Test subclass that makes all the protected methods we want to test public */ class testable_workshop_numerrors_strategy extends workshop_numerrors_strategy { /** allows to set dimensions manually */ public $dimensions = array(); /** allow to set mappings manually */ public $mappings = array(); /** * This is where the calculation of suggested grade for submission is done */ public function calculate_peer_grade(array $grades) { return parent::calculate_peer_grade($grades); } } PK\yޒnumerrors/settings.phpnu[. /** * The configuration variables for "Number of errors" grading strategy * * The values defined here are often used as defaults for all module instances. * * @package workshopform_numerrors * @copyright 2009 David Mudrak * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $settings->add(new admin_setting_configtext('workshopform_numerrors/grade0', get_string('grade0', 'workshopform_numerrors'), get_string('configgrade0', 'workshopform_numerrors'), get_string('grade0default', 'workshopform_numerrors'), $paramtype=PARAM_TEXT, $size=15)); $settings->add(new admin_setting_configtext('workshopform_numerrors/grade1', get_string('grade1', 'workshopform_numerrors'), get_string('configgrade1', 'workshopform_numerrors'), get_string('grade1default', 'workshopform_numerrors'), $paramtype=PARAM_TEXT, $size=15)); PK\?%RR edit_form.phpnu[. /** * This file defines a base class for all grading strategy editing forms. * * @package mod_workshop * @copyright 2009 David Mudrak * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); require_once($CFG->libdir . '/formslib.php'); // parent class definition /** * Base class for editing all the strategy grading forms. * * This defines the common fields that all strategy grading forms need. * Strategies should define their own class that inherits from this one, and * implements the definition_inner() method. * * @uses moodleform */ class workshop_edit_strategy_form extends moodleform { /** strategy logic instance that this class is editor of */ protected $strategy; /** @var workshop workshop. */ protected $workshop; /** * Add the fields that are common for all grading strategies. * * If the strategy does not support all these fields, then you can override * this method and remove the ones you don't want with * $mform->removeElement(). * Stretegy subclassess should define their own fields in definition_inner() * * @return void */ public function definition() { global $CFG; $mform = $this->_form; $this->workshop = $this->_customdata['workshop']; $this->strategy = $this->_customdata['strategy']; $mform->addElement('hidden', 'workshopid', $this->workshop->id); // workshopid $mform->setType('workshopid', PARAM_INT); $mform->addElement('hidden', 'strategy', $this->workshop->strategy); // strategy name $mform->setType('strategy', PARAM_PLUGIN); $this->definition_inner($mform); // todo - tags support //if (!empty($CFG->usetags)) { // $mform->addElement('header', 'tagsheader', get_string('tags')); // $mform->addElement('tags', 'tags', get_string('tags')); //} $buttonarray = array(); $buttonarray[] = $mform->createElement('submit', 'saveandcontinue', get_string('saveandcontinue', 'workshop')); $buttonarray[] = $mform->createElement('submit', 'saveandpreview', get_string('saveandpreview', 'workshop')); $buttonarray[] = $mform->createElement('submit', 'saveandclose', get_string('saveandclose', 'workshop')); $buttonarray[] = $mform->createElement('cancel'); $mform->addGroup($buttonarray, 'buttonar', '', array(' '), false); $mform->closeHeaderBefore('buttonar'); } /** * Validate the submitted form data. * * Grading strategy plugins can provide their own validation rules by * overriding the {@link self::validation_inner()} method. * * @param array $data * @param array $files * @return array */ final public function validation($data, $files) { return array_merge( parent::validation($data, $files), $this->validation_inner($data, $files) ); } /** * Add any strategy specific form fields. * * @param stdClass $mform the form being built. */ protected function definition_inner(&$mform) { // By default, do nothing. } /** * Add strategy specific validation rules. * * @param array $data * @param array $files * @return array */ protected function validation_inner($data, $files) { return array(); } } PK\RTlib.phpnu[. /** * This file defines interface of all grading strategy logic classes * * @package mod_workshop * @copyright 2009 David Mudrak * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); /** * Strategy interface defines all methods that strategy subplugins has to implement */ interface workshop_strategy { /** * Factory method returning a form that is used to define the assessment form * * @param string $actionurl URL of the action handler script, defaults to auto detect * @return stdclass The instance of the assessment form editor class */ public function get_edit_strategy_form($actionurl=null); /** * Saves the assessment dimensions and other grading form elements * * Assessment dimension (also know as assessment element) represents one aspect or criterion * to be evaluated. Each dimension consists of a set of form fields. Strategy-specific information * are saved in workshopform_{strategyname} tables. * * @param stdClass $data Raw data as returned by the form editor * @return void */ public function save_edit_strategy_form(stdclass $data); /** * 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 If opening in the assessment mode, the current assessment record * @param bool $editable Shall the form be opened as editable (true) or read-only (false) * @param array $options More assessment form options, editableweight implemented only now */ public function get_assessment_form(?moodle_url $actionurl=null, $mode='preview', ?stdclass $assessment=null, $editable=true, $options=array()); /** * Saves the filled assessment and returns the grade for submission as suggested by the reviewer * * This method processes data submitted using the form returned by {@link get_assessment_form()} * The returned grade should be rounded to 5 decimals as with round($grade, 5). * * @see grade_floatval() * @param stdClass $assessment Assessment being filled * @param stdClass $data Raw data as returned by the assessment form * @return float|null Raw percentual grade (0.00000 to 100.00000) for submission * as suggested by the peer or null if impossible to count */ public function save_assessment(stdclass $assessment, stdclass $data); /** * Has the assessment form been defined and is ready to be used by the reviewers? * * @return boolean */ public function form_ready(); /** * Returns a general information about the assessment dimensions * * @return array [dimid] => stdclass (->id ->max ->min ->weight and optionally ->scale containing scale items) */ public function get_dimensions_info(); /** * Returns recordset with detailed information of all assessments done using this strategy * * The returned structure must be a recordset of objects containing at least properties: * submissionid, assessmentid, assessmentweight, reviewerid, gradinggrade, dimensionid and grade. * It is possible to pass user id(s) of reviewer(s). Then, the method returns just the reviewer's * assessments info. * * @param array|int|null $restrict optional id or ids of the reviewer * @return moodle_recordset */ public function get_assessments_recordset($restrict=null); /** * Is a given scale used by the instance of workshop? * * If the grading strategy does not use scales, it should just return false. If the strategy * supports scales, it returns true if the given scale is used. * If workshopid is null, it checks for any workshop instance. If workshopid is provided, * it checks the given instance only. * * @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); /** * Delete all data related to a given workshop module instance * * This is called from {@link workshop_delete_instance()}. * * @param int $workshopid id of the workshop module instance being deleted * @return void */ public static function delete_instance($workshopid); } PK\q*xxaccumulative/db/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\_4accumulative/db/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 Iaccumulative/db/install.xmlnu[
PK\[\  2accumulative/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\ӡPaccumulative/backup/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&Qaccumulative/backup/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%#accumulative/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? ? accumulative/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\ ː]]accumulative/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\accumulative/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\t accumulative/assessment_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\IdU)accumulative/classes/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\CZ,accumulative/tests/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ð""accumulative/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\assessment_form.phpnu[. /** * This file defines a base class for all assessment forms * * @package mod_workshop * @copyright 2009 David Mudrak * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); require_once($CFG->libdir . '/formslib.php'); // parent class definition /** * Base class for all assessment forms * * This defines the common fields that all assessment forms need. * Strategies should define their own class that inherits from this one, and * implements the definition_inner() method. * * @uses moodleform */ class workshop_assessment_form extends moodleform { /** @var string Mode to open the form in: preview/assessment. */ public $mode; /** @var workshop_strategy workshop strategy */ public $strategy; /** @var workshop workshop. */ public $workshop; /** @var array options. */ public $options; /** * Add the fields that are common for all grading strategies. * * If the strategy does not support all these fields, then you can override * this method and remove the ones you don't want with * $mform->removeElement(). * Strategy subclassess should define their own fields in definition_inner() * * @return void */ public function definition() { global $CFG; $mform = $this->_form; $this->mode = $this->_customdata['mode']; // influences the save buttons $this->strategy = $this->_customdata['strategy']; // instance of the strategy api class $this->workshop = $this->_customdata['workshop']; // instance of the workshop api class $this->options = $this->_customdata['options']; // array with addiotional options // Disable shortforms $mform->setDisableShortforms(); // add the strategy-specific fields $this->definition_inner($mform); // add the data common for all subplugins $mform->addElement('hidden', 'strategy', $this->workshop->strategy); $mform->setType('strategy', PARAM_PLUGIN); if ($this->workshop->overallfeedbackmode and $this->is_editable()) { $mform->addElement('header', 'overallfeedbacksection', get_string('overallfeedback', 'mod_workshop')); $mform->addElement('editor', 'feedbackauthor_editor', get_string('feedbackauthor', 'mod_workshop'), null, $this->workshop->overall_feedback_content_options()); if ($this->workshop->overallfeedbackmode == 2) { $mform->addRule('feedbackauthor_editor', null, 'required', null, 'client'); } if ($this->workshop->overallfeedbackfiles) { $mform->addElement('filemanager', 'feedbackauthorattachment_filemanager', get_string('feedbackauthorattachment', 'mod_workshop'), null, $this->workshop->overall_feedback_attachment_options()); } } if (!empty($this->options['editableweight']) and $this->is_editable()) { $mform->addElement('header', 'assessmentsettings', get_string('assessmentweight', 'workshop')); $mform->addElement('select', 'weight', get_string('assessmentweight', 'workshop'), workshop::available_assessment_weights_list()); $mform->setDefault('weight', 1); } $buttonarray = array(); if ($this->mode == 'preview') { $buttonarray[] = $mform->createElement('cancel', 'backtoeditform', get_string('backtoeditform', 'workshop')); } if ($this->mode == 'assessment') { if (!empty($this->options['pending'])) { $buttonarray[] = $mform->createElement('submit', 'saveandshownext', get_string('saveandshownext', 'workshop')); } $buttonarray[] = $mform->createElement('submit', 'saveandclose', get_string('saveandclose', 'workshop')); $buttonarray[] = $mform->createElement('submit', 'saveandcontinue', get_string('saveandcontinue', 'workshop')); $buttonarray[] = $mform->createElement('cancel'); } $mform->addGroup($buttonarray, 'buttonar', '', array(' '), false); $mform->closeHeaderBefore('buttonar'); } /** * Add any strategy specific form fields. * * @param stdClass $mform the form being built. */ protected function definition_inner(&$mform) { // By default, do nothing. } /** * Is the form frozen (read-only)? * * @return boolean */ public function is_editable() { return !$this->_form->isFrozen(); } /** * Return the form custom data. * * @return array an array containing the custom data * @since Moodle 3.4 */ public function get_customdata() { return $this->_customdata; } } PK\  rubric/styles.cssnu[/* 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; } PK\rllrubric/db/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_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; } PK\#C5rubric/db/upgrade.phpnu[. /** * 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; } PK\ĥ rubric/db/install.xmlnu[
PK\aAA&rubric/lang/en/workshopform_rubric.phpnu[. /** * 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.'; PK\-Erubric/backup/moodle2/restore_workshopform_rubric_subplugin.class.phpnu[. /** * @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); } } PK\O%Drubric/backup/moodle2/backup_workshopform_rubric_subplugin.class.phpnu[. /** * @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; } } PK\skrubric/backup/moodle1/lib.phpnu[. /** * 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; } PK\arubric/edit_form.phpnu[. /** * 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; } } PK\Gffrubric/lib.phpnu[. /** * 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; } } } PK\o:$rubric/version.phpnu[. /** * 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'; PK\Hrubric/assessment_form.phpnu[. /** * 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); } } PK\\;--#rubric/classes/privacy/provider.phpnu[. /** * 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')); } } } PK\|&rubric/tests/privacy/provider_test.phpnu[. /** * 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); } } PK\Ʃ}ZZrubric/tests/lib_test.phpnu[. /** * 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); } } PK\.#. /** * This file replaces the legacy STATEMENTS section in db/install.xml, * lib.php/modulename_install() post installation hook and partially defaults.php * * @package workshopform_comments * @copyright 2010 David Mudrak * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ /** * Post installation procedure */ function xmldb_workshopform_comments_install() { global $CFG, $DB; } PK\zcomments/db/upgrade.phpnu[. /** * Keeps track of upgrades to the workshop comments grading strategy * * @package workshopform_comments * @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_comments_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\L~comments/db/install.xmlnu[
PK\b*comments/lang/en/workshopform_comments.phpnu[. /** * Strings for component 'workshopform_comments', language 'en', branch 'MOODLE_20_STABLE' * * @package workshopform_comments * @copyright 2009 David Mudrak * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['addmoredimensions'] = 'Blanks for {$a} more aspects'; $string['dimensioncommentfor'] = 'Comment for {$a}'; $string['dimensiondescription'] = 'Description'; $string['dimensionnumber'] = 'Aspect {$a}'; $string['pluginname'] = 'Comments'; $string['privacy:metadata'] = 'The Comments 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.'; PK\bHcomments/backup/moodle2/backup_workshopform_comments_subplugin.class.phpnu[. /** * @package workshopform_comments * @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 comments grading strategy information */ class backup_workshopform_comments_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_comments_dimension', array('id'), array( 'sort', 'description', 'descriptionformat')); // 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_comments', array('workshopid' => backup::VAR_ACTIVITYID)); // file annotations $subplugindimension->annotate_files('workshopform_comments', '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_comments_referencegrade'); } /** * Returns the dimension grades to attach to 'exampleassessment' XML element */ protected function define_exampleassessment_subplugin_structure() { return $this->dimension_grades_structure('workshopform_comments_examplegrade'); } /** * Returns the dimension grades to attach to 'assessment' XML element */ protected function define_assessment_subplugin_structure() { return $this->dimension_grades_structure('workshopform_comments_grade'); } //////////////////////////////////////////////////////////////////////////// // internal private methods //////////////////////////////////////////////////////////////////////////// /** * Returns the structure of dimension grades * * @param string forst parameter of {@link backup_nested_element} constructor */ private function dimension_grades_structure($elementname) { // create XML elements $subplugin = $this->get_subplugin_element(); // virtual optigroup element $subpluginwrapper = new backup_nested_element($this->get_recommended_name()); $subplugingrade = new backup_nested_element($elementname, array('id'), array( 'dimensionid', '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, peercomment, peercommentformat FROM {workshop_grades} WHERE strategy = 'comments' AND assessmentid = ?", array(backup::VAR_PARENTID)); return $subplugin; } } PK\ ;??Icomments/backup/moodle2/restore_workshopform_comments_subplugin.class.phpnu[. /** * @package workshopform_comments * @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_comments subplugin. */ class restore_workshopform_comments_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_comments_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_comments_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_comments_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_comments_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_comments_dimension element */ public function process_workshopform_comments_dimension($data) { global $DB; $data = (object)$data; $oldid = $data->id; $data->workshopid = $this->get_new_parentid('workshop'); $newitemid = $DB->insert_record('workshopform_comments', $data); $this->set_mapping($this->get_namefor('dimension'), $oldid, $newitemid, true); // Process files for this workshopform_comments->id only $this->add_related_files('workshopform_comments', 'description', $this->get_namefor('dimension'), null, $oldid); } /** * Processes the workshopform_comments_referencegrade element */ public function process_workshopform_comments_referencegrade($data) { $this->process_dimension_grades_structure('workshop_referenceassessment', $data); } /** * Processes the workshopform_comments_examplegrade element */ public function process_workshopform_comments_examplegrade($data) { $this->process_dimension_grades_structure('workshop_exampleassessment', $data); } /** * Processes the workshopform_comments_grade element */ public function process_workshopform_comments_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 = 'comments'; $data->grade = 100.00000; $data->dimensionid = $this->get_mappingid($this->get_namefor('dimension'), $data->dimensionid); $DB->insert_record('workshop_grades', $data); } } PK\2 comments/backup/moodle1/lib.phpnu[. /** * Provides support for the conversion of moodle1 backup to the moodle2 format * * @package workshopform_comments * @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 comments grading strategy data */ class moodle1_workshopform_comments_handler extends moodle1_workshopform_handler { /** * Converts into * * @param array $data legacy element data * @param array $raw raw element data * * @return array converted */ public function process_legacy_element(array $data, array $raw) { // prepare a fake record and re-use the upgrade logic $fakerecord = (object)$data; $converted = (array)workshopform_comments_upgrade_element($fakerecord, 12345678); unset($converted['workshopid']); $converted['id'] = $data['id']; $this->write_xml('workshopform_comments_dimension', $converted, array('/workshopform_comments_dimension/id')); return $converted; } } /** * Transforms a given record from workshop_elements_old into an object to be saved into workshopform_comments * * @param stdClass $old legacy record from workshop_elements_old * @param int $newworkshopid id of the new workshop instance that replaced the previous one * @return stdclass to be saved in workshopform_comments */ function workshopform_comments_upgrade_element(stdclass $old, $newworkshopid) { $new = new stdclass(); $new->workshopid = $newworkshopid; $new->sort = $old->elementno; $new->description = $old->description; $new->descriptionformat = FORMAT_HTML; return $new; } PK\೘ comments/edit_form.phpnu[. /** * This file defines an mform to edit comments grading strategy forms. * * @package workshopform_comments * @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 comments grading strategy forms. * * @uses moodleform */ class workshop_edit_comments_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_comments', $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_comments'), '', $descriptionopts); } $mform->registerNoSubmitButton('noadddims'); $mform->addElement('submit', 'noadddims', get_string('addmoredimensions', 'workshopform_comments', workshop_comments_strategy::ADDDIMS)); $mform->closeHeaderBefore('noadddims'); $this->set_data($current); } } PK\,IIcomments/lib.phpnu[. /** * This file defines a class with comments grading strategy logic * * @package workshopform_comments * @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_comments_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_comments', 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_comments/$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_comments_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 embedded 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_comments', '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_comments_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->comments; // records to be saved into {workshopform_comments} $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_comments', $record); } else { // exiting field $DB->update_record('workshopform_comments', $record); } // re-save with correct path to embedded media files $record = file_postupdate_standard_editor($record, 'description', $this->descriptionopts, $PAGE->context, 'workshopform_comments', 'description', $record->id); $DB->update_record('workshopform_comments', $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 embedded 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_comments', '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->{'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 comments'); return new workshop_comments_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 Constant raw grade 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 = 'comments'; $grade->dimensionid = $data->{'dimensionid__idx_' . $i}; $grade->grade = 100.00000; $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); } } $this->workshop->set_peer_grade($assessment->id, 100.00000); return 100.0000; } /** * 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, 100.00000 AS grade FROM {workshop_submissions} s JOIN {workshop_assessments} a ON (a.submissionid = s.id) JOIN {workshop_grades} g ON (g.assessmentid = a.id AND g.strategy = :strategy) WHERE s.example=0 AND s.workshopid=:workshopid'; // to be cont. $params = array('workshopid' => $this->workshop->id, 'strategy' => $this->workshop->strategy); if (is_null($restrict)) { // update all users - no more conditions } elseif (!empty($restrict)) { list($usql, $uparams) = $DB->get_in_or_equal($restrict, SQL_PARAMS_NAMED); $sql .= " AND a.reviewerid $usql"; $params = array_merge($params, $uparams); } else { throw new coding_exception('Empty value is not a valid parameter here'); } $sql .= ' ORDER BY s.id'; // this is important for bulk processing return $DB->get_recordset_sql($sql, $params); } /** * @see parent::get_dimensions_info() */ public function get_dimensions_info() { global $DB; $params = array('workshopid' => $this->workshop->id); $dimrecords = $DB->get_records('workshopform_comments', array('workshopid' => $this->workshop->id), 'sort', 'id'); $diminfo = array(); foreach ($dimrecords as $dimid => $dimrecord) { $diminfo[$dimid] = new stdclass(); $diminfo[$dimid]->id = $dimid; $diminfo[$dimid]->weight = 1; $diminfo[$dimid]->min = 100; $diminfo[$dimid]->max = 100; } return $diminfo; } /** * Is a given scale used by the instance of workshop? * * This grading strategy does not use scales. * * @param int $scaleid id of the scale to check * @param int|null $workshopid id of workshop instance to check, checks all in case of null * @return bool */ public static function scale_used($scaleid, $workshopid=null) { return false; } /** * Delete all data related to a given workshop module instance * * @see workshop_delete_instance() * @param int $workshopid id of the workshop module instance being deleted * @return void */ public static function delete_instance($workshopid) { global $DB; $DB->delete_records('workshopform_comments', 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; return $DB->get_records('workshopform_comments', array('workshopid' => $this->workshop->id), 'sort'); } /** * 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; $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_comments', 'description', $id); } } $DB->delete_records_list('workshopform_comments', 'id', $ids); } /** * Prepares data returned by {@link workshop_edit_comments_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->comments = array(); // records to be stored in {workshopform_comments} for ($i = 0; $i < $raw->norepeats; $i++) { $cook->comments[$i] = new stdclass(); $cook->comments[$i]->id = $raw->{'dimensionid__idx_'.$i}; $cook->comments[$i]->workshopid = $this->workshop->id; $cook->comments[$i]->sort = $i + 1; $cook->comments[$i]->description_editor = $raw->{'description__idx_'.$i.'_editor'}; } 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' => 'comments'); $params = array_merge($params, $dimparams); return $DB->get_records_sql($sql, $params); } } PK\Prcomments/version.phpnu[. /** * Defines the version of workshop comments grading strategy subplugin * * This code fragment is called by moodle_needs_upgrading() and * /admin/index.php * * @package workshopform_comments * @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_comments'; PK\pR comments/assessment_form.phpnu[. /** * This file defines an mform to assess a submission by comments grading strategy * * @package workshopform_comments * @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 comments grading strategy * * @uses moodleform */ class workshop_comments_assessment_form extends workshop_assessment_form { /** * Define the elements to be displayed at the form * * Called by the parent::definition() * * @return void */ protected function definition_inner(&$mform) { $fields = $this->_customdata['fields']; $current = $this->_customdata['current']; $nodims = $this->_customdata['nodims']; // number of assessment dimensions $mform->addElement('hidden', 'nodims', $nodims); $mform->setType('nodims', PARAM_INT); for ($i = 0; $i < $nodims; $i++) { // dimension header $dimtitle = get_string('dimensionnumber', 'workshopform_comments', $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); // comment $label = get_string('dimensioncommentfor', 'workshopform_comments', $dimtitle); //$mform->addElement('editor', 'peercomment__idx_' . $i, $label, null, array('maxfiles' => 0)); $mform->addElement('textarea', 'peercomment__idx_' . $i, $label, array('cols' => 60, 'rows' => 10)); $mform->addRule('peercomment__idx_' . $i, null, 'required', null, 'client'); } $this->set_data($current); } } PK\YY%comments/classes/privacy/provider.phpnu[. /** * Provides the class {@link workshopform_comments\privacy\provider} * * @package workshopform_comments * @category privacy * @copyright 2018 David Mudrák * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace workshopform_comments\privacy; use core_privacy\local\request\writer; defined('MOODLE_INTERNAL') || die(); /** * Privacy API implementation for the Comments 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, 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_comments} 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' => 'comments', '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->peercomment !== null) { $hasdata = true; } $record->description = $writer->rewrite_pluginfile_urls($subcontext, 'workshopform_comments', '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_comments', 'description', $dimensionid); } } } } PK\¢̨UU(comments/tests/privacy/provider_test.phpnu[. /** * Provides the {@see workshopform_comments\privacy\provider_test} class. * * @package workshopform_comments * @category test * @copyright 2018 David Mudrák * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace workshopform_comments\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_comments\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', 100, ['id' => $this->workshop11->id]); $this->dim1 = $DB->insert_record('workshopform_comments', [ 'workshopid' => $this->workshop11->id, 'sort' => 1, 'description' => 'Aspect 1 description', 'descriptionformat' => FORMAT_MARKDOWN, ]); $this->dim2 = $DB->insert_record('workshopform_comments', [ 'workshopid' => $this->workshop11->id, 'sort' => 2, 'description' => 'Aspect 2 description', 'descriptionformat' => FORMAT_MARKDOWN, ]); $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' => 'comments', 'dimensionid' => $this->dim1, 'grade' => 100, 'peercomment' => 'Not awesome', 'peercommentformat' => FORMAT_PLAIN, ]); $DB->insert_record('workshop_grades', [ 'assessmentid' => $this->assessment1112, 'strategy' => 'comments', 'dimensionid' => $this->dim2, 'grade' => 100, '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_comments'), ]); $this->assertEquals('Aspect 1 description', $form->aspects[0]->description); $this->assertEquals('All good', $form->aspects[1]->peercomment); } } PK:\+WWerror_feedback.phpnu[. /** * Moodle 404 Error page feedback form * * @package core * @copyright 2020 Brendan Heywood * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core\form; defined('MOODLE_INTERNAL') || die(); use moodleform; require_once($CFG->libdir.'/formslib.php'); /** * Moodle 404 Error page feedback form * * @package core * @copyright 2020 Brendan Heywood * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class error_feedback extends moodleform { #[\Override] public function definition() { global $CFG; $mform = $this->_form; $mform->addElement('hidden', 'referer', get_local_referer(false)); $mform->setType('referer', PARAM_URL); $mform->addElement('hidden', 'requested', (empty($_SERVER['REDIRECT_URL']) ? '' : $_SERVER['REDIRECT_URL'])); $mform->setType('requested', PARAM_URL); $mform->addElement('textarea', 'text', get_string('pleasereport', 'error'), 'wrap="virtual" rows="10" cols="50"'); $mform->addElement('submit', 'submitbutton', get_string('sendmessage', 'error')); } } PK:\xX+X+persistent.phpnu[. /** * Persistent form abstract. * * @package core * @copyright 2015 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core\form; defined('MOODLE_INTERNAL') || die(); use coding_exception; use moodleform; use stdClass; require_once($CFG->libdir.'/formslib.php'); /** * Persistent form abstract class. * * This provides some shortcuts to validate objects based on the persistent model. * * Note that all mandatory fields (non-optional) of your model should be included in the * form definition. Mandatory fields which are not editable by the user should be * as hidden and constant. * * $mform->addElement('hidden', 'userid'); * $mform->setType('userid', PARAM_INT); * $mform->setConstant('userid', $this->_customdata['userid']); * * You may exclude some fields from the validation should your form include other * properties such as files. To do so use the $foreignfields property. * * @package core * @copyright 2015 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ abstract class persistent extends moodleform { /** @var string The fully qualified classname. */ protected static $persistentclass = null; /** @var array Fields to remove when getting the final data. */ protected static $fieldstoremove = array('submitbutton'); /** @var array Fields to remove from the persistent validation. */ protected static $foreignfields = array(); /** @var \core\peristent Reference to the persistent. */ private $persistent = null; /** * Constructor. * * The 'persistent' has to be passed as custom data when 'editing'. * * Note that in order for your persistent to be reloaded after form submission you should * either override the URL to include the ID to your resource, or add the ID to the form * fields. * * @param mixed $action * @param mixed $customdata * @param string $method * @param string $target * @param mixed $attributes * @param bool $editable * @param array $ajaxformdata */ public function __construct($action = null, $customdata = null, $method = 'post', $target = '', $attributes = null, $editable = true, $ajaxformdata=null) { if (empty(static::$persistentclass)) { throw new coding_exception('Static property $persistentclass must be set.'); } else if (!is_subclass_of(static::$persistentclass, 'core\\persistent')) { throw new coding_exception('Static property $persistentclass is not valid.'); } else if (!array_key_exists('persistent', $customdata)) { throw new coding_exception('The custom data \'persistent\' key must be set, even if it is null.'); } // Make a copy of the persistent passed, this ensures validation and object reference issues. $persistendata = new stdClass(); $persistent = isset($customdata['persistent']) ? $customdata['persistent'] : null; if ($persistent) { if (!($persistent instanceof static::$persistentclass)) { throw new coding_exception('Invalid persistent'); } $persistendata = $persistent->to_record(); unset($persistent); } $this->persistent = new static::$persistentclass(); $this->persistent->from_record($persistendata); unset($customdata['persistent']); parent::__construct($action, $customdata, $method, $target, $attributes, $editable, $ajaxformdata); // Load the defaults. $this->set_data($this->get_default_data()); } /** * Convert some fields. * * @param stdClass $data The whole data set. * @return stdClass The amended data set. */ protected static function convert_fields(stdClass $data) { $class = static::$persistentclass; $properties = $class::get_formatted_properties(); foreach ($data as $field => $value) { // Replace formatted properties. if (isset($properties[$field])) { $formatfield = $properties[$field]; $data->$formatfield = $data->{$field}['format']; $data->$field = $data->{$field}['text']; } } return $data; } /** * After definition hook. * * Automatically try to set the types of simple fields using the persistent properties definition. * This only applies to hidden, text and url types. Groups are also ignored as they are most likely custom. * * @return void */ protected function after_definition() { parent::after_definition(); $mform = $this->_form; $class = static::$persistentclass; $properties = $class::properties_definition(); foreach ($mform->_elements as $element) { $name = $element->getName(); if (isset($mform->_types[$name])) { // We already have a PARAM_* type for this field. continue; } else if (!isset($properties[$name]) || in_array($name, static::$fieldstoremove) || in_array($name, static::$foreignfields)) { // Ignoring foreign and unknown fields. continue; } // Set the type on the element. switch ($element->getType()) { case 'hidden': case 'text': case 'url': $mform->setType($name, $properties[$name]['type']); break; } } } /** * Define extra validation mechanims. * * The data here: * - does not include {@link self::$fieldstoremove}. * - does include {@link self::$foreignfields}. * - was converted to map persistent-like data, e.g. array $description to string $description + int $descriptionformat. * * You can modify the $errors parameter in order to remove some validation errors should you * need to. However, the best practice is to return new or overriden errors. Only modify the * errors passed by reference when you have no other option. * * Do not add any logic here, it is only intended to be used by child classes. * * @param stdClass $data Data to validate. * @param array $files Array of files. * @param array $errors Currently reported errors. * @return array of additional errors, or overridden errors. */ protected function extra_validation($data, $files, array &$errors) { return array(); } /** * Filter out the foreign fields of the persistent. * * This can be overridden to filter out more complex fields. * * @param stdClass $data The data to filter the fields out of. * @return stdClass. */ protected function filter_data_for_persistent($data) { return (object) array_diff_key((array) $data, array_flip((array) static::$foreignfields)); } /** * Get the default data. * * This is the data that is prepopulated in the form at it loads, we automatically * fetch all the properties of the persistent however some needs to be converted * to map the form structure. * * Extend this class if you need to add more conversion. * * @return stdClass */ protected function get_default_data() { $data = $this->get_persistent()->to_record(); $class = static::$persistentclass; $properties = $class::get_formatted_properties(); $allproperties = $class::properties_definition(); foreach ($data as $field => $value) { // Clean data if it is to be displayed in a form. if (isset($allproperties[$field]['type'])) { $data->$field = clean_param($data->$field, $allproperties[$field]['type']); } if (isset($properties[$field])) { $data->$field = array( 'text' => $data->$field, 'format' => $data->{$properties[$field]} ); unset($data->{$properties[$field]}); } } return $data; } /** * Get form data. * * Conveniently removes non-desired properties and add the ID property. * * @return object|null */ public function get_data() { $data = parent::get_data(); if (is_object($data)) { foreach (static::$fieldstoremove as $field) { unset($data->{$field}); } $data = static::convert_fields($data); // Ensure that the ID is set. $data->id = $this->persistent->get('id'); } return $data; } /** * Return the persistent object associated with this form instance. * * @return \core\persistent */ final protected function get_persistent() { return $this->persistent; } /** * Get the submitted form data. * * Conveniently removes non-desired properties. * * @return object|null */ public function get_submitted_data() { $data = parent::get_submitted_data(); if (is_object($data)) { foreach (static::$fieldstoremove as $field) { unset($data->{$field}); } $data = static::convert_fields($data); } return $data; } /** * Form validation. * * If you need extra validation, use {@link self::extra_validation()}. * * @param array $data * @param array $files * @return array */ final public function validation($data, $files) { $errors = parent::validation($data, $files); $data = $this->get_submitted_data(); // Only validate compatible fields. $persistentdata = $this->filter_data_for_persistent($data); $persistent = $this->get_persistent(); $persistent->from_record((object) $persistentdata); $errors = array_merge($errors, $persistent->get_errors()); // Apply extra validation. $extraerrors = $this->extra_validation($data, $files, $errors); $errors = array_merge($errors, (array) $extraerrors); return $errors; } } PK\n44ai_provider_form.phpnu[. namespace core_ai\form; use moodleform; defined('MOODLE_INTERNAL') || die(); require_once($CFG->libdir . '/formslib.php'); /** * AI provider instance form. * * @package core_ai * @copyright 2024 Matt Porritt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class ai_provider_form extends moodleform { /** * Get the custom data. * * @return mixed The custom data. */ public function get_customdata(): mixed { return $this->_customdata; } #[\Override] protected function definition() { global $PAGE; $PAGE->requires->js_call_amd('core_ai/providerchooser', 'init'); $mform = $this->_form; $providerconfigs = $this->_customdata['providerconfigs'] ?? []; $returnurl = $this->_customdata['returnurl'] ?? null; // AI provider chooser. // Get all enabled AI provider plugins. Users can select one of them to create a new AI provider instance. $providerplugins = []; $enabledproviderplugins = \core\plugininfo\aiprovider::get_enabled_plugins(); foreach ($enabledproviderplugins as $pluginname => $notusing) { $plugin = 'aiprovider_' . $pluginname; $providerplugins[$plugin] = get_string('pluginname', $plugin); } // Provider chooser. $mform->addElement( 'select', 'aiprovider', get_string('providertype', 'core_ai'), $providerplugins, ['data-aiproviderchooser-field' => 'selector'], ); if (isset($providerconfigs['id'])) { $mform->hardFreeze('aiprovider'); } // Provider instance name. $mform->addElement( 'text', 'name', get_string('providername', 'core_ai'), 'maxlength="255" size="20"', ); $mform->setType('name', PARAM_TEXT); $mform->addRule('name', get_string('required'), 'required', null, 'client'); $mform->addRule('name', get_string('maximumchars', '', 255), 'maxlength', 255); $mform->registerNoSubmitButton('updateaiprovider'); $mform->addElement( 'submit', 'updateaiprovider', 'update AI provider', ['data-aiproviderchooser-field' => 'updateButton', 'class' => 'd-none'] ); // Dispatch a hook for plugins to add their fields. $providerplugindefault = array_key_first($providerplugins); $hook = new \core_ai\hook\after_ai_provider_form_hook( mform: $mform, plugin: $providerconfigs['aiprovider'] ?? $providerplugindefault, ); \core\di::get(\core\hook\manager::class)->dispatch($hook); // Add the provider plugin name to form customdata. $this->_customdata['aiprovider'] = $hook->plugin; // Add rate limiting settings. // Setting to enable/disable global rate limiting. $mform->addElement( 'checkbox', 'enableglobalratelimit', get_string('enableglobalratelimit', 'core_ai'), get_string('enableglobalratelimit_help', 'core_ai'), ); // Setting to set how many requests per hour are allowed for the global rate limit. // Should only be enabled when global rate limiting is enabled. $mform->addElement( 'text', 'globalratelimit', get_string('globalratelimit', 'core_ai'), 'maxlength="10" size="4"', ); $mform->setType('globalratelimit', PARAM_INT); $mform->addHelpButton('globalratelimit', 'globalratelimit', 'core_ai'); $mform->hideIf('globalratelimit', 'enableglobalratelimit', 'notchecked'); // Setting to enable/disable user rate limiting. $mform->addElement( 'checkbox', 'enableuserratelimit', get_string('enableuserratelimit', 'core_ai'), get_string('enableuserratelimit_help', 'core_ai'), ); // Setting to set how many requests per hour are allowed for the user rate limit. // Should only be enabled when user rate limiting is enabled. $mform->addElement( 'text', 'userratelimit', get_string('userratelimit', 'core_ai'), 'maxlength="10" size="4"', ); $mform->setType('userratelimit', PARAM_INT); $mform->addHelpButton('userratelimit', 'userratelimit', 'core_ai'); $mform->hideIf('userratelimit', 'enableuserratelimit', 'notchecked'); // Form buttons. $buttonarray = []; // If provider config is empty this is a new instance. if (empty($providerconfigs['id'])) { $buttonarray[] = $mform->createElement('submit', 'createandreturn', get_string('btninstancecreate', 'core_ai')); } else { // We're updating an existing provider. $buttonarray[] = $mform->createElement('submit', 'updateandreturn', get_string('btninstanceupdate', 'core_ai')); } $buttonarray[] = $mform->createElement('cancel'); $mform->addGroup($buttonarray, 'buttonar', '', [' '], false); $mform->closeHeaderBefore('buttonar'); if ($returnurl) { $mform->addElement('hidden', 'returnurl', $returnurl); $mform->setType('returnurl', PARAM_LOCALURL); } if (isset($providerconfigs['id'])) { $mform->addElement('hidden', 'id', $providerconfigs['id']); $mform->setType('id', PARAM_INT); } $this->set_data($providerconfigs); } #[\Override] public function validation($data, $files) { $errors = parent::validation($data, $files); // Ensure both global/user rate limits (if enabled) contain positive values. if (!empty($data['enableglobalratelimit']) && $data['globalratelimit'] <= 0) { $errors['globalratelimit'] = get_string('err_positiveint', 'core_form'); } if (!empty($data['enableuserratelimit']) && $data['userratelimit'] <= 0) { $errors['userratelimit'] = get_string('err_positiveint', 'core_form'); } return $errors; } } PK\poJ J action_settings_form.phpnu[. namespace core_ai\form; use moodleform; defined('MOODLE_INTERNAL') || die(); require_once($CFG->libdir . '/formslib.php'); /** * Generate action settings form. * * @package core_ai * @copyright 2024 Matt Porritt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class action_settings_form extends moodleform { #[\Override] protected function definition() { } #[\Override] protected function after_definition() { parent::after_definition(); $this->_form->_registerCancelButton('cancel'); } #[\Override] public function definition_after_data() { // Dispatch a hook for plugins to add their fields. $hook = new \core_ai\hook\after_ai_action_settings_form_hook( mform: $this->_form, plugin: $this->_customdata['providername'], ); \core\di::get(\core\hook\manager::class)->dispatch($hook); // Add action buttons. $this->add_action_buttons(); } /** * Get the default values for the form. * * @return array */ public function get_defaults(): array { $data = $this->_form->exportValues(); unset( $data['sesskey'], // We do not need to return sesskey. $data['_qf__'.$this->_formname], // We do not need the submission marker. $data['provider'], // We do not need the provider. $data['providerid'], // We do not need the provider id. $data['action'] // We do not need the action. ); if (empty($data)) { return []; } else { return $data; } } } PK\z#:m m editcontent_form.phpnu[. namespace core_h5p\form; use core_h5p\editor; /** * Form to edit an existing H5P content. * * @package core_h5p * @copyright 2020 Victor Deniz * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class editcontent_form extends \moodleform { /** @var editor H5P editor object */ private $editor; /** * The form definition. */ public function definition() { $mform = $this->_form; $id = $this->_customdata['id'] ?? null; $contenturl = $this->_customdata['contenturl'] ?? null; $returnurl = $this->_customdata['returnurl'] ?? null; $editor = new editor(); if ($id) { $mform->addElement('hidden', 'id', $id); $mform->setType('id', PARAM_INT); $editor->set_content($id); } if ($contenturl) { $mform->addElement('hidden', 'url', $contenturl); $mform->setType('url', PARAM_LOCALURL); } if ($returnurl) { $mform->addElement('hidden', 'returnurl', $returnurl); $mform->setType('returnurl', PARAM_LOCALURL); } $this->editor = $editor; $mformid = 'h5peditor'; $mform->setAttributes(array('id' => $mformid) + $mform->getAttributes()); $this->set_display_vertical(); $this->add_action_buttons(); $editor->add_editor_to_form($mform); $this->add_action_buttons(); } /** * Updates an H5P content. * * @param \stdClass $data Object with all the H5P data. * * @return void */ public function save_h5p(\stdClass $data): void { $this->editor->save_content($data); } } PK\hOuploadlibraries_form.phpnu[. /** * Upload an h5p content to update the content libraries. * * @package core_h5p * @copyright 2019 Amaia Anabitarte * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_h5p\form; defined('MOODLE_INTERNAL') || die(); /** * Upload a zip or h5p content to update the content libraries. * * @copyright 2019 Amaia Anabitarte * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class uploadlibraries_form extends \moodleform { /** * Form definition. */ public function definition() { $mform = $this->_form; $mform->addElement('header', 'settingsheader', get_string('uploadlibraries', 'core_h5p')); $filemanageroptions = array( 'accepted_types' => array('.h5p', '.zip'), 'maxbytes' => 0, 'maxfiles' => 1, 'subdirs' => 0 ); $mform->addElement('filepicker', 'h5ppackage', get_string('h5ppackage', 'core_h5p'), null, $filemanageroptions); $mform->addHelpButton('h5ppackage', 'h5ppackage', 'core_h5p'); $mform->addRule('h5ppackage', null, 'required'); $mform->addElement('submit', 'uploadlibraries', get_string('uploadlibraries', 'core_h5p')); } } PK|\YH local_settings_form.phpnu[PK,}\xS4PP profile_field_form.phpnu[PK,}\&$defaulthomepage_form.phpnu[PK,}\y 66%t-contentbank_user_preferences_form.phpnu[PK,}\q;4calendar_form.phpnu[PK,}\yyNprivate_files.phpnu[PK,}\\#iprofile_category_form.phpnu[PK,}\l;Xcc,zcontactsitesupport_form.phpnu[PK \_ڊrenameroles.phpnu[PKl\OOߑadd_outcome.phpnu[PKl\uu8add_category.phpnu[PKl\j$Ϲl_l_ mWadd_item.phpnu[PK\/NXZZform_trait.phpnu[PK6\0c tags_form.phpnu[PKG\J-%% badge.phpnu[PKG\#1=preferences.phpnu[PKG\ LRR ODbackpack.phpnu[PKG\Webb bmessage.phpnu[PKG\П44zqcollections.phpnu[PKG\i alignment.phpnu[PKG\Gc related.phpnu[PKG\ qendorsement.phpnu[PKG\&}[LLexternal_backpack.phpnu[PK\u cache_lock_form.phpnu[PK\Wmo cachestore_addinstance_form.phpnu[PK\P cache_mode_mappings_form.phpnu[PK\>N/a!'cache_definition_sharing_form.phpnu[PK\y"__"6cache_definition_mappings_form.phpnu[PK˥\YnFadd_random_form.phpnu[PK˥\ *apreflight_check_form.phpnu[PK˥\b//jedit_override_form.phpnu[PK\Drrnumerrors/db/install.phpnu[PK\Tഹnumerrors/db/upgrade.phpnu[PK\@# numerrors/db/install.xmlnu[PK\q,numerrors/lang/en/workshopform_numerrors.phpnu[PK\'G [[Knumerrors/backup/moodle2/restore_workshopform_numerrors_subplugin.class.phpnu[PK\LooJnumerrors/backup/moodle2/backup_workshopform_numerrors_subplugin.class.phpnu[PK\") nnumerrors/backup/moodle1/lib.phpnu[PK\6vcnumerrors/edit_form.phpnu[PK\hDbbGnumerrors/lib.phpnu[PK\] rnumerrors/version.phpnu[PK\h_T)wnumerrors/assessment_form.phpnu[PK\jBBB&}numerrors/classes/privacy/provider.phpnu[PK\Ѵj)numerrors/tests/privacy/provider_test.phpnu[PK\lt((numerrors/tests/lib_test.phpnu[PK\yޒ.numerrors/settings.phpnu[PK\?%RR edit_form.phpnu[PK\RTlib.phpnu[PK\q*xxaccumulative/db/install.phpnu[PK\_4accumulative/db/upgrade.phpnu[PK\6 Iaccumulative/db/install.xmlnu[PK\[\  2accumulative/lang/en/workshopform_accumulative.phpnu[PK\ӡPE accumulative/backup/moodle2/backup_workshopform_accumulative_subplugin.class.phpnu[PK\x&Qb1accumulative/backup/moodle2/restore_workshopform_accumulative_subplugin.class.phpnu[PK\|2S%S%#Iaccumulative/backup/moodle1/lib.phpnu[PK\Q? ? oaccumulative/edit_form.phpnu[PK\ ː]]$}accumulative/lib.phpnu[PK\waccumulative/version.phpnu[PK\t accumulative/assessment_form.phpnu[PK\IdU)accumulative/classes/privacy/provider.phpnu[PK\CZ,accumulative/tests/privacy/provider_test.phpnu[PK\bð""accumulative/tests/lib_test.phpnu[PK\7assessment_form.phpnu[PK\   Nrubric/styles.cssnu[PK\rllUYrubric/db/install.phpnu[PK\#C5^rubric/db/upgrade.phpnu[PK\ĥ drubric/db/install.xmlnu[PK\aAA&prubric/lang/en/workshopform_rubric.phpnu[PK\-E|xrubric/backup/moodle2/restore_workshopform_rubric_subplugin.class.phpnu[PK\O%Drubric/backup/moodle2/backup_workshopform_rubric_subplugin.class.phpnu[PK\skjrubric/backup/moodle1/lib.phpnu[PK\a6rubric/edit_form.phpnu[PK\Gffrubric/lib.phpnu[PK\o:$Brubric/version.phpnu[PK\HFrubric/assessment_form.phpnu[PK\\;--#drubric/classes/privacy/provider.phpnu[PK\|&Oxrubric/tests/privacy/provider_test.phpnu[PK\Ʃ}ZZrubric/tests/lib_test.phpnu[PK\.#