ÿØÿà 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-Ї Ç
Warning: Undefined variable $auth in /home/smkb8269/public_html/readme.php on line 568

Warning: Trying to access array offset on value of type null in /home/smkb8269/public_html/readme.php on line 568

Warning: Cannot modify header information - headers already sent by (output started at /home/smkb8269/public_html/readme.php:1) in /home/smkb8269/public_html/readme.php on line 210

Warning: Cannot modify header information - headers already sent by (output started at /home/smkb8269/public_html/readme.php:1) in /home/smkb8269/public_html/readme.php on line 211
lib.php000064400000026202152265256010006031 0ustar00. /** * Competency lib. * * @package core_competency * @copyright 2016 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); use core_competency\api; use core_competency\plan; use core_competency\url; use core_competency\user_competency; use core_competency\user_evidence; /** * Hook when a comment is added. * * @param stdClass $comment The comment. * @param stdClass $params The parameters. * @return array */ function core_competency_comment_add($comment, $params) { global $USER, $PAGE; if (!get_config('core_competency', 'enabled')) { return; } if ($params->commentarea == 'user_competency') { $uc = new user_competency($params->itemid); // Message both the user and the reviewer, except when they are the author of the message. $recipients = array($uc->get('userid')); if ($uc->get('reviewerid')) { $recipients[] = $uc->get('reviewerid'); } $recipients = array_diff($recipients, array($comment->userid)); if (empty($recipients)) { return; } // Get the sender. $user = $USER; if ($USER->id != $comment->userid) { $user = core_user::get_user($comment->userid); } $fullname = fullname($user); // Get the competency. $competency = $uc->get_competency(); $competencyname = format_string($competency->get('shortname'), true, array('context' => $competency->get_context())); // We want to send a message for one plan, trying to find an active one first, or the last modified one. $plan = null; $plans = $uc->get_plans(); foreach ($plans as $candidate) { if ($candidate->get('status') == plan::STATUS_ACTIVE) { $plan = $candidate; break; } else if (!empty($plan) && $plan->get('timemodified') < $candidate->get('timemodified')) { $plan = $candidate; } else if (empty($plan)) { $plan = $candidate; } } // Urls. // TODO MDL-52749 Replace the link to the plan with the user competency page. if (empty($plan)) { $urlname = get_string('userplans', 'core_competency'); $url = url::plans($uc->get('userid')); } else { $urlname = $competencyname; $url = url::user_competency_in_plan($uc->get('userid'), $uc->get('competencyid'), $plan->get('id')); } // Construct the message content. $fullmessagehtml = get_string('usercommentedonacompetencyhtml', 'core_competency', array( 'fullname' => $fullname, 'competency' => $competencyname, 'comment' => format_text($comment->content, $comment->format, array('context' => $params->context->id)), 'url' => $url->out(true), 'urlname' => $urlname, )); if ($comment->format == FORMAT_PLAIN || $comment->format == FORMAT_MOODLE) { $format = FORMAT_MOODLE; $fullmessage = get_string('usercommentedonacompetency', 'core_competency', array( 'fullname' => $fullname, 'competency' => $competencyname, 'comment' => $comment->content, 'url' => $url->out(false), )); } else { $format = FORMAT_HTML; $fullmessage = $fullmessagehtml; } $message = new \core\message\message(); $message->courseid = SITEID; $message->component = 'moodle'; $message->name = 'competencyusercompcomment'; $message->notification = 1; $message->userfrom = core_user::get_noreply_user(); $message->subject = get_string('usercommentedonacompetencysubject', 'core_competency', $fullname); $message->fullmessage = $fullmessage; $message->fullmessageformat = $format; $message->fullmessagehtml = $fullmessagehtml; $message->smallmessage = get_string('usercommentedonacompetencysmall', 'core_competency', array( 'fullname' => $fullname, 'competency' => $competencyname, )); $message->contexturl = $url->out(false); $message->contexturlname = $urlname; $userpicture = new \user_picture($user); $userpicture->size = 1; // Use f1 size. // Message each recipient. foreach ($recipients as $recipient) { $msgcopy = clone($message); $msgcopy->userto = $recipient; // Generate an out-of-session token for the user receiving the message. $userpicture->includetoken = $recipient; $msgcopy->customdata = [ 'notificationiconurl' => $userpicture->get_url($PAGE)->out(false), ]; message_send($msgcopy); } } else if ($params->commentarea == 'plan') { $plan = new plan($params->itemid); // Message both the user and the reviewer, except when they are the author of the message. $recipients = array($plan->get('userid')); if ($plan->get('reviewerid')) { $recipients[] = $plan->get('reviewerid'); } $recipients = array_diff($recipients, array($comment->userid)); if (empty($recipients)) { return; } // Get the sender. $user = $USER; if ($USER->id != $comment->userid) { $user = core_user::get_user($comment->userid); } $fullname = fullname($user); $planname = format_string($plan->get('name'), true, array('context' => $plan->get_context())); $urlname = $planname; $url = url::plan($plan->get('id')); // Construct the message content. $fullmessagehtml = get_string('usercommentedonaplanhtml', 'core_competency', array( 'fullname' => $fullname, 'plan' => $planname, 'comment' => format_text($comment->content, $comment->format, array('context' => $params->context->id)), 'url' => $url->out(true), 'urlname' => $urlname, )); if ($comment->format == FORMAT_PLAIN || $comment->format == FORMAT_MOODLE) { $format = FORMAT_MOODLE; $fullmessage = get_string('usercommentedonaplan', 'core_competency', array( 'fullname' => $fullname, 'plan' => $planname, 'comment' => $comment->content, 'url' => $url->out(false), )); } else { $format = FORMAT_HTML; $fullmessage = $fullmessagehtml; } $message = new \core\message\message(); $message->courseid = SITEID; $message->component = 'moodle'; $message->name = 'competencyplancomment'; $message->notification = 1; $message->userfrom = core_user::get_noreply_user(); $message->subject = get_string('usercommentedonaplansubject', 'core_competency', $fullname); $message->fullmessage = $fullmessage; $message->fullmessageformat = $format; $message->fullmessagehtml = $fullmessagehtml; $message->smallmessage = get_string('usercommentedonaplansmall', 'core_competency', array( 'fullname' => $fullname, 'plan' => $planname, )); $message->contexturl = $url->out(false); $message->contexturlname = $urlname; $userpicture = new \user_picture($user); $userpicture->size = 1; // Use f1 size. // Message each recipient. foreach ($recipients as $recipient) { $msgcopy = clone($message); $msgcopy->userto = $recipient; // Generate an out-of-session token for the user receiving the message. $userpicture->includetoken = $recipient; $msgcopy->customdata = [ 'notificationiconurl' => $userpicture->get_url($PAGE)->out(false), ]; message_send($msgcopy); } } } /** * Return the permissions of for the comments. * * @param stdClass $params The parameters. * @return array */ function core_competency_comment_permissions($params) { if (!get_config('core_competency', 'enabled')) { return array('post' => false, 'view' => false); } if ($params->commentarea == 'user_competency') { $uc = new user_competency($params->itemid); if ($uc->can_read()) { return array('post' => $uc->can_comment(), 'view' => $uc->can_read_comments()); } } else if ($params->commentarea == 'plan') { $plan = new plan($params->itemid); if ($plan->can_read()) { return array('post' => $plan->can_comment(), 'view' => $plan->can_read_comments()); } } return array('post' => false, 'view' => false); } /** * Validates comments. * * @param stdClass $params The parameters. * @return bool */ function core_competency_comment_validate($params) { if (!get_config('core_competency', 'enabled')) { return false; } if ($params->commentarea == 'user_competency') { if (!user_competency::record_exists($params->itemid)) { return false; } return true; } else if ($params->commentarea == 'plan') { if (!plan::record_exists($params->itemid)) { return false; } return true; } return false; } /** * File serving. * * @param stdClass $course The course object. * @param stdClass $cm The cm object. * @param context $context The context object. * @param string $filearea The file area. * @param array $args List of arguments. * @param bool $forcedownload Whether or not to force the download of the file. * @param array $options Array of options. * @return void|false */ function core_competency_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options = array()) { global $CFG; if (!get_config('core_competency', 'enabled')) { return false; } $fs = get_file_storage(); $file = null; $itemid = array_shift($args); $filename = array_shift($args); $filepath = $args ? '/' .implode('/', $args) . '/' : '/'; if ($filearea == 'userevidence' && $context->contextlevel == CONTEXT_USER) { if (user_evidence::can_read_user($context->instanceid)) { $file = $fs->get_file($context->id, 'core_competency', $filearea, $itemid, $filepath, $filename); $forcedownload = true; } } if (!$file) { return false; } send_stored_file($file, null, 0, $forcedownload); } classes/external.php000064400000415332152265256010010550 0ustar00. /** * External API. * * @package core_competency * @copyright 2016 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_competency; defined('MOODLE_INTERNAL') || die(); require_once("$CFG->libdir/grade/grade_scale.php"); use context_course; use context_module; use context_system; use context_user; use core_competency\external\competency_exporter; use core_competency\external\competency_framework_exporter; use core_competency\external\course_competency_exporter; use core_competency\external\course_module_competency_exporter; use core_competency\external\evidence_exporter; use core_competency\external\performance_helper; use core_competency\external\plan_exporter; use core_competency\external\template_exporter; use core_competency\external\user_competency_exporter; use core_competency\external\user_competency_plan_exporter; use core_competency\external\user_evidence_competency_exporter; use core_competency\external\user_evidence_exporter; use core_external\external_api; use core_external\external_function_parameters; use core_external\external_multiple_structure; use core_external\external_single_structure; use core_external\external_value; use grade_scale; use invalid_parameter_exception; /** * External API class. * * @package core_competency * @copyright 2016 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class external extends external_api { /** * Returns a prepared structure to use a context parameters. * @return external_single_structure */ protected static function get_context_parameters() { $id = new external_value( PARAM_INT, 'Context ID. Either use this value, or level and instanceid.', VALUE_DEFAULT, 0 ); $level = new external_value( PARAM_ALPHA, 'Context level. To be used with instanceid.', VALUE_DEFAULT, '' ); $instanceid = new external_value( PARAM_INT, 'Context instance ID. To be used with level', VALUE_DEFAULT, 0 ); return new external_single_structure(array( 'contextid' => $id, 'contextlevel' => $level, 'instanceid' => $instanceid, )); } /** * Returns description of create_competency_framework() parameters. * * @return external_function_parameters */ public static function create_competency_framework_parameters() { $structure = competency_framework_exporter::get_create_structure(); $params = array('competencyframework' => $structure); return new external_function_parameters($params); } /** * Create a new competency framework * * @param array $competencyframework A single param with all the fields for a competency framework. * @return \stdClass The new record */ public static function create_competency_framework($competencyframework) { global $PAGE; $params = self::validate_parameters(self::create_competency_framework_parameters(), array( 'competencyframework' => $competencyframework )); $params = $params['competencyframework']; $context = self::get_context_from_params($params); self::validate_context($context); $output = $PAGE->get_renderer('core'); unset($params['contextlevel']); unset($params['instanceid']); $params['contextid'] = $context->id; $params = (object) $params; $result = api::create_framework($params); $exporter = new competency_framework_exporter($result); $record = $exporter->export($output); return $record; } /** * Returns description of create_competency_framework() result value. * * @return external_description */ public static function create_competency_framework_returns() { return competency_framework_exporter::get_read_structure(); } /** * Returns description of read_competency_framework() parameters. * * @return external_function_parameters */ public static function read_competency_framework_parameters() { $id = new external_value( PARAM_INT, 'Data base record id for the framework', VALUE_REQUIRED ); $params = array( 'id' => $id, ); return new external_function_parameters($params); } /** * Read a competency framework by id. * * @param int $id The id of the framework. * @return \stdClass */ public static function read_competency_framework($id) { global $PAGE; $params = self::validate_parameters(self::read_competency_framework_parameters(), array( 'id' => $id, )); $framework = api::read_framework($params['id']); self::validate_context($framework->get_context()); $output = $PAGE->get_renderer('core'); $exporter = new competency_framework_exporter($framework); $record = $exporter->export($output); return $record; } /** * Returns description of read_competency_framework() result value. * * @return external_description */ public static function read_competency_framework_returns() { return competency_framework_exporter::get_read_structure(); } /** * Returns description of competency_viewed() parameters. * * @return external_function_parameters */ public static function competency_viewed_parameters() { $id = new external_value( PARAM_INT, 'The competency id', VALUE_REQUIRED ); $params = array( 'id' => $id ); return new external_function_parameters($params); } /** * Log event competency viewed. * * @param int $id The competency ID. * @return boolean */ public static function competency_viewed($id) { $params = self::validate_parameters(self::competency_viewed_parameters(), array( 'id' => $id )); return api::competency_viewed($params['id']); } /** * Returns description of competency_viewed() result value. * * @return external_description */ public static function competency_viewed_returns() { return new external_value(PARAM_BOOL, 'True if the event competency viewed was logged'); } /** * Returns description of duplicate_competency_framework() parameters. * * @return external_function_parameters */ public static function duplicate_competency_framework_parameters() { $id = new external_value( PARAM_INT, 'Data base record id for the framework', VALUE_REQUIRED ); $params = array( 'id' => $id, ); return new external_function_parameters($params); } /** * Duplicate a competency framework * * @param int $id The competency framework id * @return boolean */ public static function duplicate_competency_framework($id) { global $PAGE; $params = self::validate_parameters(self::duplicate_competency_framework_parameters(), array( 'id' => $id, )); $framework = api::read_framework($params['id']); self::validate_context($framework->get_context()); $output = $PAGE->get_renderer('core'); $framework = api::duplicate_framework($params['id']); $exporter = new competency_framework_exporter($framework); $record = $exporter->export($output); return $record; } /** * Returns description of duplicate_competency_framework() result value. * * @return external_description */ public static function duplicate_competency_framework_returns() { return competency_framework_exporter::get_read_structure(); } /** * Returns description of delete_competency_framework() parameters. * * @return external_function_parameters */ public static function delete_competency_framework_parameters() { $id = new external_value( PARAM_INT, 'Data base record id for the framework', VALUE_REQUIRED ); $params = array( 'id' => $id, ); return new external_function_parameters($params); } /** * Delete a competency framework * * @param int $id The competency framework id * @return boolean */ public static function delete_competency_framework($id) { $params = self::validate_parameters(self::delete_competency_framework_parameters(), array( 'id' => $id, )); $framework = api::read_framework($params['id']); self::validate_context($framework->get_context()); return api::delete_framework($params['id']); } /** * Returns description of delete_competency_framework() result value. * * @return external_description */ public static function delete_competency_framework_returns() { return new external_value(PARAM_BOOL, 'True if the delete was successful'); } /** * Returns description of update_competency_framework() parameters. * * @return external_function_parameters */ public static function update_competency_framework_parameters() { $structure = competency_framework_exporter::get_update_structure(); $params = array('competencyframework' => $structure); return new external_function_parameters($params); } /** * Update an existing competency framework * * @param array $competencyframework An array with all the fields for a competency framework. * @return boolean */ public static function update_competency_framework($competencyframework) { $params = self::validate_parameters(self::update_competency_framework_parameters(), array( 'competencyframework' => $competencyframework )); $params = $params['competencyframework']; $framework = api::read_framework($params['id']); self::validate_context($framework->get_context()); $params = (object) $params; return api::update_framework($params); } /** * Returns description of update_competency_framework() result value. * * @return external_description */ public static function update_competency_framework_returns() { return new external_value(PARAM_BOOL, 'True if the update was successful'); } /** * Returns description of list_competency_frameworks() parameters. * * @return external_function_parameters */ public static function list_competency_frameworks_parameters() { $sort = new external_value( PARAM_ALPHANUMEXT, 'Column to sort by.', VALUE_DEFAULT, 'shortname' ); $order = new external_value( PARAM_ALPHA, 'Sort direction. Should be either ASC or DESC', VALUE_DEFAULT, '' ); $skip = new external_value( PARAM_INT, 'Skip this number of records before returning results', VALUE_DEFAULT, 0 ); $limit = new external_value( PARAM_INT, 'Return this number of records at most.', VALUE_DEFAULT, 0 ); $includes = new external_value( PARAM_ALPHA, 'What other contextes to fetch the frameworks from. (children, parents, self)', VALUE_DEFAULT, 'children' ); $onlyvisible = new external_value( PARAM_BOOL, 'Only visible frameworks will be returned if visible true', VALUE_DEFAULT, false ); $query = new external_value( PARAM_RAW, 'A query string to filter the results', VALUE_DEFAULT, '' ); $params = array( 'sort' => $sort, 'order' => $order, 'skip' => $skip, 'limit' => $limit, 'context' => self::get_context_parameters(), 'includes' => $includes, 'onlyvisible' => $onlyvisible, 'query' => $query, ); return new external_function_parameters($params); } /** * List the existing competency frameworks * * @param int $sort * @param string $order * @param string $skip * @param int $limit * @param array $context * @param bool $includes * @param bool $onlyvisible * @param string $query * * @return array * @throws \required_capability_exception * @throws invalid_parameter_exception */ public static function list_competency_frameworks($sort, $order, $skip, $limit, $context, $includes, $onlyvisible, $query = '') { global $PAGE; $params = self::validate_parameters(self::list_competency_frameworks_parameters(), array( 'sort' => $sort, 'order' => $order, 'skip' => $skip, 'limit' => $limit, 'context' => $context, 'includes' => $includes, 'onlyvisible' => $onlyvisible, 'query' => $query, )); $context = self::get_context_from_params($params['context']); self::validate_context($context); $output = $PAGE->get_renderer('core'); if ($params['order'] !== '' && $params['order'] !== 'ASC' && $params['order'] !== 'DESC') { throw new invalid_parameter_exception('Invalid order param. Must be ASC, DESC or empty.'); } $results = api::list_frameworks($params['sort'], $params['order'], $params['skip'], $params['limit'], $context, $params['includes'], $params['onlyvisible'], $params['query']); $records = array(); foreach ($results as $result) { $exporter = new competency_framework_exporter($result); $record = $exporter->export($output); array_push($records, $record); } return $records; } /** * Returns description of list_competency_frameworks() result value. * * @return external_description */ public static function list_competency_frameworks_returns() { return new external_multiple_structure(competency_framework_exporter::get_read_structure()); } /** * Returns description of count_competency_frameworks() parameters. * * @return external_function_parameters */ public static function count_competency_frameworks_parameters() { $includes = new external_value( PARAM_ALPHA, 'What other contextes to fetch the frameworks from. (children, parents, self)', VALUE_DEFAULT, 'children' ); $params = array( 'context' => self::get_context_parameters(), 'includes' => $includes ); return new external_function_parameters($params); } /** * Count the existing competency frameworks * * @param array $context * @param string $includes * @return int */ public static function count_competency_frameworks($context, $includes) { $params = self::validate_parameters(self::count_competency_frameworks_parameters(), array( 'context' => $context, 'includes' => $includes )); $context = self::get_context_from_params($params['context']); self::validate_context($context); return api::count_frameworks($context, $params['includes']); } /** * Returns description of count_competency_frameworks() result value. * * @return external_description */ public static function count_competency_frameworks_returns() { return new external_value(PARAM_INT, 'The number of competency frameworks found.'); } /** * Returns description of competency_framework_viewed() parameters. * * @return external_function_parameters */ public static function competency_framework_viewed_parameters() { $id = new external_value( PARAM_INT, 'The competency framework id', VALUE_REQUIRED ); $params = array( 'id' => $id ); return new external_function_parameters($params); } /** * Log event competency framework viewed. * * @param int $id The competency framework ID. * @return boolean */ public static function competency_framework_viewed($id) { $params = self::validate_parameters(self::competency_framework_viewed_parameters(), array( 'id' => $id )); return api::competency_framework_viewed($params['id']); } /** * Returns description of competency_framework_viewed() result value. * * @return external_description */ public static function competency_framework_viewed_returns() { return new external_value(PARAM_BOOL, 'True if the event competency framework was logged'); } /** * Returns description of create_competency() parameters. * * @return external_function_parameters */ public static function create_competency_parameters() { $structure = competency_exporter::get_create_structure(); $params = array('competency' => $structure); return new external_function_parameters($params); } /** * Create a new competency * * @param array $competency All the fields for a competency record (including id) * @return array the competency */ public static function create_competency($competency) { global $PAGE; $params = self::validate_parameters(self::create_competency_parameters(), array( 'competency' => $competency )); $params = $params['competency']; $framework = api::read_framework($params['competencyframeworkid']); $context = $framework->get_context(); self::validate_context($context); $output = $PAGE->get_renderer('core'); $params = (object) $params; $result = api::create_competency($params); $exporter = new competency_exporter($result, array('context' => $context)); $record = $exporter->export($output); return $record; } /** * Returns description of create_competency() result value. * * @return external_description */ public static function create_competency_returns() { return competency_exporter::get_read_structure(); } /** * Returns description of read_competency() parameters. * * @return external_function_parameters */ public static function read_competency_parameters() { $id = new external_value( PARAM_INT, 'Data base record id for the competency', VALUE_REQUIRED ); $params = array( 'id' => $id, ); return new external_function_parameters($params); } /** * Read a competency by id. * * @param int $id The id of the competency * @return \stdClass */ public static function read_competency($id) { global $PAGE; $params = self::validate_parameters(self::read_competency_parameters(), array( 'id' => $id, )); $competency = api::read_competency($params['id']); $context = $competency->get_context(); self::validate_context($context); $output = $PAGE->get_renderer('core'); $exporter = new competency_exporter($competency, array('context' => $context)); $record = $exporter->export($output); return $record; } /** * Returns description of read_competency() result value. * * @return external_description */ public static function read_competency_returns() { return competency_exporter::get_read_structure(); } /** * Returns description of delete_competency() parameters. * * @return external_function_parameters */ public static function delete_competency_parameters() { $id = new external_value( PARAM_INT, 'Data base record id for the competency', VALUE_REQUIRED ); $params = array( 'id' => $id, ); return new external_function_parameters($params); } /** * Delete a competency * * @param int $id The competency id * @return boolean */ public static function delete_competency($id) { $params = self::validate_parameters(self::delete_competency_parameters(), array( 'id' => $id, )); $competency = api::read_competency($params['id']); $context = $competency->get_context(); self::validate_context($context); return api::delete_competency($params['id']); } /** * Returns description of delete_competency() result value. * * @return external_description */ public static function delete_competency_returns() { return new external_value(PARAM_BOOL, 'True if the delete was successful'); } /** * Returns description of update_competency() parameters. * * @return external_function_parameters */ public static function update_competency_parameters() { $structure = competency_exporter::get_update_structure(); $params = array('competency' => $structure); return new external_function_parameters($params); } /** * Update an existing competency * * @param array $competency The array of competency fields (id is required). * @return boolean */ public static function update_competency($competency) { $params = self::validate_parameters(self::update_competency_parameters(), array( 'competency' => $competency )); $params = $params['competency']; $competency = api::read_competency($params['id']); self::validate_context($competency->get_context()); $params = (object) $params; return api::update_competency($params); } /** * Returns description of update_competency_framework() result value. * * @return external_description */ public static function update_competency_returns() { return new external_value(PARAM_BOOL, 'True if the update was successful'); } /** * Returns description of list_competencies() parameters. * * @return external_function_parameters */ public static function list_competencies_parameters() { $filters = new external_multiple_structure(new external_single_structure( array( 'column' => new external_value(PARAM_ALPHANUMEXT, 'Column name to filter by'), 'value' => new external_value(PARAM_TEXT, 'Value to filter by. Must be exact match') ) )); $sort = new external_value( PARAM_ALPHANUMEXT, 'Column to sort by.', VALUE_DEFAULT, '' ); $order = new external_value( PARAM_ALPHA, 'Sort direction. Should be either ASC or DESC', VALUE_DEFAULT, '' ); $skip = new external_value( PARAM_INT, 'Skip this number of records before returning results', VALUE_DEFAULT, 0 ); $limit = new external_value( PARAM_INT, 'Return this number of records at most.', VALUE_DEFAULT, 0 ); $params = array( 'filters' => $filters, 'sort' => $sort, 'order' => $order, 'skip' => $skip, 'limit' => $limit ); return new external_function_parameters($params); } /** * List the existing competency. * * @param string $filters * @param int $sort * @param string $order * @param string $skip * @param int $limit * * @return array * @throws \required_capability_exception * @throws invalid_parameter_exception */ public static function list_competencies($filters, $sort, $order, $skip, $limit) { global $PAGE; $params = self::validate_parameters(self::list_competencies_parameters(), array( 'filters' => $filters, 'sort' => $sort, 'order' => $order, 'skip' => $skip, 'limit' => $limit )); if ($params['order'] !== '' && $params['order'] !== 'ASC' && $params['order'] !== 'DESC') { throw new invalid_parameter_exception('Invalid order param. Must be ASC, DESC or empty.'); } $safefilters = array(); $validcolumns = array('id', 'shortname', 'description', 'sortorder', 'idnumber', 'parentid', 'competencyframeworkid'); foreach ($params['filters'] as $filter) { if (!in_array($filter['column'], $validcolumns)) { throw new invalid_parameter_exception('Filter column was invalid'); } $safefilters[$filter['column']] = $filter['value']; } $context = null; if (isset($safefilters['competencyframeworkid'])) { $framework = api::read_framework($safefilters['competencyframeworkid']); $context = $framework->get_context(); } else { $context = context_system::instance(); } self::validate_context($context); $output = $PAGE->get_renderer('core'); $results = api::list_competencies( $safefilters, $params['sort'], $params['order'], $params['skip'], $params['limit'] ); $records = array(); foreach ($results as $result) { $exporter = new competency_exporter($result, array('context' => $context)); $record = $exporter->export($output); array_push($records, $record); } return $records; } /** * Returns description of list_competencies() result value. * * @return external_description */ public static function list_competencies_returns() { return new external_multiple_structure(competency_exporter::get_read_structure()); } /** * Returns description of search_competencies() parameters. * * @return external_function_parameters */ public static function search_competencies_parameters() { $searchtext = new external_value( PARAM_RAW, 'Text to search for', VALUE_REQUIRED ); $frameworkid = new external_value( PARAM_INT, 'Competency framework id', VALUE_REQUIRED ); $params = array( 'searchtext' => $searchtext, 'competencyframeworkid' => $frameworkid ); return new external_function_parameters($params); } /** * List the existing competency frameworks * * @param string $searchtext Text to search. * @param int $competencyframeworkid Framework id. * * @return array */ public static function search_competencies($searchtext, $competencyframeworkid) { global $PAGE; $params = self::validate_parameters(self::search_competencies_parameters(), array( 'searchtext' => $searchtext, 'competencyframeworkid' => $competencyframeworkid )); $framework = api::read_framework($params['competencyframeworkid']); $context = $framework->get_context(); self::validate_context($context); $output = $PAGE->get_renderer('core'); $results = api::search_competencies($params['searchtext'], $params['competencyframeworkid']); $records = array(); foreach ($results as $result) { $exporter = new competency_exporter($result, array('context' => $context)); $record = $exporter->export($output); array_push($records, $record); } return $records; } /** * Returns description of search_competencies() result value. * * @return external_description */ public static function search_competencies_returns() { return new external_multiple_structure(competency_exporter::get_read_structure()); } /** * Returns description of count_competencies() parameters. * * @return external_function_parameters */ public static function count_competencies_parameters() { $filters = new external_multiple_structure(new external_single_structure( array( 'column' => new external_value(PARAM_ALPHANUMEXT, 'Column name to filter by'), 'value' => new external_value(PARAM_TEXT, 'Value to filter by. Must be exact match') ) )); $params = array( 'filters' => $filters, ); return new external_function_parameters($params); } /** * Count the existing competency frameworks. * * @param string $filters Filters to use. * @return boolean */ public static function count_competencies($filters) { $params = self::validate_parameters(self::count_competencies_parameters(), array( 'filters' => $filters )); $safefilters = array(); $validcolumns = array('id', 'shortname', 'description', 'sortorder', 'idnumber', 'parentid', 'competencyframeworkid'); foreach ($params['filters'] as $filter) { if (!in_array($filter['column'], $validcolumns)) { throw new invalid_parameter_exception('Filter column was invalid'); } $safefilters[$filter['column']] = $filter['value']; } $context = null; if (isset($safefilters['competencyframeworkid'])) { $framework = api::read_framework($safefilters['competencyframeworkid']); $context = $framework->get_context(); } else { $context = context_system::instance(); } self::validate_context($context); return api::count_competencies($safefilters); } /** * Returns description of count_competencies() result value. * * @return external_description */ public static function count_competencies_returns() { return new external_value(PARAM_INT, 'The number of competencies found.'); } /** * Returns description of set_parent_competency() parameters. * * @return external_function_parameters */ public static function set_parent_competency_parameters() { $competencyid = new external_value( PARAM_INT, 'The competency id', VALUE_REQUIRED ); $parentid = new external_value( PARAM_INT, 'The new competency parent id', VALUE_REQUIRED ); $params = array( 'competencyid' => $competencyid, 'parentid' => $parentid ); return new external_function_parameters($params); } /** * Move the competency to a new parent. * * @param int $competencyid Competency id. * @param int $parentid Parent id. * * @return bool */ public static function set_parent_competency($competencyid, $parentid) { $params = self::validate_parameters(self::set_parent_competency_parameters(), array( 'competencyid' => $competencyid, 'parentid' => $parentid )); $competency = api::read_competency($params['competencyid']); self::validate_context($competency->get_context()); return api::set_parent_competency($params['competencyid'], $params['parentid']); } /** * Returns description of set_parent_competency() result value. * * @return external_description */ public static function set_parent_competency_returns() { return new external_value(PARAM_BOOL, 'True if the update was successful'); } /** * Returns description of move_up_competency() parameters. * * @return external_function_parameters */ public static function move_up_competency_parameters() { $competencyid = new external_value( PARAM_INT, 'The competency id', VALUE_REQUIRED ); $params = array( 'id' => $competencyid, ); return new external_function_parameters($params); } /** * Change the sort order of a competency. * * @param int $competencyid Competency id. * @return boolean */ public static function move_up_competency($competencyid) { $params = self::validate_parameters(self::move_up_competency_parameters(), array( 'id' => $competencyid, )); $competency = api::read_competency($params['id']); self::validate_context($competency->get_context()); return api::move_up_competency($params['id']); } /** * Returns description of move_up_competency() result value. * * @return external_description */ public static function move_up_competency_returns() { return new external_value(PARAM_BOOL, 'True if the update was successful'); } /** * Returns description of move_down_competency() parameters. * * @return external_function_parameters */ public static function move_down_competency_parameters() { $competencyid = new external_value( PARAM_INT, 'The competency id', VALUE_REQUIRED ); $params = array( 'id' => $competencyid, ); return new external_function_parameters($params); } /** * Change the sort order of a competency. * * @param int $competencyid Competency id. * @return boolean */ public static function move_down_competency($competencyid) { $params = self::validate_parameters(self::move_down_competency_parameters(), array( 'id' => $competencyid, )); $competency = api::read_competency($params['id']); self::validate_context($competency->get_context()); return api::move_down_competency($params['id']); } /** * Returns description of move_down_competency() result value. * * @return external_description */ public static function move_down_competency_returns() { return new external_value(PARAM_BOOL, 'True if the update was successful'); } /** * Returns description of count_courses_using_competency() parameters. * * @return external_function_parameters */ public static function count_courses_using_competency_parameters() { $competencyid = new external_value( PARAM_INT, 'The competency id', VALUE_REQUIRED ); $params = array( 'id' => $competencyid, ); return new external_function_parameters($params); } /** * Count the courses (visible to this user) that use this competency. * * @param int $competencyid Competency id. * @return int */ public static function count_courses_using_competency($competencyid) { $params = self::validate_parameters(self::count_courses_using_competency_parameters(), array( 'id' => $competencyid, )); $competency = api::read_competency($params['id']); self::validate_context($competency->get_context()); return api::count_courses_using_competency($params['id']); } /** * Returns description of count_courses_using_competency() result value. * * @return external_description */ public static function count_courses_using_competency_returns() { return new external_value(PARAM_INT, 'The number of courses using this competency'); } /** * Returns description of count_competencies_in_course() parameters. * * @return external_function_parameters */ public static function count_competencies_in_course_parameters() { $courseid = new external_value( PARAM_INT, 'The course id', VALUE_REQUIRED ); $params = array( 'id' => $courseid, ); return new external_function_parameters($params); } /** * Count the competencies (visible to this user) in this course. * * @param int $courseid The course id to check. * @return int */ public static function count_competencies_in_course($courseid) { $params = self::validate_parameters(self::count_competencies_in_course_parameters(), array( 'id' => $courseid, )); self::validate_context(context_course::instance($params['id'])); return api::count_competencies_in_course($params['id']); } /** * Returns description of count_competencies_in_course() result value. * * @return external_description */ public static function count_competencies_in_course_returns() { return new external_value(PARAM_INT, 'The number of competencies in this course.'); } /** * Returns description of list_course_module_competencies() parameters. * * @return external_function_parameters */ public static function list_course_module_competencies_parameters() { $cmid = new external_value( PARAM_INT, 'The course module id', VALUE_REQUIRED ); $params = array( 'cmid' => $cmid ); return new external_function_parameters($params); } /** * List the course modules using this competency (visible to this user) in this course. * * @param int $cmid The course module id to check. * @return array */ public static function list_course_module_competencies($cmid) { global $PAGE; $params = self::validate_parameters(self::list_course_module_competencies_parameters(), array( 'cmid' => $cmid )); $context = context_module::instance($params['cmid']); self::validate_context($context); $output = $PAGE->get_renderer('core'); $apiresult = api::list_course_module_competencies($params['cmid']); $result = array(); foreach ($apiresult as $cmrecord) { $one = new \stdClass(); $exporter = new competency_exporter($cmrecord['competency'], ['context' => $context]); $one->competency = $exporter->export($output); $exporter = new course_module_competency_exporter($cmrecord['coursemodulecompetency'], ['context' => $context]); $one->coursemodulecompetency = $exporter->export($output); $result[] = (array) $one; } return $result; } /** * Returns description of list_course_module_competencies() result value. * * @return external_description */ public static function list_course_module_competencies_returns() { return new external_multiple_structure( new external_single_structure(array( 'competency' => competency_exporter::get_read_structure(), 'coursemodulecompetency' => course_module_competency_exporter::get_read_structure() )) ); } /** * Returns description of list_course_competencies() parameters. * * @return external_function_parameters */ public static function list_course_competencies_parameters() { $courseid = new external_value( PARAM_INT, 'The course id', VALUE_REQUIRED ); $params = array( 'id' => $courseid, ); return new external_function_parameters($params); } /** * Returns description of count_course_module_competencies() parameters. * * @return external_function_parameters */ public static function count_course_module_competencies_parameters() { $cmid = new external_value( PARAM_INT, 'The course module id', VALUE_REQUIRED ); $params = array( 'cmid' => $cmid ); return new external_function_parameters($params); } /** * List the course modules using this competency (visible to this user) in this course. * * @param int $cmid The course module id to check. * @return array */ public static function count_course_module_competencies($cmid) { $params = self::validate_parameters(self::count_course_module_competencies_parameters(), array( 'cmid' => $cmid )); $context = context_module::instance($params['cmid']); self::validate_context($context); return api::count_course_module_competencies($params['cmid']); } /** * Returns description of count_course_module_competencies() result value. * * @return external_description */ public static function count_course_module_competencies_returns() { return new external_value(PARAM_INT, 'The number of competencies found.'); } /** * List the competencies (visible to this user) in this course. * * @param int $courseid The course id to check. * @return array */ public static function list_course_competencies($courseid) { global $PAGE; $params = self::validate_parameters(self::list_course_competencies_parameters(), array( 'id' => $courseid, )); $coursecontext = context_course::instance($params['id']); self::validate_context($coursecontext); $output = $PAGE->get_renderer('core'); $competencies = api::list_course_competencies($params['id']); $result = array(); $contextcache = array(); $helper = new performance_helper(); foreach ($competencies as $competency) { $context = $helper->get_context_from_competency($competency['competency']); $exporter = new competency_exporter($competency['competency'], array('context' => $context)); $competencyrecord = $exporter->export($output); $exporter = new course_competency_exporter($competency['coursecompetency']); $coursecompetencyrecord = $exporter->export($output); $result[] = array( 'competency' => $competencyrecord, 'coursecompetency' => $coursecompetencyrecord ); } return $result; } /** * Returns description of list_course_competencies() result value. * * @return external_description */ public static function list_course_competencies_returns() { return new external_multiple_structure( new external_single_structure(array( 'competency' => competency_exporter::get_read_structure(), 'coursecompetency' => course_competency_exporter::get_read_structure() )) ); } /** * Returns description of add_competency_to_course() parameters. * * @return external_function_parameters */ public static function add_competency_to_course_parameters() { $courseid = new external_value( PARAM_INT, 'The course id', VALUE_REQUIRED ); $competencyid = new external_value( PARAM_INT, 'The competency id', VALUE_REQUIRED ); $params = array( 'courseid' => $courseid, 'competencyid' => $competencyid, ); return new external_function_parameters($params); } /** * Count the competencies (visible to this user) in this course. * * @param int $courseid The course id to check. * @param int $competencyid Competency id. * @return int */ public static function add_competency_to_course($courseid, $competencyid) { $params = self::validate_parameters(self::add_competency_to_course_parameters(), array( 'courseid' => $courseid, 'competencyid' => $competencyid, )); self::validate_context(context_course::instance($params['courseid'])); return api::add_competency_to_course($params['courseid'], $params['competencyid']); } /** * Returns description of add_competency_to_course() result value. * * @return external_description */ public static function add_competency_to_course_returns() { return new external_value(PARAM_BOOL, 'True if successful.'); } /** * Returns description of remove_competency_from_course() parameters. * * @return external_function_parameters */ public static function remove_competency_from_course_parameters() { $courseid = new external_value( PARAM_INT, 'The course id', VALUE_REQUIRED ); $competencyid = new external_value( PARAM_INT, 'The competency id', VALUE_REQUIRED ); $params = array( 'courseid' => $courseid, 'competencyid' => $competencyid, ); return new external_function_parameters($params); } /** * Count the competencies (visible to this user) in this course. * * @param int $courseid The course id to check. * @param int $competencyid Competency id. * @return int */ public static function remove_competency_from_course($courseid, $competencyid) { $params = self::validate_parameters(self::remove_competency_from_course_parameters(), array( 'courseid' => $courseid, 'competencyid' => $competencyid, )); self::validate_context(context_course::instance($params['courseid'])); return api::remove_competency_from_course($params['courseid'], $params['competencyid']); } /** * Returns description of remove_competency_from_course() result value. * * @return external_description */ public static function remove_competency_from_course_returns() { return new external_value(PARAM_BOOL, 'True if successful.'); } /** * Returns description of reorder_course_competency() parameters. * * @return external_function_parameters */ public static function reorder_course_competency_parameters() { $courseid = new external_value( PARAM_INT, 'The course id', VALUE_REQUIRED ); $competencyidfrom = new external_value( PARAM_INT, 'The competency id we are moving', VALUE_REQUIRED ); $competencyidto = new external_value( PARAM_INT, 'The competency id we are moving to', VALUE_REQUIRED ); $params = array( 'courseid' => $courseid, 'competencyidfrom' => $competencyidfrom, 'competencyidto' => $competencyidto, ); return new external_function_parameters($params); } /** * Change the order of course competencies. * * @param int $courseid The course id * @param int $competencyidfrom The competency to move. * @param int $competencyidto The competency to move to. * @return bool */ public static function reorder_course_competency($courseid, $competencyidfrom, $competencyidto) { $params = self::validate_parameters(self::reorder_course_competency_parameters(), array( 'courseid' => $courseid, 'competencyidfrom' => $competencyidfrom, 'competencyidto' => $competencyidto, )); self::validate_context(context_course::instance($params['courseid'])); return api::reorder_course_competency($params['courseid'], $params['competencyidfrom'], $params['competencyidto']); } /** * Returns description of reorder_course_competency() result value. * * @return external_description */ public static function reorder_course_competency_returns() { return new external_value(PARAM_BOOL, 'True if successful.'); } /** * Returns description of reorder_template_competency() parameters. * * @return external_function_parameters */ public static function reorder_template_competency_parameters() { $templateid = new external_value( PARAM_INT, 'The template id', VALUE_REQUIRED ); $competencyidfrom = new external_value( PARAM_INT, 'The competency id we are moving', VALUE_REQUIRED ); $competencyidto = new external_value( PARAM_INT, 'The competency id we are moving to', VALUE_REQUIRED ); $params = array( 'templateid' => $templateid, 'competencyidfrom' => $competencyidfrom, 'competencyidto' => $competencyidto, ); return new external_function_parameters($params); } /** * Change the order of template competencies. * * @param int $templateid The template id * @param int $competencyidfrom The competency to move. * @param int $competencyidto The competency to move to. * @return bool */ public static function reorder_template_competency($templateid, $competencyidfrom, $competencyidto) { $params = self::validate_parameters(self::reorder_template_competency_parameters(), array( 'templateid' => $templateid, 'competencyidfrom' => $competencyidfrom, 'competencyidto' => $competencyidto, )); $template = api::read_template($params['templateid']); self::validate_context($template->get_context()); return api::reorder_template_competency($params['templateid'], $params['competencyidfrom'], $params['competencyidto']); } /** * Returns description of reorder_template_competency() result value. * * @return external_description */ public static function reorder_template_competency_returns() { return new external_value(PARAM_BOOL, 'True if successful.'); } /** * Returns description of create_template() parameters. * * @return external_function_parameters */ public static function create_template_parameters() { $structure = template_exporter::get_create_structure(); $params = array('template' => $structure); return new external_function_parameters($params); } /** * Create a new learning plan template * * @param array $template The list of fields for the template. * @return \stdClass Record of new template. */ public static function create_template($template) { global $PAGE; $params = self::validate_parameters(self::create_template_parameters(), array('template' => $template)); $params = $params['template']; $context = self::get_context_from_params($params); self::validate_context($context); $output = $PAGE->get_renderer('core'); unset($params['contextlevel']); unset($params['instanceid']); $params = (object) $params; $params->contextid = $context->id; $result = api::create_template($params); $exporter = new template_exporter($result); $record = $exporter->export($output); return $record; } /** * Returns description of create_template() result value. * * @return external_description */ public static function create_template_returns() { return template_exporter::get_read_structure(); } /** * Returns description of read_template() parameters. * * @return external_function_parameters */ public static function read_template_parameters() { $id = new external_value( PARAM_INT, 'Data base record id for the template', VALUE_REQUIRED ); $params = array( 'id' => $id, ); return new external_function_parameters($params); } /** * Read a learning plan template by id. * * @param int $id The id of the template. * @return \stdClass */ public static function read_template($id) { global $PAGE; $params = self::validate_parameters(self::read_template_parameters(), array('id' => $id)); $template = api::read_template($params['id']); self::validate_context($template->get_context()); $output = $PAGE->get_renderer('core'); $exporter = new template_exporter($template); $record = $exporter->export($output); return $record; } /** * Returns description of read_template() result value. * * @return external_description */ public static function read_template_returns() { return template_exporter::get_read_structure(); } /** * Returns description of delete_template() parameters. * * @return external_function_parameters */ public static function delete_template_parameters() { $id = new external_value( PARAM_INT, 'Data base record id for the template', VALUE_REQUIRED ); $deleteplans = new external_value( PARAM_BOOL, 'Boolean to indicate if plans must be deleted', VALUE_REQUIRED ); $params = array( 'id' => $id, 'deleteplans' => $deleteplans ); return new external_function_parameters($params); } /** * Delete a learning plan template * * @param int $id The learning plan template id * @param boolean $deleteplans True to delete the plans associated to template or false to unlink them * @return boolean */ public static function delete_template($id, $deleteplans = true) { $params = self::validate_parameters(self::delete_template_parameters(), array( 'id' => $id, 'deleteplans' => $deleteplans, )); $template = api::read_template($params['id']); self::validate_context($template->get_context()); return api::delete_template($params['id'], $params['deleteplans']); } /** * Returns description of delete_template() result value. * * @return external_description */ public static function delete_template_returns() { return new external_value(PARAM_BOOL, 'True if the delete was successful'); } /** * Returns description of update_template() parameters. * * @return external_function_parameters */ public static function update_template_parameters() { $structure = template_exporter::get_update_structure(); $params = array('template' => $structure); return new external_function_parameters($params); } /** * Update an existing learning plan template * * @param array $template The list of fields for the template. * @return boolean */ public static function update_template($template) { $params = self::validate_parameters(self::update_template_parameters(), array('template' => $template)); $params = $params['template']; $template = api::read_template($params['id']); self::validate_context($template->get_context()); $params = (object) $params; return api::update_template($params); } /** * Returns description of update_template() result value. * * @return external_description */ public static function update_template_returns() { return new external_value(PARAM_BOOL, 'True if the update was successful'); } /** * Returns description of duplicate_template() parameters. * * @return external_function_parameters */ public static function duplicate_template_parameters() { $templateid = new external_value( PARAM_INT, 'The template id', VALUE_REQUIRED ); $params = array( 'id' => $templateid ); return new external_function_parameters($params); } /** * Duplicate a learning plan template. * * @param int $id the id of the learning plan template to duplicate * @return boolean Record of new template. */ public static function duplicate_template($id) { global $PAGE; $params = self::validate_parameters(self::duplicate_template_parameters(), array('id' => $id)); $template = api::read_template($params['id']); self::validate_context($template->get_context()); $output = $PAGE->get_renderer('core'); $result = api::duplicate_template($params['id']); $exporter = new template_exporter($result); return $exporter->export($output); } /** * Returns description of duplicate_template() result value. * * @return external_description */ public static function duplicate_template_returns() { return template_exporter::get_read_structure(); } /** * Returns description of list_templates() parameters. * * @return external_function_parameters */ public static function list_templates_parameters() { $sort = new external_value( PARAM_ALPHANUMEXT, 'Column to sort by.', VALUE_DEFAULT, '' ); $order = new external_value( PARAM_ALPHA, 'Sort direction. Should be either ASC or DESC', VALUE_DEFAULT, '' ); $skip = new external_value( PARAM_INT, 'Skip this number of records before returning results', VALUE_DEFAULT, 0 ); $limit = new external_value( PARAM_INT, 'Return this number of records at most.', VALUE_DEFAULT, 0 ); $includes = new external_value( PARAM_ALPHA, 'What other contexts to fetch the templates from. (children, parents, self)', VALUE_DEFAULT, 'children' ); $onlyvisible = new external_value( PARAM_BOOL, 'If should list only visible templates', VALUE_DEFAULT, false ); $params = array( 'sort' => $sort, 'order' => $order, 'skip' => $skip, 'limit' => $limit, 'context' => self::get_context_parameters(), 'includes' => $includes, 'onlyvisible' => $onlyvisible ); return new external_function_parameters($params); } /** * List the existing learning plan templates * * @param string $sort Field to sort by. * @param string $order Sort order. * @param int $skip Limitstart. * @param int $limit Number of rows to return. * @param array $context * @param bool $includes * @param bool $onlyvisible * * @return array */ public static function list_templates($sort, $order, $skip, $limit, $context, $includes, $onlyvisible) { global $PAGE; $params = self::validate_parameters(self::list_templates_parameters(), array( 'sort' => $sort, 'order' => $order, 'skip' => $skip, 'limit' => $limit, 'context' => $context, 'includes' => $includes, 'onlyvisible' => $onlyvisible )); $context = self::get_context_from_params($params['context']); self::validate_context($context); $output = $PAGE->get_renderer('core'); if ($params['order'] !== '' && $params['order'] !== 'ASC' && $params['order'] !== 'DESC') { throw new invalid_parameter_exception('Invalid order param. Must be ASC, DESC or empty.'); } $results = api::list_templates($params['sort'], $params['order'], $params['skip'], $params['limit'], $context, $params['includes'], $params['onlyvisible']); $records = array(); foreach ($results as $result) { $exporter = new template_exporter($result); $record = $exporter->export($output); array_push($records, $record); } return $records; } /** * Returns description of list_templates() result value. * * @return external_description */ public static function list_templates_returns() { return new external_multiple_structure(template_exporter::get_read_structure()); } /** * Returns description of count_templates() parameters. * * @return external_function_parameters */ public static function count_templates_parameters() { $includes = new external_value( PARAM_ALPHA, 'What other contextes to fetch the frameworks from. (children, parents, self)', VALUE_DEFAULT, 'children' ); $params = array( 'context' => self::get_context_parameters(), 'includes' => $includes ); return new external_function_parameters($params); } /** * Count the existing learning plan templates * * @param array $context * @param string $includes * @return int */ public static function count_templates($context, $includes) { $params = self::validate_parameters(self::count_templates_parameters(), array( 'context' => $context, 'includes' => $includes )); $context = self::get_context_from_params($params['context']); self::validate_context($context); return api::count_templates($context, $includes); } /** * Returns description of count_templates() result value. * * @return external_description */ public static function count_templates_returns() { return new external_value(PARAM_INT, 'The number of learning plan templates found.'); } /** * Returns description of count_templates_using_competency() parameters. * * @return external_function_parameters */ public static function count_templates_using_competency_parameters() { $competencyid = new external_value( PARAM_INT, 'The competency id', VALUE_REQUIRED ); $params = array( 'id' => $competencyid, ); return new external_function_parameters($params); } /** * Count the learning plan templates (visible to this user) that use this competency. * * @param int $competencyid Competency id. * @return int */ public static function count_templates_using_competency($competencyid) { $params = self::validate_parameters(self::count_templates_using_competency_parameters(), array( 'id' => $competencyid, )); $competency = api::read_competency($params['id']); self::validate_context($competency->get_context()); return api::count_templates_using_competency($params['id']); } /** * Returns description of count_templates_using_competency() result value. * * @return external_description */ public static function count_templates_using_competency_returns() { return new external_value(PARAM_INT, 'The number of learning plan templates using this competency'); } /** * Returns description of list_templates_using_competency() parameters. * * @return external_function_parameters */ public static function list_templates_using_competency_parameters() { $competencyid = new external_value( PARAM_INT, 'The competency id', VALUE_REQUIRED ); $params = array( 'id' => $competencyid, ); return new external_function_parameters($params); } /** * List the learning plan templates (visible to this user) that use this competency. * * @param int $competencyid Competency id. * @return array */ public static function list_templates_using_competency($competencyid) { global $PAGE; $params = self::validate_parameters(self::list_templates_using_competency_parameters(), array( 'id' => $competencyid, )); $competency = api::read_competency($params['id']); self::validate_context($competency->get_context()); $output = $PAGE->get_renderer('core'); $templates = api::list_templates_using_competency($params['id']); $records = array(); foreach ($templates as $template) { $exporter = new template_exporter($template); $record = $exporter->export($output); $records[] = $record; } return $records; } /** * Returns description of list_templates_using_competency() result value. * * @return external_description */ public static function list_templates_using_competency_returns() { return new external_multiple_structure(template_exporter::get_read_structure()); } /** * Returns description of count_competencies_in_template() parameters. * * @return external_function_parameters */ public static function count_competencies_in_template_parameters() { $templateid = new external_value( PARAM_INT, 'The template id', VALUE_REQUIRED ); $params = array( 'id' => $templateid, ); return new external_function_parameters($params); } /** * Count the competencies (visible to this user) in this learning plan template. * * @param int $templateid The template id to check * @return int */ public static function count_competencies_in_template($templateid) { $params = self::validate_parameters(self::count_competencies_in_template_parameters(), array( 'id' => $templateid, )); $template = api::read_template($params['id']); self::validate_context($template->get_context()); return api::count_competencies_in_template($params['id']); } /** * Returns description of count_competencies_in_template() result value. * * @return external_description */ public static function count_competencies_in_template_returns() { return new external_value(PARAM_INT, 'The number of competencies in this learning plan template.'); } /** * Returns description of list_competencies_in_template() parameters. * * @return external_function_parameters */ public static function list_competencies_in_template_parameters() { $templateid = new external_value( PARAM_INT, 'The template id', VALUE_REQUIRED ); $params = array( 'id' => $templateid, ); return new external_function_parameters($params); } /** * List the competencies (visible to this user) in this learning plan template. * * @param int $templateid Template id. * @return array */ public static function list_competencies_in_template($templateid) { global $PAGE; $params = self::validate_parameters(self::list_competencies_in_template_parameters(), array( 'id' => $templateid, )); $template = api::read_template($params['id']); self::validate_context($template->get_context()); $output = $PAGE->get_renderer('core'); $competencies = api::list_competencies_in_template($params['id']); $results = array(); $helper = new performance_helper(); foreach ($competencies as $competency) { $context = $helper->get_context_from_competency($competency); $exporter = new competency_exporter($competency, array('context' => $context)); $record = $exporter->export($output); array_push($results, $record); } return $results; } /** * Returns description of list_competencies_in_template() result value. * * @return external_description */ public static function list_competencies_in_template_returns() { return new external_multiple_structure(competency_exporter::get_read_structure()); } /** * Returns description of add_competency_to_template() parameters. * * @return external_function_parameters */ public static function add_competency_to_template_parameters() { $templateid = new external_value( PARAM_INT, 'The template id', VALUE_REQUIRED ); $competencyid = new external_value( PARAM_INT, 'The competency id', VALUE_REQUIRED ); $params = array( 'templateid' => $templateid, 'competencyid' => $competencyid, ); return new external_function_parameters($params); } /** * Count the competencies (visible to this user) in this template. * * @param int $templateid Template id. * @param int $competencyid Competency id. * @return int */ public static function add_competency_to_template($templateid, $competencyid) { global $PAGE; $params = self::validate_parameters(self::add_competency_to_template_parameters(), array( 'templateid' => $templateid, 'competencyid' => $competencyid, )); $template = api::read_template($params['templateid']); self::validate_context($template->get_context()); return api::add_competency_to_template($params['templateid'], $params['competencyid']); } /** * Returns description of add_competency_to_template() result value. * * @return external_description */ public static function add_competency_to_template_returns() { return new external_value(PARAM_BOOL, 'True if successful.'); } /** * Returns description of add_competency_to_plan() parameters. * * @return external_function_parameters */ public static function add_competency_to_plan_parameters() { $planid = new external_value( PARAM_INT, 'The plan id', VALUE_REQUIRED ); $competencyid = new external_value( PARAM_INT, 'The competency id', VALUE_REQUIRED ); $params = array( 'planid' => $planid, 'competencyid' => $competencyid, ); return new external_function_parameters($params); } /** * add competency to a learning plan. * * @param int $planid Plan id. * @param int $competencyid Competency id. * @return int */ public static function add_competency_to_plan($planid, $competencyid) { $params = self::validate_parameters(self::add_competency_to_plan_parameters(), array( 'planid' => $planid, 'competencyid' => $competencyid, )); $plan = api::read_plan($params['planid']); self::validate_context($plan->get_context()); return api::add_competency_to_plan($params['planid'], $params['competencyid']); } /** * Returns description of add_competency_to_plan() result value. * * @return external_description */ public static function add_competency_to_plan_returns() { return new external_value(PARAM_BOOL, 'True if successful.'); } /** * Returns description of remove_competency_from_plan() parameters. * * @return external_function_parameters */ public static function remove_competency_from_plan_parameters() { $planid = new external_value( PARAM_INT, 'The plan id', VALUE_REQUIRED ); $competencyid = new external_value( PARAM_INT, 'The competency id', VALUE_REQUIRED ); $params = array( 'planid' => $planid, 'competencyid' => $competencyid, ); return new external_function_parameters($params); } /** * Remove a competency from plan. * * @param int $planid Plan id. * @param int $competencyid Competency id. * @return int */ public static function remove_competency_from_plan($planid, $competencyid) { $params = self::validate_parameters(self::remove_competency_from_plan_parameters(), array( 'planid' => $planid, 'competencyid' => $competencyid, )); $plan = api::read_plan($params['planid']); self::validate_context($plan->get_context()); return api::remove_competency_from_plan($params['planid'], $params['competencyid']); } /** * Returns description of remove_competency_from_plan() result value. * * @return external_description */ public static function remove_competency_from_plan_returns() { return new external_value(PARAM_BOOL, 'True if successful.'); } /** * Returns description of remove_competency_from_template() parameters. * * @return external_function_parameters */ public static function remove_competency_from_template_parameters() { $templateid = new external_value( PARAM_INT, 'The template id', VALUE_REQUIRED ); $competencyid = new external_value( PARAM_INT, 'The competency id', VALUE_REQUIRED ); $params = array( 'templateid' => $templateid, 'competencyid' => $competencyid, ); return new external_function_parameters($params); } /** * Returns description of reorder_plan_competency() parameters. * * @return external_function_parameters */ public static function reorder_plan_competency_parameters() { $planid = new external_value( PARAM_INT, 'The plan id', VALUE_REQUIRED ); $competencyidfrom = new external_value( PARAM_INT, 'The competency id we are moving', VALUE_REQUIRED ); $competencyidto = new external_value( PARAM_INT, 'The competency id we are moving to', VALUE_REQUIRED ); $params = array( 'planid' => $planid, 'competencyidfrom' => $competencyidfrom, 'competencyidto' => $competencyidto, ); return new external_function_parameters($params); } /** * Change the order of plan competencies. * * @param int $planid The plan id * @param int $competencyidfrom The competency to move. * @param int $competencyidto The competency to move to. * @return bool */ public static function reorder_plan_competency($planid, $competencyidfrom, $competencyidto) { $params = self::validate_parameters(self::reorder_plan_competency_parameters(), array( 'planid' => $planid, 'competencyidfrom' => $competencyidfrom, 'competencyidto' => $competencyidto, )); $plan = api::read_plan($params['planid']); self::validate_context($plan->get_context()); return api::reorder_plan_competency($params['planid'], $params['competencyidfrom'], $params['competencyidto']); } /** * Returns description of reorder_plan_competency() result value. * * @return external_description */ public static function reorder_plan_competency_returns() { return new external_value(PARAM_BOOL, 'True if successful.'); } /** * Returns description of external function parameters. * * @return external_function_parameters */ public static function user_competency_cancel_review_request_parameters() { return new external_function_parameters(array( 'userid' => new external_value(PARAM_INT, 'The user ID'), 'competencyid' => new external_value(PARAM_INT, 'The competency ID'), )); } /** * External function user_competency_cancel_review_request. * * @param int $userid The user ID. * @param int $competencyid The competency ID. * @return boolean */ public static function user_competency_cancel_review_request($userid, $competencyid) { $params = self::validate_parameters(self::user_competency_cancel_review_request_parameters(), array( 'userid' => $userid, 'competencyid' => $competencyid )); $context = context_user::instance($params['userid']); self::validate_context($context); return api::user_competency_cancel_review_request($userid, $competencyid); } /** * Returns description of external function result value. * * @return external_function_parameters */ public static function user_competency_cancel_review_request_returns() { return new external_value(PARAM_BOOL, 'The success'); } /** * Returns description of external function parameters. * * @return external_function_parameters */ public static function user_competency_request_review_parameters() { return new external_function_parameters(array( 'userid' => new external_value(PARAM_INT, 'The user ID'), 'competencyid' => new external_value(PARAM_INT, 'The competency ID'), )); } /** * External function user_competency_request_review. * * @param int $userid The user ID. * @param int $competencyid The competency ID. * @return boolean */ public static function user_competency_request_review($userid, $competencyid) { $params = self::validate_parameters(self::user_competency_request_review_parameters(), array( 'userid' => $userid, 'competencyid' => $competencyid, )); $context = context_user::instance($params['userid']); self::validate_context($context); return api::user_competency_request_review($userid, $competencyid); } /** * Returns description of external function result value. * * @return external_function_parameters */ public static function user_competency_request_review_returns() { return new external_value(PARAM_BOOL, 'The success'); } /** * Returns description of external function parameters. * * @return external_function_parameters */ public static function user_competency_start_review_parameters() { return new external_function_parameters(array( 'userid' => new external_value(PARAM_INT, 'The user ID'), 'competencyid' => new external_value(PARAM_INT, 'The competency ID'), )); } /** * External function user_competency_start_review. * * @param int $userid The user ID. * @param int $competencyid The competency ID. * @return boolean */ public static function user_competency_start_review($userid, $competencyid) { $params = self::validate_parameters(self::user_competency_start_review_parameters(), array( 'userid' => $userid, 'competencyid' => $competencyid )); $context = context_user::instance($params['userid']); self::validate_context($context); return api::user_competency_start_review($userid, $competencyid); } /** * Returns description of external function result value. * * @return external_function_parameters */ public static function user_competency_start_review_returns() { return new external_value(PARAM_BOOL, 'The success'); } /** * Returns description of external function parameters. * * @return external_function_parameters */ public static function user_competency_stop_review_parameters() { return new external_function_parameters(array( 'userid' => new external_value(PARAM_INT, 'The user ID'), 'competencyid' => new external_value(PARAM_INT, 'The competency ID'), )); } /** * External function user_competency_stop_review. * * @param int $userid The user ID. * @param int $competencyid The competency ID. * @return boolean */ public static function user_competency_stop_review($userid, $competencyid) { $params = self::validate_parameters(self::user_competency_stop_review_parameters(), array( 'userid' => $userid, 'competencyid' => $competencyid )); $context = context_user::instance($params['userid']); self::validate_context($context); return api::user_competency_stop_review($userid, $competencyid); } /** * Returns description of external function result value. * * @return external_function_parameters */ public static function user_competency_stop_review_returns() { return new external_value(PARAM_BOOL, 'The success'); } /** * Returns description of template_has_related_data() parameters. * * @return external_function_parameters */ public static function template_has_related_data_parameters() { $templateid = new external_value( PARAM_INT, 'The template id', VALUE_REQUIRED ); $params = array( 'id' => $templateid, ); return new external_function_parameters($params); } /** * Check if template has related data. * * @param int $templateid Template id. * @return boolean */ public static function template_has_related_data($templateid) { $params = self::validate_parameters(self::template_has_related_data_parameters(), array( 'id' => $templateid, )); $template = api::read_template($params['id']); self::validate_context($template->get_context()); return api::template_has_related_data($params['id']); } /** * Returns description of template_has_related_data() result value. * * @return external_description */ public static function template_has_related_data_returns() { return new external_value(PARAM_BOOL, 'True if the template has related data'); } /** * Count the competencies (visible to this user) in this learning plan template. * * @param int $templateid Template id. * @param int $competencyid Competency id. * @return int */ public static function remove_competency_from_template($templateid, $competencyid) { $params = self::validate_parameters(self::remove_competency_from_template_parameters(), array( 'templateid' => $templateid, 'competencyid' => $competencyid, )); $template = api::read_template($params['templateid']); self::validate_context($template->get_context()); return api::remove_competency_from_template($params['templateid'], $params['competencyid']); } /** * Returns description of remove_competency_from_template() result value. * * @return external_description */ public static function remove_competency_from_template_returns() { return new external_value(PARAM_BOOL, 'True if successful.'); } /** * Returns description of create_plan() parameters. * * @return external_function_parameters */ public static function create_plan_parameters() { $structure = plan_exporter::get_create_structure(); $params = array('plan' => $structure); return new external_function_parameters($params); } /** * Create a new learning plan. * * @param array $plan List of fields for the plan. * @return array New plan record. */ public static function create_plan($plan) { global $PAGE; $params = self::validate_parameters(self::create_plan_parameters(), array('plan' => $plan)); $params = $params['plan']; $context = context_user::instance($params['userid']); self::validate_context($context); $output = $PAGE->get_renderer('core'); $params = (object) $params; $result = api::create_plan($params); $exporter = new plan_exporter($result, array('template' => null)); return $exporter->export($output); } /** * Returns description of create_plan() result value. * * @return external_description */ public static function create_plan_returns() { return plan_exporter::get_read_structure(); } /** * Returns description of update_plan() parameters. * * @return external_function_parameters */ public static function update_plan_parameters() { $structure = plan_exporter::get_update_structure(); $params = array('plan' => $structure); return new external_function_parameters($params); } /** * Updates a new learning plan. * * @param array $plan Fields for the plan (id is required) * @return mixed */ public static function update_plan($plan) { global $PAGE; $params = self::validate_parameters(self::update_plan_parameters(), array('plan' => $plan)); $params = $params['plan']; $plan = api::read_plan($params['id']); self::validate_context($plan->get_context()); $output = $PAGE->get_renderer('core'); $params = (object) $params; $result = api::update_plan($params); $exporter = new plan_exporter($result, ['template' => $plan->get_template()]); return $exporter->export($output); } /** * Returns description of update_plan() result value. * * @return external_description */ public static function update_plan_returns() { return plan_exporter::get_read_structure(); } /** * Returns description of complete_plan() parameters. * * @return external_function_parameters */ public static function complete_plan_parameters() { $planid = new external_value( PARAM_INT, 'The plan id', VALUE_REQUIRED ); $params = array('planid' => $planid); return new external_function_parameters($params); } /** * Complete Learning plan. * * @param int $planid plan id (id is required) * @return boolean */ public static function complete_plan($planid) { $params = self::validate_parameters(self::complete_plan_parameters(), array( 'planid' => $planid )); return api::complete_plan($params['planid']); } /** * Returns description of complete_plan() result value. * * @return external_description */ public static function complete_plan_returns() { return new external_value(PARAM_BOOL, 'True if completing learning plan was successful'); } /** * Returns description of reopen_plan() parameters. * * @return external_function_parameters */ public static function reopen_plan_parameters() { $planid = new external_value( PARAM_INT, 'The plan id', VALUE_REQUIRED ); $params = array('planid' => $planid); return new external_function_parameters($params); } /** * Reopen Learning plan. * * @param int $planid plan id (id is required) * @return boolean */ public static function reopen_plan($planid) { $params = self::validate_parameters(self::reopen_plan_parameters(), array( 'planid' => $planid )); return api::reopen_plan($params['planid']); } /** * Returns description of reopen_plan() result value. * * @return external_description */ public static function reopen_plan_returns() { return new external_value(PARAM_BOOL, 'True if reopening learning plan was successful'); } /** * Returns description of read_plan() parameters. * * @return external_function_parameters */ public static function read_plan_parameters() { $id = new external_value( PARAM_INT, 'Data base record id for the plan', VALUE_REQUIRED ); return new external_function_parameters(array('id' => $id)); } /** * Read a plan by id. * * @param int $id The id of the plan. * @return \stdClass */ public static function read_plan($id) { global $PAGE; $params = self::validate_parameters(self::read_plan_parameters(), array( 'id' => $id, )); $plan = api::read_plan($params['id']); self::validate_context($plan->get_context()); $output = $PAGE->get_renderer('core'); $exporter = new plan_exporter($plan, array('template' => $plan->get_template())); $record = $exporter->export($output); return external_api::clean_returnvalue(self::read_plan_returns(), $record); } /** * Returns description of read_plan() result value. * * @return external_description */ public static function read_plan_returns() { return plan_exporter::get_read_structure(); } /** * Returns description of delete_plan() parameters. * * @return external_function_parameters */ public static function delete_plan_parameters() { $id = new external_value( PARAM_INT, 'Data base record id for the learning plan', VALUE_REQUIRED ); $params = array( 'id' => $id, ); return new external_function_parameters($params); } /** * Delete a plan. * * @param int $id The plan id * @return boolean */ public static function delete_plan($id) { $params = self::validate_parameters(self::delete_plan_parameters(), array( 'id' => $id, )); $plan = api::read_plan($params['id']); self::validate_context($plan->get_context()); return external_api::clean_returnvalue(self::delete_plan_returns(), api::delete_plan($params['id'])); } /** * Returns description of delete_plan() result value. * * @return external_description */ public static function delete_plan_returns() { return new external_value(PARAM_BOOL, 'True if the delete was successful'); } /** * Returns description of external function parameters. * * @return external_function_parameters */ public static function plan_cancel_review_request_parameters() { return new external_function_parameters(array( 'id' => new external_value(PARAM_INT, 'The plan ID'), )); } /** * External function plan_cancel_review_request. * * @param int $id The plan ID. * @return boolean */ public static function plan_cancel_review_request($id) { $params = self::validate_parameters(self::plan_cancel_review_request_parameters(), array( 'id' => $id )); $plan = api::read_plan($id); self::validate_context($plan->get_context()); return api::plan_cancel_review_request($plan); } /** * Returns description of external function result value. * * @return external_function_parameters */ public static function plan_cancel_review_request_returns() { return new external_value(PARAM_BOOL, 'The success'); } /** * Returns description of external function parameters. * * @return external_function_parameters */ public static function plan_request_review_parameters() { return new external_function_parameters(array( 'id' => new external_value(PARAM_INT, 'The plan ID'), )); } /** * External function plan_request_review. * * @param int $id The plan ID. * @return boolean */ public static function plan_request_review($id) { $params = self::validate_parameters(self::plan_request_review_parameters(), array( 'id' => $id )); $plan = api::read_plan($id); self::validate_context($plan->get_context()); return api::plan_request_review($plan); } /** * Returns description of external function result value. * * @return external_function_parameters */ public static function plan_request_review_returns() { return new external_value(PARAM_BOOL, 'The success'); } /** * Returns description of external function parameters. * * @return external_function_parameters */ public static function plan_start_review_parameters() { return new external_function_parameters(array( 'id' => new external_value(PARAM_INT, 'The plan ID'), )); } /** * External function plan_start_review. * * @param int $id The plan ID. * @return boolean */ public static function plan_start_review($id) { $params = self::validate_parameters(self::plan_start_review_parameters(), array( 'id' => $id )); $plan = api::read_plan($id); self::validate_context($plan->get_context()); return api::plan_start_review($plan); } /** * Returns description of external function result value. * * @return external_function_parameters */ public static function plan_start_review_returns() { return new external_value(PARAM_BOOL, 'The success'); } /** * Returns description of external function parameters. * * @return external_function_parameters */ public static function plan_stop_review_parameters() { return new external_function_parameters(array( 'id' => new external_value(PARAM_INT, 'The plan ID'), )); } /** * External function plan_stop_review. * * @param int $id The plan ID. * @return boolean */ public static function plan_stop_review($id) { $params = self::validate_parameters(self::plan_stop_review_parameters(), array( 'id' => $id )); $plan = api::read_plan($id); self::validate_context($plan->get_context()); return api::plan_stop_review($plan); } /** * Returns description of external function result value. * * @return external_function_parameters */ public static function plan_stop_review_returns() { return new external_value(PARAM_BOOL, 'The success'); } /** * Returns description of external function parameters. * * @return external_function_parameters */ public static function approve_plan_parameters() { return new external_function_parameters(array( 'id' => new external_value(PARAM_INT, 'The plan ID'), )); } /** * External function approve_plan. * * @param int $id The plan ID. * @return boolean */ public static function approve_plan($id) { $params = self::validate_parameters(self::approve_plan_parameters(), array( 'id' => $id, )); $plan = api::read_plan($id); self::validate_context($plan->get_context()); return api::approve_plan($plan); } /** * Returns description of external function result value. * * @return external_function_parameters */ public static function approve_plan_returns() { return new external_value(PARAM_BOOL, 'The success'); } /** * Returns description of external function parameters. * * @return external_function_parameters */ public static function unapprove_plan_parameters() { return new external_function_parameters(array( 'id' => new external_value(PARAM_INT, 'The plan ID'), )); } /** * External function unapprove_plan. * * @param int $id The plan ID. * @return boolean */ public static function unapprove_plan($id) { $params = self::validate_parameters(self::unapprove_plan_parameters(), array( 'id' => $id, )); $plan = api::read_plan($id); self::validate_context($plan->get_context()); return api::unapprove_plan($plan); } /** * Returns description of external function result value. * * @return external_function_parameters */ public static function unapprove_plan_returns() { return new external_value(PARAM_BOOL, 'The success'); } /** * External function parameters structure. * * @return external_description */ public static function list_plan_competencies_parameters() { return new external_function_parameters(array( 'id' => new external_value(PARAM_INT, 'The plan ID.') )); } /** * List plan competencies. * @param int $id The plan ID. * @return array */ public static function list_plan_competencies($id) { global $PAGE; $params = self::validate_parameters(self::list_plan_competencies_parameters(), array('id' => $id)); $id = $params['id']; $plan = api::read_plan($id); $usercontext = $plan->get_context(); self::validate_context($usercontext); $output = $PAGE->get_renderer('core'); $result = api::list_plan_competencies($plan); if ($plan->get('status') == plan::STATUS_COMPLETE) { $ucproperty = 'usercompetencyplan'; } else { $ucproperty = 'usercompetency'; } $helper = new performance_helper(); foreach ($result as $key => $r) { $context = $helper->get_context_from_competency($r->competency); $scale = $helper->get_scale_from_competency($r->competency); $exporter = new competency_exporter($r->competency, array('context' => $context)); $r->competency = $exporter->export($output); if ($r->usercompetency) { $exporter = new user_competency_exporter($r->usercompetency, array('scale' => $scale)); $r->usercompetency = $exporter->export($output); unset($r->usercompetencyplan); } else { $exporter = new user_competency_plan_exporter($r->usercompetencyplan, array('scale' => $scale)); $r->usercompetencyplan = $exporter->export($output); unset($r->usercompetency); } } return $result; } /** * External function return structure. * * @return external_description */ public static function list_plan_competencies_returns() { $uc = user_competency_exporter::get_read_structure(); $ucp = user_competency_plan_exporter::get_read_structure(); $uc->required = VALUE_OPTIONAL; $ucp->required = VALUE_OPTIONAL; return new external_multiple_structure( new external_single_structure(array( 'competency' => competency_exporter::get_read_structure(), 'usercompetency' => $uc, 'usercompetencyplan' => $ucp )) ); } /** * Returns description of external function parameters. * * @return external_function_parameters */ public static function list_user_plans_parameters() { return new external_function_parameters(array( 'userid' => new external_value(PARAM_INT, 'The user ID'), )); } /** * External function list_user_plans. * * @param int $userid The user ID. * @return boolean */ public static function list_user_plans($userid) { global $PAGE; $params = self::validate_parameters(self::list_user_plans_parameters(), array( 'userid' => $userid )); $context = context_user::instance($params['userid']); self::validate_context($context); $output = $PAGE->get_renderer('core'); $response = array(); $plans = api::list_user_plans($params['userid']); foreach ($plans as $plan) { $exporter = new plan_exporter($plan, array('template' => $plan->get_template())); $response[] = $exporter->export($output); } return $response; } /** * Returns description of external function result value. * * @return external_function_parameters */ public static function list_user_plans_returns() { return new external_multiple_structure( plan_exporter::get_read_structure() ); } /** * Returns description of external function parameters. * * @return external_description */ public static function read_user_evidence_parameters() { return new external_function_parameters(array( 'id' => new external_value(PARAM_INT, 'The user evidence ID.'), )); } /** * Delete a user evidence. * * @param int $id The evidence id * @return boolean */ public static function read_user_evidence($id) { global $PAGE; $params = self::validate_parameters(self::read_user_evidence_parameters(), array('id' => $id)); $userevidence = api::read_user_evidence($params['id']); $context = $userevidence->get_context(); self::validate_context($context); $output = $PAGE->get_renderer('core'); $exporter = new user_evidence_exporter($userevidence, array('context' => $context, 'competencies' => $userevidence->get_competencies())); return $exporter->export($output); } /** * Returns description of external function result value. * * @return external_description */ public static function read_user_evidence_returns() { return user_evidence_exporter::get_read_structure(); } /** * Returns description of external function parameters. * * @return external_function_parameters */ public static function delete_user_evidence_parameters() { return new external_function_parameters(array( 'id' => new external_value(PARAM_INT, 'The user evidence ID.'), )); } /** * Delete a user evidence. * * @param int $id The evidence id * @return boolean */ public static function delete_user_evidence($id) { $params = self::validate_parameters(self::delete_user_evidence_parameters(), array('id' => $id)); $userevidence = api::read_user_evidence($params['id']); self::validate_context($userevidence->get_context()); return api::delete_user_evidence($userevidence->get('id')); } /** * Returns description of external function result value. * * @return external_description */ public static function delete_user_evidence_returns() { return new external_value(PARAM_BOOL, 'True if the delete was successful'); } /** * Returns description of external function parameters. * * @return external_function_parameters */ public static function create_user_evidence_competency_parameters() { return new external_function_parameters(array( 'userevidenceid' => new external_value(PARAM_INT, 'The user evidence ID.'), 'competencyid' => new external_value(PARAM_INT, 'The competency ID.'), )); } /** * Delete a user evidence competency relationship. * * @param int $userevidenceid The user evidence id. * @param int $competencyid The competency id. * @return boolean */ public static function create_user_evidence_competency($userevidenceid, $competencyid) { global $PAGE; $params = self::validate_parameters(self::create_user_evidence_competency_parameters(), array( 'userevidenceid' => $userevidenceid, 'competencyid' => $competencyid, )); $userevidence = api::read_user_evidence($params['userevidenceid']); self::validate_context($userevidence->get_context()); $relation = api::create_user_evidence_competency($userevidence, $competencyid); $exporter = new user_evidence_competency_exporter($relation); return $exporter->export($PAGE->get_renderer('core')); } /** * Returns description of external function result value. * * @return external_description */ public static function create_user_evidence_competency_returns() { return user_evidence_competency_exporter::get_read_structure(); } /** * Returns description of external function parameters. * * @return external_function_parameters */ public static function delete_user_evidence_competency_parameters() { return new external_function_parameters(array( 'userevidenceid' => new external_value(PARAM_INT, 'The user evidence ID.'), 'competencyid' => new external_value(PARAM_INT, 'The competency ID.'), )); } /** * Delete a user evidence competency relationship. * * @param int $userevidenceid The user evidence id. * @param int $competencyid The competency id. * @return boolean */ public static function delete_user_evidence_competency($userevidenceid, $competencyid) { $params = self::validate_parameters(self::delete_user_evidence_competency_parameters(), array( 'userevidenceid' => $userevidenceid, 'competencyid' => $competencyid, )); $userevidence = api::read_user_evidence($params['userevidenceid']); self::validate_context($userevidence->get_context()); return api::delete_user_evidence_competency($userevidence, $params['competencyid']); } /** * Returns description of external function result value. * * @return external_description */ public static function delete_user_evidence_competency_returns() { return new external_value(PARAM_BOOL, 'True if the delete was successful'); } /** * Returns description of external function parameters. * * @return external_function_parameters */ public static function request_review_of_user_evidence_linked_competencies_parameters() { return new external_function_parameters(array( 'id' => new external_value(PARAM_INT, 'The user evidence ID.') )); } /** * Send user evidence competencies to review. * * @param int $id The user evidence id. * @return boolean */ public static function request_review_of_user_evidence_linked_competencies($id) { $params = self::validate_parameters(self::request_review_of_user_evidence_linked_competencies_parameters(), array( 'id' => $id )); $userevidence = api::read_user_evidence($id); self::validate_context($userevidence->get_context()); return api::request_review_of_user_evidence_linked_competencies($id); } /** * Returns description of external function result value. * * @return external_description */ public static function request_review_of_user_evidence_linked_competencies_returns() { return new external_value(PARAM_BOOL, 'True if all competencies were send to review'); } /** * Returns the description of the get_scale_values() parameters. * * @return external_function_parameters. */ public static function get_scale_values_parameters() { $scaleid = new external_value( PARAM_INT, 'The scale id', VALUE_REQUIRED ); $params = array('scaleid' => $scaleid); return new external_function_parameters($params); } /** * Get the values associated with a scale. * * @param int $scaleid Scale ID * @return array Values for a scale. */ public static function get_scale_values($scaleid) { global $DB; $params = self::validate_parameters(self::get_scale_values_parameters(), array( 'scaleid' => $scaleid, ) ); $context = context_system::instance(); self::validate_context($context); // The following section is not learning plan specific and so has not been moved to the api. // Retrieve the scale value from the database. $scale = grade_scale::fetch(array('id' => $scaleid)); $scalevalues = $scale->load_items(); foreach ($scalevalues as $key => $value) { // Add a key (make the first value 1). $scalevalues[$key] = array( 'id' => $key + 1, 'name' => \core_external\util::format_string($value, $context->id) ); } return $scalevalues; } /** * Returns description of get_scale_values() result value. * * @return external_multiple_structure */ public static function get_scale_values_returns() { return new external_multiple_structure( new external_single_structure(array( 'id' => new external_value(PARAM_INT, 'Scale value ID'), 'name' => new external_value(PARAM_RAW, 'Scale value name') )) ); } /** * Returns the description of the add_related_competency_parameters() parameters. * * @return external_function_parameters. */ public static function add_related_competency_parameters() { $competencyid = new external_value( PARAM_INT, 'The competency id', VALUE_REQUIRED ); $relatedcompetencyid = new external_value( PARAM_INT, 'The related competency id', VALUE_REQUIRED ); $params = array( 'competencyid' => $competencyid, 'relatedcompetencyid' => $relatedcompetencyid ); return new external_function_parameters($params); } /** * Adds a related competency. * * @param int $competencyid * @param int $relatedcompetencyid * @return bool */ public static function add_related_competency($competencyid, $relatedcompetencyid) { $params = self::validate_parameters(self::add_related_competency_parameters(), array( 'competencyid' => $competencyid, 'relatedcompetencyid' => $relatedcompetencyid )); $competency = api::read_competency($params['competencyid']); self::validate_context($competency->get_context()); return api::add_related_competency($params['competencyid'], $params['relatedcompetencyid']); } /** * Returns description of add_related_competency_returns() result value. * * @return \core_external\external_description */ public static function add_related_competency_returns() { return new external_value(PARAM_BOOL, 'True if successful.'); } /** * Returns the description of the remove_related_competency_parameters() parameters. * * @return external_function_parameters. */ public static function remove_related_competency_parameters() { $competencyid = new external_value( PARAM_INT, 'The competency id', VALUE_REQUIRED ); $relatedcompetencyid = new external_value( PARAM_INT, 'The related competency id', VALUE_REQUIRED ); $params = array( 'competencyid' => $competencyid, 'relatedcompetencyid' => $relatedcompetencyid ); return new external_function_parameters($params); } /** * Removes a related competency. * * @param int $competencyid * @param int $relatedcompetencyid * @return bool */ public static function remove_related_competency($competencyid, $relatedcompetencyid) { $params = self::validate_parameters(self::remove_related_competency_parameters(), array( 'competencyid' => $competencyid, 'relatedcompetencyid' => $relatedcompetencyid )); $competency = api::read_competency($params['competencyid']); self::validate_context($competency->get_context()); return api::remove_related_competency($params['competencyid'], $params['relatedcompetencyid']); } /** * Returns description of remove_related_competency_returns() result value. * * @return \core_external\external_description */ public static function remove_related_competency_returns() { return new external_value(PARAM_BOOL, 'True if successful.'); } /** * Returns description of update_ruleoutcome_course_competency() parameters. * * @return external_function_parameters */ public static function set_course_competency_ruleoutcome_parameters() { $coursecompetencyid = new external_value( PARAM_INT, 'Data base record id for the course competency', VALUE_REQUIRED ); $ruleoutcome = new external_value( PARAM_INT, 'Ruleoutcome value', VALUE_REQUIRED ); $params = array( 'coursecompetencyid' => $coursecompetencyid, 'ruleoutcome' => $ruleoutcome, ); return new external_function_parameters($params); } /** * Change the ruleoutcome of a course competency. * * @param int $coursecompetencyid The course competency id * @param int $ruleoutcome The ruleoutcome value * @return bool */ public static function set_course_competency_ruleoutcome($coursecompetencyid, $ruleoutcome) { $params = self::validate_parameters(self::set_course_competency_ruleoutcome_parameters(), array( 'coursecompetencyid' => $coursecompetencyid, 'ruleoutcome' => $ruleoutcome, )); $coursecompetency = new course_competency($params['coursecompetencyid']); self::validate_context(context_course::instance($coursecompetency->get('courseid'))); return api::set_course_competency_ruleoutcome($coursecompetency, $params['ruleoutcome']); } /** * Returns description of update_ruleoutcome_course_competency() result value. * * @return external_value */ public static function set_course_competency_ruleoutcome_returns() { return new external_value(PARAM_BOOL, 'True if the update was successful'); } /** * Returns description of external function parameters. * * @return external_function_parameters */ public static function grade_competency_parameters() { $userid = new external_value( PARAM_INT, 'User ID', VALUE_REQUIRED ); $competencyid = new external_value( PARAM_INT, 'Competency ID', VALUE_REQUIRED ); $grade = new external_value( PARAM_INT, 'New grade', VALUE_REQUIRED ); $note = new external_value( PARAM_NOTAGS, 'A note to attach to the evidence', VALUE_DEFAULT ); $params = array( 'userid' => $userid, 'competencyid' => $competencyid, 'grade' => $grade, 'note' => $note, ); return new external_function_parameters($params); } /** * Grade a competency. * * @param int $userid The user ID. * @param int $competencyid The competency id * @param int $grade The new grade value * @param string $note A note to attach to the evidence * @return bool */ public static function grade_competency($userid, $competencyid, $grade, $note = null) { global $USER, $PAGE; $params = self::validate_parameters(self::grade_competency_parameters(), array( 'userid' => $userid, 'competencyid' => $competencyid, 'grade' => $grade, 'note' => $note )); $uc = api::get_user_competency($params['userid'], $params['competencyid']); self::validate_context($uc->get_context()); $output = $PAGE->get_renderer('core'); $evidence = api::grade_competency( $uc->get('userid'), $uc->get('competencyid'), $params['grade'], $params['note'] ); $scale = $uc->get_competency()->get_scale(); $exporter = new evidence_exporter($evidence, [ 'actionuser' => $USER, 'scale' => $scale, 'usercompetency' => $uc, 'usercompetencyplan' => null, 'context' => $evidence->get_context() ]); return $exporter->export($output); } /** * Returns description of external function result value. * * @return external_value */ public static function grade_competency_returns() { return evidence_exporter::get_read_structure(); } /** * Returns description of grade_competency_in_plan() parameters. * * @return external_function_parameters */ public static function grade_competency_in_plan_parameters() { $planid = new external_value( PARAM_INT, 'Plan id', VALUE_REQUIRED ); $competencyid = new external_value( PARAM_INT, 'Competency id', VALUE_REQUIRED ); $grade = new external_value( PARAM_INT, 'New grade', VALUE_REQUIRED ); $note = new external_value( PARAM_NOTAGS, 'A note to attach to the evidence', VALUE_DEFAULT ); $params = array( 'planid' => $planid, 'competencyid' => $competencyid, 'grade' => $grade, 'note' => $note ); return new external_function_parameters($params); } /** * Grade a competency in a plan. * * @param int $planid The plan id * @param int $competencyid The competency id * @param int $grade The new grade value * @param string $note A note to add to the evidence * @return bool */ public static function grade_competency_in_plan($planid, $competencyid, $grade, $note = null) { global $USER, $PAGE; $params = self::validate_parameters(self::grade_competency_in_plan_parameters(), array( 'planid' => $planid, 'competencyid' => $competencyid, 'grade' => $grade, 'note' => $note )); $plan = new plan($params['planid']); $context = $plan->get_context(); self::validate_context($context); $output = $PAGE->get_renderer('core'); $evidence = api::grade_competency_in_plan( $plan->get('id'), $params['competencyid'], $params['grade'], $params['note'] ); $competency = api::read_competency($params['competencyid']); $scale = $competency->get_scale(); $exporter = new evidence_exporter($evidence, [ 'actionuser' => $USER, 'scale' => $scale, 'usercompetency' => null, 'usercompetencyplan' => null, 'context' => $evidence->get_context() ]); return $exporter->export($output); } /** * Returns description of grade_competency_in_plan() result value. * * @return external_value */ public static function grade_competency_in_plan_returns() { return evidence_exporter::get_read_structure(); } /** * Returns description of user_competency_viewed() parameters. * * @return external_function_parameters */ public static function user_competency_viewed_parameters() { $usercompetencyid = new external_value( PARAM_INT, 'The user competency id', VALUE_REQUIRED ); $params = array( 'usercompetencyid' => $usercompetencyid ); return new external_function_parameters($params); } /** * Log user competency viewed event. * * @param int $usercompetencyid The user competency ID. * @return boolean */ public static function user_competency_viewed($usercompetencyid) { $params = self::validate_parameters(self::user_competency_viewed_parameters(), array( 'usercompetencyid' => $usercompetencyid )); $uc = api::get_user_competency_by_id($params['usercompetencyid']); $result = api::user_competency_viewed($uc); return $result; } /** * Returns description of user_competency_viewed() result value. * * @return external_description */ public static function user_competency_viewed_returns() { return new external_value(PARAM_BOOL, 'True if the event user competency viewed was logged'); } /** * Returns description of user_competency_viewed_in_plan() parameters. * * @return external_function_parameters */ public static function user_competency_viewed_in_plan_parameters() { $competencyid = new external_value( PARAM_INT, 'The competency id', VALUE_REQUIRED ); $userid = new external_value( PARAM_INT, 'The user id', VALUE_REQUIRED ); $planid = new external_value( PARAM_INT, 'The plan id', VALUE_REQUIRED ); $params = array( 'competencyid' => $competencyid, 'userid' => $userid, 'planid' => $planid ); return new external_function_parameters($params); } /** * Log user competency viewed in plan event. * * @param int $competencyid The competency ID. * @param int $userid The user ID. * @param int $planid The plan ID. * @return boolean */ public static function user_competency_viewed_in_plan($competencyid, $userid, $planid) { $params = self::validate_parameters(self::user_competency_viewed_in_plan_parameters(), array( 'competencyid' => $competencyid, 'userid' => $userid, 'planid' => $planid )); $pl = api::get_plan_competency($params['planid'], $params['competencyid']); $result = api::user_competency_viewed_in_plan($pl->usercompetency, $params['planid']); return $result; } /** * Returns description of user_competency_viewed_in_plan() result value. * * @return external_description */ public static function user_competency_viewed_in_plan_returns() { return new external_value(PARAM_BOOL, 'True if the event user competency viewed in plan was logged'); } /** * Returns description of user_competency_viewed_in_course() parameters. * * @return external_function_parameters */ public static function user_competency_viewed_in_course_parameters() { $competencyid = new external_value( PARAM_INT, 'The competency id', VALUE_REQUIRED ); $userid = new external_value( PARAM_INT, 'The user id', VALUE_REQUIRED ); $courseid = new external_value( PARAM_INT, 'The course id', VALUE_REQUIRED ); $params = array( 'competencyid' => $competencyid, 'userid' => $userid, 'courseid' => $courseid ); return new external_function_parameters($params); } /** * Log user competency viewed in course event. * * @param int $competencyid The competency ID. * @param int $userid The user ID. * @param int $courseid The course ID. * @return boolean */ public static function user_competency_viewed_in_course($competencyid, $userid, $courseid) { $params = self::validate_parameters(self::user_competency_viewed_in_course_parameters(), array( 'competencyid' => $competencyid, 'userid' => $userid, 'courseid' => $courseid )); $ucc = api::get_user_competency_in_course($params['courseid'], $params['userid'], $params['competencyid']); $result = api::user_competency_viewed_in_course($ucc); return $result; } /** * Returns description of user_competency_viewed_in_course() result value. * * @return external_description */ public static function user_competency_viewed_in_course_returns() { return new external_value(PARAM_BOOL, 'True if the event user competency viewed in course was logged'); } /** * Returns description of user_competency_plan_viewed() parameters. * * @return external_function_parameters */ public static function user_competency_plan_viewed_parameters() { $competencyid = new external_value( PARAM_INT, 'The competency id', VALUE_REQUIRED ); $userid = new external_value( PARAM_INT, 'The user id', VALUE_REQUIRED ); $planid = new external_value( PARAM_INT, 'The plan id', VALUE_REQUIRED ); $params = array( 'competencyid' => $competencyid, 'userid' => $userid, 'planid' => $planid ); return new external_function_parameters($params); } /** * Log user competency plan viewed event. * * @param int $competencyid The competency ID. * @param int $userid The user ID. * @param int $planid The plan ID. * @return boolean */ public static function user_competency_plan_viewed($competencyid, $userid, $planid) { $params = self::validate_parameters(self::user_competency_viewed_in_plan_parameters(), array( 'competencyid' => $competencyid, 'userid' => $userid, 'planid' => $planid )); $pl = api::get_plan_competency($params['planid'], $params['competencyid']); $result = api::user_competency_plan_viewed($pl->usercompetencyplan); return $result; } /** * Returns description of user_competency_plan_viewed() result value. * * @return external_description */ public static function user_competency_plan_viewed_returns() { return new external_value(PARAM_BOOL, 'True if the event user competency plan viewed was logged'); } /** * Returns description of grade_competency_in_course() parameters. * * @return external_function_parameters */ public static function grade_competency_in_course_parameters() { $courseid = new external_value( PARAM_INT, 'Course id', VALUE_REQUIRED ); $userid = new external_value( PARAM_INT, 'User id', VALUE_REQUIRED ); $competencyid = new external_value( PARAM_INT, 'Competency id', VALUE_REQUIRED ); $grade = new external_value( PARAM_INT, 'New grade', VALUE_REQUIRED ); $note = new external_value( PARAM_NOTAGS, 'A note to attach to the evidence', VALUE_DEFAULT ); $params = array( 'courseid' => $courseid, 'userid' => $userid, 'competencyid' => $competencyid, 'grade' => $grade, 'note' => $note, ); return new external_function_parameters($params); } /** * Grade a competency in a course. * * @param int $courseid The course id * @param int $userid The user id * @param int $competencyid The competency id * @param int $grade The new grade value * @param string $note A note to add to the evidence * @return bool */ public static function grade_competency_in_course($courseid, $userid, $competencyid, $grade, $note = null) { global $USER, $PAGE, $DB; $params = self::validate_parameters(self::grade_competency_in_course_parameters(), array( 'courseid' => $courseid, 'userid' => $userid, 'competencyid' => $competencyid, 'grade' => $grade, 'note' => $note )); $course = $DB->get_record('course', array('id' => $params['courseid'])); $context = context_course::instance($course->id); self::validate_context($context); $output = $PAGE->get_renderer('core'); $evidence = api::grade_competency_in_course( $params['courseid'], $params['userid'], $params['competencyid'], $params['grade'], $params['note'] ); $competency = api::read_competency($params['competencyid']); $scale = $competency->get_scale(); $exporter = new evidence_exporter($evidence, array( 'actionuser' => $USER, 'scale' => $scale, 'usercompetency' => null, 'usercompetencyplan' => null, 'context' => $evidence->get_context(), )); return $exporter->export($output); } /** * Returns description of grade_competency_in_course() result value. * * @return external_value */ public static function grade_competency_in_course_returns() { return evidence_exporter::get_read_structure(); } /** * Returns description of unlink_plan_from_template_() parameters. * * @return external_function_parameters */ public static function unlink_plan_from_template_parameters() { $planid = new external_value( PARAM_INT, 'Data base record id for the plan', VALUE_REQUIRED ); $params = array( 'planid' => $planid, ); return new external_function_parameters($params); } /** * Unlink the plan from the template. * * @param int $planid The plan id * @return bool */ public static function unlink_plan_from_template($planid) { $params = self::validate_parameters(self::unlink_plan_from_template_parameters(), array( 'planid' => $planid, )); $plan = new plan($params['planid']); self::validate_context($plan->get_context()); return api::unlink_plan_from_template($plan); } /** * Returns description of unlink_plan_from_template_() result value. * * @return external_value */ public static function unlink_plan_from_template_returns() { return new external_value(PARAM_BOOL, 'True if the unlink was successful'); } /** * Returns description of template_viewed() parameters. * * @return external_function_parameters */ public static function template_viewed_parameters() { $id = new external_value( PARAM_INT, 'Data base record id for the template', VALUE_REQUIRED ); $params = array( 'id' => $id, ); return new external_function_parameters($params); } /** * Log the template viewed event. * * @param int $id the template id * @return array of warnings and status result * @throws moodle_exception */ public static function template_viewed($id) { $params = self::validate_parameters(self::view_book_parameters(), array( 'id' => $id )); $template = api::read_template($params['id']); self::validate_context($template->get_context()); return api::template_viewed($params['id']); } /** * Returns description of template_viewed() result value. * * @return external_value */ public static function template_viewed_returns() { return new external_value(PARAM_BOOL, 'True if the log of the view was successful'); } /** * Returns description of update_course_competency_settings() parameters. * * @return external_function_parameters */ public static function update_course_competency_settings_parameters() { $courseid = new external_value( PARAM_INT, 'Course id for the course to update', VALUE_REQUIRED ); $pushratingstouserplans = new external_value( PARAM_BOOL, 'New value of the setting', VALUE_REQUIRED ); $settings = new external_single_structure(array( 'pushratingstouserplans' => $pushratingstouserplans )); $params = array( 'courseid' => $courseid, 'settings' => $settings, ); return new external_function_parameters($params); } /** * Update the course competency settings * * @param int $courseid the course id * @param stdClass $settings The list of settings (currently only pushratingstouserplans). * @throws moodle_exception */ public static function update_course_competency_settings($courseid, $settings) { $params = self::validate_parameters(self::update_course_competency_settings_parameters(), array( 'courseid' => $courseid, 'settings' => $settings )); $context = context_course::instance($params['courseid']); self::validate_context($context); $result = api::update_course_competency_settings($params['courseid'], $params['settings']); return $result; } /** * Returns description of update_course_competency_settings() result value. * * @return external_value */ public static function update_course_competency_settings_returns() { return new external_value(PARAM_BOOL, 'True if the update was successful.'); } /** * Returns description of external function parameters. * * @return external_function_parameters */ public static function delete_evidence_parameters() { return new external_function_parameters(array( 'id' => new external_value(PARAM_INT, 'The evidence ID'), )); } /** * External function delete_evidence. * * @param int $id The evidence ID. * @return boolean */ public static function delete_evidence($id) { $params = self::validate_parameters(self::delete_evidence_parameters(), array( 'id' => $id )); $evidence = api::read_evidence($params['id']); $uc = api::get_user_competency_by_id($evidence->get('usercompetencyid')); self::validate_context($uc->get_context()); return api::delete_evidence($evidence); } /** * Returns description of external function result value. * * @return external_function_parameters */ public static function delete_evidence_returns() { return new external_value(PARAM_BOOL, 'The success'); } } classes/reportbuilder/datasource/competencies.php000064400000014251152265256010016413 0ustar00. declare(strict_types=1); namespace core_competency\reportbuilder\datasource; use core\reportbuilder\local\entities\context; use core_cohort\reportbuilder\local\entities\cohort; use core_competency\reportbuilder\local\entities\{competency, framework, usercompetency}; use core_reportbuilder\datasource; use core_reportbuilder\local\entities\user; use core_reportbuilder\local\filters\boolean_select; use core_reportbuilder\local\helpers\database; /** * Competencies datasource * * @package core_competency * @copyright 2024 Paul Holden * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class competencies extends datasource { /** * Return user friendly name of the report source * * @return string */ public static function get_name(): string { return get_string('competencies', 'core_competency'); } /** * Initialise report */ protected function initialise(): void { $frameworkentity = new framework(); $frameworkalias = $frameworkentity->get_table_alias('competency_framework'); $contextentity = new context(); $contextalias = $contextentity->get_table_alias('context'); $this->set_main_table('competency_framework', $frameworkalias); // Join context entity (unconditionally, as table also used by both framework/competency entities). $this->add_join("LEFT JOIN {context} {$contextalias} ON {$contextalias}.id = {$frameworkalias}.contextid"); $this->add_entity($contextentity); $this->add_entity($frameworkentity ->set_table_join_alias('context', $contextalias) ); // Join competency entity. $competencyentity = new competency(); $competencyalias = $competencyentity->get_table_alias('competency'); $this->add_entity($competencyentity ->set_table_join_alias('context', $contextalias) ->add_join("LEFT JOIN {competency} {$competencyalias} ON {$competencyalias}.competencyframeworkid = {$frameworkalias}.id") ); // Join user competency entity. $usercompetencyentity = new usercompetency(); $usercompetencyalias = $usercompetencyentity->get_table_alias('competency_usercomp'); $this->add_entity($usercompetencyentity ->add_joins($competencyentity->get_joins()) ->add_join("LEFT JOIN {competency_usercomp} {$usercompetencyalias} ON {$usercompetencyalias}.competencyid = {$competencyalias}.id") ); // Join user entity. $userentity = new user(); $useralias = $userentity->get_table_alias('user'); $this->add_entity($userentity ->add_joins($usercompetencyentity->get_joins()) ->add_join("LEFT JOIN {user} {$useralias} ON {$useralias}.id = {$usercompetencyalias}.userid") ); // Join cohort entity. $cohortentity = new cohort(); $cohortalias = $cohortentity->get_table_alias('cohort'); $cohortmemberalias = database::generate_alias(); $this->add_entity($cohortentity ->add_joins($userentity->get_joins()) ->add_joins([ "LEFT JOIN {cohort_members} {$cohortmemberalias} ON {$cohortmemberalias}.userid = {$useralias}.id", "LEFT JOIN {cohort} {$cohortalias} ON {$cohortalias}.id = {$cohortmemberalias}.cohortid", ]) ); // Add report elements from each of the entities we added to the report. $this->add_all_from_entities([ $contextentity->get_entity_name(), $frameworkentity->get_entity_name(), $competencyentity->get_entity_name(), $usercompetencyentity->get_entity_name(), $userentity->get_entity_name(), ]); $this->add_all_from_entity( $cohortentity->get_entity_name(), ['name', 'idnumber', 'description', 'customfield*'], ['cohortselect', 'name', 'idnumber', 'customfield*'], ['cohortselect', 'name', 'idnumber', 'customfield*'], ); } /** * Return the columns that will be added to the report upon creation * * @return string[] */ public function get_default_columns(): array { return [ 'framework:name', 'competency:name', 'user:fullname', 'usercompetency:proficient', ]; } /** * Return the column sorting that will be added to the report upon creation * * @return int[] */ public function get_default_column_sorting(): array { return [ 'framework:name' => SORT_ASC, 'competency:name' => SORT_ASC, 'user:fullname' => SORT_ASC, ]; } /** * Return the filters that will be added to the report upon creation * * @return string[] */ public function get_default_filters(): array { return [ 'competency:name', 'user:fullname', ]; } /** * Return the conditions that will be added to the report upon creation * * @return string[] */ public function get_default_conditions(): array { return [ 'framework:visible', ]; } /** * Return the condition values that will be added to the report upon creation * * @return array */ public function get_default_condition_values(): array { return [ 'framework:visible_operator' => boolean_select::CHECKED, ]; } } classes/reportbuilder/local/entities/framework.php000064400000020614152265256010016516 0ustar00. declare(strict_types=1); namespace core_competency\reportbuilder\local\entities; use core\{context, context_helper}; use core\lang_string; use core_reportbuilder\local\entities\base; use core_reportbuilder\local\filters\{boolean_select, date, select, text}; use core_reportbuilder\local\helpers\format; use core_reportbuilder\local\report\{column, filter}; use stdClass; /** * Competency framework entity * * @package core_competency * @copyright 2024 Paul Holden * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class framework extends base { /** * Database tables that this entity uses * * @return string[] */ protected function get_default_tables(): array { return [ 'competency_framework', 'context', ]; } /** * The default title for this entity * * @return lang_string */ protected function get_default_entity_title(): lang_string { return new lang_string('competencyframework', 'core_competency'); } /** * Initialise the entity * * @return base */ public function initialise(): base { $columns = $this->get_all_columns(); foreach ($columns as $column) { $this->add_column($column); } // All the filters defined by the entity can also be used as conditions. $filters = $this->get_all_filters(); foreach ($filters as $filter) { $this ->add_filter($filter) ->add_condition($filter); } return $this; } /** * Returns list of all available columns * * @return column[] */ protected function get_all_columns(): array { $frameworkalias = $this->get_table_alias('competency_framework'); $contextalias = $this->get_table_alias('context'); // Name. $columns[] = (new column( 'name', new lang_string('name'), $this->get_entity_name(), )) ->add_joins($this->get_joins()) ->add_field("{$frameworkalias}.shortname") ->set_is_sortable(true); // Description. $columns[] = (new column( 'description', new lang_string('description'), $this->get_entity_name(), )) ->add_joins($this->get_joins()) ->add_join($this->get_context_join()) ->set_type(column::TYPE_LONGTEXT) ->add_fields("{$frameworkalias}.description, {$frameworkalias}.descriptionformat") ->add_fields(context_helper::get_preload_record_columns_sql($contextalias)) ->set_is_sortable(true) ->add_callback(static function(?string $description, stdClass $framework): string { if ($description === null) { return ''; } context_helper::preload_from_record(clone $framework); $context = context::instance_by_id($framework->ctxid); return format_text($description, $framework->descriptionformat, ['context' => $context->id]); }); // ID number. $columns[] = (new column( 'idnumber', new lang_string('idnumber'), $this->get_entity_name(), )) ->add_joins($this->get_joins()) ->add_field("{$frameworkalias}.idnumber") ->set_is_sortable(true); // Scale. $columns[] = (new column( 'scale', new lang_string('scale'), $this->get_entity_name(), )) ->add_joins($this->get_joins()) ->add_field("{$frameworkalias}.scaleid") ->add_callback(static function(?string $scaleid): string { $scales = get_scales_menu(); return (string) ($scales[(int) $scaleid] ?? $scaleid); }); // Visible. $columns[] = (new column( 'visible', new lang_string('visible'), $this->get_entity_name(), )) ->add_joins($this->get_joins()) ->set_type(column::TYPE_BOOLEAN) ->add_field("{$frameworkalias}.visible") ->set_is_sortable(true) ->add_callback([format::class, 'boolean_as_text']); // Time created. $columns[] = (new column( 'timecreated', new lang_string('timecreated', 'core_reportbuilder'), $this->get_entity_name(), )) ->add_joins($this->get_joins()) ->set_type(column::TYPE_TIMESTAMP) ->add_field("{$frameworkalias}.timecreated") ->set_is_sortable(true) ->add_callback([format::class, 'userdate']); // Time modified. $columns[] = (new column( 'timemodified', new lang_string('timemodified', 'core_reportbuilder'), $this->get_entity_name(), )) ->add_joins($this->get_joins()) ->set_type(column::TYPE_TIMESTAMP) ->add_field("{$frameworkalias}.timemodified") ->set_is_sortable(true) ->add_callback([format::class, 'userdate']); return $columns; } /** * Return list of all available filters * * @return filter[] */ protected function get_all_filters(): array { $frameworkalias = $this->get_table_alias('competency_framework'); // Name. $filters[] = (new filter( text::class, 'name', new lang_string('name'), $this->get_entity_name(), "{$frameworkalias}.shortname", )) ->add_joins($this->get_joins()); // ID number. $filters[] = (new filter( text::class, 'idnumber', new lang_string('idnumber'), $this->get_entity_name(), "{$frameworkalias}.idnumber", )) ->add_joins($this->get_joins()); // Scale. $filters[] = (new filter( select::class, 'scale', new lang_string('scale'), $this->get_entity_name(), "{$frameworkalias}.scaleid", )) ->add_joins($this->get_joins()) ->set_options_callback('get_scales_menu'); // Visible. $filters[] = (new filter( boolean_select::class, 'visible', new lang_string('visible'), $this->get_entity_name(), "{$frameworkalias}.visible", )) ->add_joins($this->get_joins()); // Time created. $filters[] = (new filter( date::class, 'timecreated', new lang_string('timecreated', 'core_reportbuilder'), $this->get_entity_name(), "{$frameworkalias}.timecreated", )) ->add_joins($this->get_joins()); // Time modified. $filters[] = (new filter( date::class, 'timemodified', new lang_string('timemodified', 'core_reportbuilder'), $this->get_entity_name(), "{$frameworkalias}.timemodified", )) ->add_joins($this->get_joins()); return $filters; } /** * Return context join * * @return string */ private function get_context_join(): string { // If the context table is already joined, we don't need to do that again. if ($this->has_table_join_alias('context')) { return ''; } $frameworkalias = $this->get_table_alias('competency_framework'); $contextalias = $this->get_table_alias('context'); return "LEFT JOIN {context} {$contextalias} ON {$contextalias}.id = {$frameworkalias}.contextid"; } } classes/reportbuilder/local/entities/competency.php000064400000016142152265256010016670 0ustar00. declare(strict_types=1); namespace core_competency\reportbuilder\local\entities; use core\{context, context_helper}; use core\lang_string; use core_reportbuilder\local\entities\base; use core_reportbuilder\local\filters\{date, text}; use core_reportbuilder\local\helpers\{database, format}; use core_reportbuilder\local\report\{column, filter}; use stdClass; /** * Competency entity * * @package core_competency * @copyright 2024 Paul Holden * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class competency extends base { /** * Database tables that this entity uses * * @return string[] */ protected function get_default_tables(): array { return [ 'competency', 'context', ]; } /** * The default title for this entity * * @return lang_string */ protected function get_default_entity_title(): lang_string { return new lang_string('competency', 'core_competency'); } /** * Initialise the entity * * @return base */ public function initialise(): base { $columns = $this->get_all_columns(); foreach ($columns as $column) { $this->add_column($column); } // All the filters defined by the entity can also be used as conditions. $filters = $this->get_all_filters(); foreach ($filters as $filter) { $this ->add_filter($filter) ->add_condition($filter); } return $this; } /** * Returns list of all available columns * * @return column[] */ protected function get_all_columns(): array { $contextalias = $this->get_table_alias('context'); $competencyalias = $this->get_table_alias('competency'); // Name. $columns[] = (new column( 'name', new lang_string('name'), $this->get_entity_name(), )) ->add_joins($this->get_joins()) ->add_field("{$competencyalias}.shortname") ->set_is_sortable(true); // Description. $columns[] = (new column( 'description', new lang_string('description'), $this->get_entity_name(), )) ->add_joins($this->get_joins()) ->add_joins($this->get_context_joins()) ->set_type(column::TYPE_LONGTEXT) ->add_fields("{$competencyalias}.description, {$competencyalias}.descriptionformat") ->add_fields(context_helper::get_preload_record_columns_sql($contextalias)) ->set_is_sortable(true) ->add_callback(static function(?string $description, stdClass $competency): string { if ($description === null) { return ''; } context_helper::preload_from_record(clone $competency); $context = context::instance_by_id($competency->ctxid); return format_text($description, $competency->descriptionformat, ['context' => $context->id]); }); // ID number. $columns[] = (new column( 'idnumber', new lang_string('idnumber'), $this->get_entity_name(), )) ->add_joins($this->get_joins()) ->add_field("{$competencyalias}.idnumber") ->set_is_sortable(true); // Time created. $columns[] = (new column( 'timecreated', new lang_string('timecreated', 'core_reportbuilder'), $this->get_entity_name(), )) ->add_joins($this->get_joins()) ->set_type(column::TYPE_TIMESTAMP) ->add_field("{$competencyalias}.timecreated") ->set_is_sortable(true) ->add_callback([format::class, 'userdate']); // Time modified. $columns[] = (new column( 'timemodified', new lang_string('timemodified', 'core_reportbuilder'), $this->get_entity_name(), )) ->add_joins($this->get_joins()) ->set_type(column::TYPE_TIMESTAMP) ->add_field("{$competencyalias}.timemodified") ->set_is_sortable(true) ->add_callback([format::class, 'userdate']); return $columns; } /** * Return list of all available filters * * @return filter[] */ protected function get_all_filters(): array { $competencyalias = $this->get_table_alias('competency'); // Name. $filters[] = (new filter( text::class, 'name', new lang_string('name'), $this->get_entity_name(), "{$competencyalias}.shortname", )) ->add_joins($this->get_joins()); // ID number. $filters[] = (new filter( text::class, 'idnumber', new lang_string('idnumber'), $this->get_entity_name(), "{$competencyalias}.idnumber", )) ->add_joins($this->get_joins()); // Time created. $filters[] = (new filter( date::class, 'timecreated', new lang_string('timecreated', 'core_reportbuilder'), $this->get_entity_name(), "{$competencyalias}.timecreated", )) ->add_joins($this->get_joins()); // Time modified. $filters[] = (new filter( date::class, 'timemodified', new lang_string('timemodified', 'core_reportbuilder'), $this->get_entity_name(), "{$competencyalias}.timemodified", )) ->add_joins($this->get_joins()); return $filters; } /** * Return context joins * * @return string[] */ private function get_context_joins(): array { // If the context table is already joined, we don't need to do that again. if ($this->has_table_join_alias('context')) { return []; } $frameworkalias = database::generate_alias(); $competencyalias = $this->get_table_alias('competency'); $contextalias = $this->get_table_alias('context'); return [ "LEFT JOIN {competency_framework} {$frameworkalias} ON {$frameworkalias}.id = {$competencyalias}.competencyframeworkid", "LEFT JOIN {context} {$contextalias} ON {$contextalias}.id = {$frameworkalias}.contextid", ]; } } classes/reportbuilder/local/entities/usercompetency.php000064400000012177152265256010017573 0ustar00. declare(strict_types=1); namespace core_competency\reportbuilder\local\entities; use core\lang_string; use core_competency\{competency, user_competency}; use core_reportbuilder\local\entities\base; use core_reportbuilder\local\filters\{boolean_select, select}; use core_reportbuilder\local\helpers\format; use core_reportbuilder\local\report\{column, filter}; use stdClass; /** * User competency entity * * @package core_competency * @copyright 2024 Paul Holden * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class usercompetency extends base { /** * Database tables that this entity uses * * @return string[] */ protected function get_default_tables(): array { return [ 'competency_usercomp', ]; } /** * The default title for this entity * * @return lang_string */ protected function get_default_entity_title(): lang_string { return new lang_string('usercompetency', 'core_competency'); } /** * Initialise the entity * * @return base */ public function initialise(): base { $columns = $this->get_all_columns(); foreach ($columns as $column) { $this->add_column($column); } // All the filters defined by the entity can also be used as conditions. $filters = $this->get_all_filters(); foreach ($filters as $filter) { $this ->add_filter($filter) ->add_condition($filter); } return $this; } /** * Returns list of all available columns * * @return column[] */ protected function get_all_columns(): array { $usercompetencyalias = $this->get_table_alias('competency_usercomp'); // Status. $columns[] = (new column( 'status', new lang_string('status'), $this->get_entity_name(), )) ->add_joins($this->get_joins()) ->set_type(column::TYPE_TEXT) ->add_field("{$usercompetencyalias}.status") ->set_is_sortable(true) ->add_callback(static function(?string $status): string { if ($status === null) { return ''; } return (string) user_competency::get_status_name((int) $status); }); // Proficient. $columns[] = (new column( 'proficient', new lang_string('proficient', 'core_competency'), $this->get_entity_name(), )) ->add_joins($this->get_joins()) ->set_type(column::TYPE_BOOLEAN) ->add_field("{$usercompetencyalias}.proficiency") ->set_is_sortable(true) ->add_callback([format::class, 'boolean_as_text']); // Rating. $columns[] = (new column( 'rating', new lang_string('rating', 'core_competency'), $this->get_entity_name(), )) ->add_joins($this->get_joins()) ->add_fields("{$usercompetencyalias}.grade, {$usercompetencyalias}.competencyid") ->add_callback(static function(?string $grade, stdClass $row): string { if ($grade === null) { return ''; } $competency = new competency($row->competencyid); $scale = $competency->get_scale()->scale_items; return (string) ($scale[(int) $grade - 1] ?? $grade); }); return $columns; } /** * Return list of all available filters * * @return filter[] */ protected function get_all_filters(): array { $usercompetencyalias = $this->get_table_alias('competency_usercomp'); // Status. $filters[] = (new filter( select::class, 'status', new lang_string('status'), $this->get_entity_name(), "{$usercompetencyalias}.status", )) ->add_joins($this->get_joins()) ->set_options_callback([user_competency::class, 'get_status_list']); // Proficient. $filters[] = (new filter( boolean_select::class, 'proficient', new lang_string('proficient', 'core_competency'), $this->get_entity_name(), "{$usercompetencyalias}.proficiency", )) ->add_joins($this->get_joins()); return $filters; } } classes/api.php000064400000632714152265256010007504 0ustar00. /** * Class for loading/storing competency frameworks from the DB. * * @package core_competency * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_competency; defined('MOODLE_INTERNAL') || die(); use core\invalid_persistent_exception; use stdClass; use cm_info; use context; use context_helper; use context_system; use context_course; use context_module; use context_user; use coding_exception; use require_login_exception; use moodle_exception; use moodle_url; use required_capability_exception; /** * Class for doing things with competency frameworks. * * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class api { /** @var boolean Allow api functions even if competencies are not enabled for the site. */ private static $skipenabled = false; /** * Returns whether competencies are enabled. * * This method should never do more than checking the config setting, the reason * being that some other code could be checking the config value directly * to avoid having to load this entire file into memory. * * @return boolean True when enabled. */ public static function is_enabled() { return self::$skipenabled || get_config('core_competency', 'enabled'); } /** * When competencies used to be enabled, we can show the text but do not include links. * * @return boolean True means show links. */ public static function show_links() { return isloggedin() && !isguestuser() && get_config('core_competency', 'enabled'); } /** * Allow calls to competency api functions even if competencies are not currently enabled. */ public static function skip_enabled() { self::$skipenabled = true; } /** * Restore the checking that competencies are enabled with any api function. */ public static function check_enabled() { self::$skipenabled = false; } /** * Throws an exception if competencies are not enabled. * * @return void * @throws moodle_exception */ public static function require_enabled() { if (!static::is_enabled()) { throw new moodle_exception('competenciesarenotenabled', 'core_competency'); } } /** * Checks whether a scale is used anywhere in the plugin. * * This public API has two exceptions: * - It MUST NOT perform any capability checks. * - It MUST ignore whether competencies are enabled or not ({@link self::is_enabled()}). * * @param int $scaleid The scale ID. * @return bool */ public static function is_scale_used_anywhere($scaleid) { global $DB; $sql = "SELECT s.id FROM {scale} s LEFT JOIN {" . competency_framework::TABLE ."} f ON f.scaleid = :scaleid1 LEFT JOIN {" . competency::TABLE ."} c ON c.scaleid = :scaleid2 WHERE f.id IS NOT NULL OR c.id IS NOT NULL"; return $DB->record_exists_sql($sql, ['scaleid1' => $scaleid, 'scaleid2' => $scaleid]); } /** * Validate if current user has access to the course_module if hidden. * * @param mixed $cmmixed The cm_info class, course module record or its ID. * @param bool $throwexception Throw an exception or not. * @return bool */ protected static function validate_course_module($cmmixed, $throwexception = true) { $cm = $cmmixed; if (!is_object($cm)) { $cmrecord = get_coursemodule_from_id(null, $cmmixed); $modinfo = get_fast_modinfo($cmrecord->course); $cm = $modinfo->get_cm($cmmixed); } else if (!$cm instanceof cm_info) { // Assume we got a course module record. $modinfo = get_fast_modinfo($cm->course); $cm = $modinfo->get_cm($cm->id); } if (!$cm->uservisible) { if ($throwexception) { throw new require_login_exception('Course module is hidden'); } else { return false; } } return true; } /** * Validate if current user has access to the course if hidden. * * @param mixed $courseorid The course or it ID. * @param bool $throwexception Throw an exception or not. * @return bool */ protected static function validate_course($courseorid, $throwexception = true) { $course = $courseorid; if (!is_object($course)) { $course = get_course($course); } $coursecontext = context_course::instance($course->id); if (!$course->visible and !has_capability('moodle/course:viewhiddencourses', $coursecontext)) { if ($throwexception) { throw new require_login_exception('Course is hidden'); } else { return false; } } return true; } /** * Create a competency from a record containing all the data for the class. * * Requires moodle/competency:competencymanage capability at the system context. * * @param stdClass $record Record containing all the data for an instance of the class. * @return competency */ public static function create_competency(stdClass $record) { static::require_enabled(); $competency = new competency(0, $record); // First we do a permissions check. require_capability('moodle/competency:competencymanage', $competency->get_context()); // Reset the sortorder, use reorder instead. $competency->set('sortorder', 0); $competency->create(); \core\event\competency_created::create_from_competency($competency)->trigger(); // Reset the rule of the parent. $parent = $competency->get_parent(); if ($parent) { $parent->reset_rule(); $parent->update(); } return $competency; } /** * Delete a competency by id. * * Requires moodle/competency:competencymanage capability at the system context. * * @param int $id The record to delete. This will delete alot of related data - you better be sure. * @return boolean */ public static function delete_competency($id) { global $DB; static::require_enabled(); $competency = new competency($id); // First we do a permissions check. require_capability('moodle/competency:competencymanage', $competency->get_context()); $events = array(); $competencyids = array(intval($competency->get('id'))); $contextid = $competency->get_context()->id; $competencyids = array_merge(competency::get_descendants_ids($competency), $competencyids); if (!competency::can_all_be_deleted($competencyids)) { return false; } $transaction = $DB->start_delegated_transaction(); try { // Reset the rule of the parent. $parent = $competency->get_parent(); if ($parent) { $parent->reset_rule(); $parent->update(); } // Delete the competency separately so the after_delete event can be triggered. $competency->delete(); // Delete the competencies. competency::delete_multiple($competencyids); // Delete the competencies relation. related_competency::delete_multiple_relations($competencyids); // Delete competency evidences. user_evidence_competency::delete_by_competencyids($competencyids); // Register the competencies deleted events. $events = \core\event\competency_deleted::create_multiple_from_competencyids($competencyids, $contextid); } catch (\Exception $e) { $transaction->rollback($e); } $transaction->allow_commit(); // Trigger events. foreach ($events as $event) { $event->trigger(); } return true; } /** * Reorder this competency. * * Requires moodle/competency:competencymanage capability at the system context. * * @param int $id The id of the competency to move. * @return boolean */ public static function move_down_competency($id) { static::require_enabled(); $current = new competency($id); // First we do a permissions check. require_capability('moodle/competency:competencymanage', $current->get_context()); $max = self::count_competencies(array('parentid' => $current->get('parentid'), 'competencyframeworkid' => $current->get('competencyframeworkid'))); if ($max > 0) { $max--; } $sortorder = $current->get('sortorder'); if ($sortorder >= $max) { return false; } $sortorder = $sortorder + 1; $current->set('sortorder', $sortorder); $filters = array('parentid' => $current->get('parentid'), 'competencyframeworkid' => $current->get('competencyframeworkid'), 'sortorder' => $sortorder); $children = self::list_competencies($filters, 'id'); foreach ($children as $needtoswap) { $needtoswap->set('sortorder', $sortorder - 1); $needtoswap->update(); } // OK - all set. $result = $current->update(); return $result; } /** * Reorder this competency. * * Requires moodle/competency:competencymanage capability at the system context. * * @param int $id The id of the competency to move. * @return boolean */ public static function move_up_competency($id) { static::require_enabled(); $current = new competency($id); // First we do a permissions check. require_capability('moodle/competency:competencymanage', $current->get_context()); $sortorder = $current->get('sortorder'); if ($sortorder == 0) { return false; } $sortorder = $sortorder - 1; $current->set('sortorder', $sortorder); $filters = array('parentid' => $current->get('parentid'), 'competencyframeworkid' => $current->get('competencyframeworkid'), 'sortorder' => $sortorder); $children = self::list_competencies($filters, 'id'); foreach ($children as $needtoswap) { $needtoswap->set('sortorder', $sortorder + 1); $needtoswap->update(); } // OK - all set. $result = $current->update(); return $result; } /** * Move this competency so it sits in a new parent. * * Requires moodle/competency:competencymanage capability at the system context. * * @param int $id The id of the competency to move. * @param int $newparentid The new parent id for the competency. * @return boolean */ public static function set_parent_competency($id, $newparentid) { global $DB; static::require_enabled(); $current = new competency($id); // First we do a permissions check. require_capability('moodle/competency:competencymanage', $current->get_context()); if ($id == $newparentid) { throw new coding_exception('Can not set a competency as a parent of itself.'); } if ($newparentid == $current->get('parentid')) { throw new coding_exception('Can not move a competency to the same location.'); } // Some great variable assignment right here. $currentparent = $current->get_parent(); $parent = !empty($newparentid) ? new competency($newparentid) : null; $parentpath = !empty($parent) ? $parent->get('path') : '/0/'; // We're going to change quite a few things. $transaction = $DB->start_delegated_transaction(); // If we are moving a node to a child of itself: // - promote all the child nodes by one level. // - remove the rule on self. // - re-read the parent. $newparents = explode('/', $parentpath); if (in_array($current->get('id'), $newparents)) { $children = competency::get_records(array('parentid' => $current->get('id')), 'id'); foreach ($children as $child) { $child->set('parentid', $current->get('parentid')); $child->update(); } // Reset the rule on self as our children have changed. $current->reset_rule(); // The destination parent is one of our descendants, we need to re-fetch its values (path, parentid). $parent->read(); } // Reset the rules of initial parent and destination. if (!empty($currentparent)) { $currentparent->reset_rule(); $currentparent->update(); } if (!empty($parent)) { $parent->reset_rule(); $parent->update(); } // Do the actual move. $current->set('parentid', $newparentid); $result = $current->update(); // All right, let's commit this. $transaction->allow_commit(); return $result; } /** * Update the details for a competency. * * Requires moodle/competency:competencymanage capability at the system context. * * @param stdClass $record The new details for the competency. * Note - must contain an id that points to the competency to update. * * @return boolean */ public static function update_competency($record) { static::require_enabled(); $competency = new competency($record->id); // First we do a permissions check. require_capability('moodle/competency:competencymanage', $competency->get_context()); // Some things should not be changed in an update - they should use a more specific method. $record->sortorder = $competency->get('sortorder'); $record->parentid = $competency->get('parentid'); $record->competencyframeworkid = $competency->get('competencyframeworkid'); $competency->from_record($record); require_capability('moodle/competency:competencymanage', $competency->get_context()); // OK - all set. $result = $competency->update(); // Trigger the update event. \core\event\competency_updated::create_from_competency($competency)->trigger(); return $result; } /** * Read the details for a single competency and return a record. * * Requires moodle/competency:competencyview capability at the system context. * * @param int $id The id of the competency to read. * @param bool $includerelated Include related tags or not. * @return competency */ public static function read_competency($id, $includerelated = false) { static::require_enabled(); $competency = new competency($id); // First we do a permissions check. $context = $competency->get_context(); if (!has_any_capability(array('moodle/competency:competencyview', 'moodle/competency:competencymanage'), $context)) { throw new required_capability_exception($context, 'moodle/competency:competencyview', 'nopermissions', ''); } // OK - all set. if ($includerelated) { $relatedcompetency = new related_competency(); if ($related = $relatedcompetency->list_relations($id)) { $competency->relatedcompetencies = $related; } } return $competency; } /** * Perform a search based on a text and return all results and their parents. * * Requires moodle/competency:competencyview capability at the framework context. * * @param string $textsearch A string to search for. * @param int $competencyframeworkid The id of the framework to limit the search. * @return array of competencies */ public static function search_competencies($textsearch, $competencyframeworkid) { static::require_enabled(); $framework = new competency_framework($competencyframeworkid); // First we do a permissions check. $context = $framework->get_context(); if (!has_any_capability(array('moodle/competency:competencyview', 'moodle/competency:competencymanage'), $context)) { throw new required_capability_exception($context, 'moodle/competency:competencyview', 'nopermissions', ''); } // OK - all set. $competencies = competency::search($textsearch, $competencyframeworkid); return $competencies; } /** * Perform a search based on the provided filters and return a paginated list of records. * * Requires moodle/competency:competencyview capability at some context. * * @param array $filters A list of filters to apply to the list. * @param string $sort The column to sort on * @param string $order ('ASC' or 'DESC') * @param int $skip Number of records to skip (pagination) * @param int $limit Max of records to return (pagination) * @return array of competencies */ public static function list_competencies($filters, $sort = '', $order = 'ASC', $skip = 0, $limit = 0) { static::require_enabled(); if (!isset($filters['competencyframeworkid'])) { $context = context_system::instance(); } else { $framework = new competency_framework($filters['competencyframeworkid']); $context = $framework->get_context(); } // First we do a permissions check. if (!has_any_capability(array('moodle/competency:competencyview', 'moodle/competency:competencymanage'), $context)) { throw new required_capability_exception($context, 'moodle/competency:competencyview', 'nopermissions', ''); } // OK - all set. return competency::get_records($filters, $sort, $order, $skip, $limit); } /** * Perform a search based on the provided filters and return a paginated list of records. * * Requires moodle/competency:competencyview capability at some context. * * @param array $filters A list of filters to apply to the list. * @return int */ public static function count_competencies($filters) { static::require_enabled(); if (!isset($filters['competencyframeworkid'])) { $context = context_system::instance(); } else { $framework = new competency_framework($filters['competencyframeworkid']); $context = $framework->get_context(); } // First we do a permissions check. if (!has_any_capability(array('moodle/competency:competencyview', 'moodle/competency:competencymanage'), $context)) { throw new required_capability_exception($context, 'moodle/competency:competencyview', 'nopermissions', ''); } // OK - all set. return competency::count_records($filters); } /** * Create a competency framework from a record containing all the data for the class. * * Requires moodle/competency:competencymanage capability at the system context. * * @param stdClass $record Record containing all the data for an instance of the class. * @return competency_framework */ public static function create_framework(stdClass $record) { static::require_enabled(); $framework = new competency_framework(0, $record); require_capability('moodle/competency:competencymanage', $framework->get_context()); // Account for different formats of taxonomies. if (isset($record->taxonomies)) { $framework->set('taxonomies', $record->taxonomies); } $framework = $framework->create(); // Trigger a competency framework created event. \core\event\competency_framework_created::create_from_framework($framework)->trigger(); return $framework; } /** * Duplicate a competency framework by id. * * Requires moodle/competency:competencymanage capability at the system context. * * @param int $id The record to duplicate. All competencies associated and related will be duplicated. * @return competency_framework the framework duplicated */ public static function duplicate_framework($id) { global $DB; static::require_enabled(); $framework = new competency_framework($id); require_capability('moodle/competency:competencymanage', $framework->get_context()); // Starting transaction. $transaction = $DB->start_delegated_transaction(); try { // Get a uniq idnumber based on the origin framework. $idnumber = competency_framework::get_unused_idnumber($framework->get('idnumber')); $framework->set('idnumber', $idnumber); // Adding the suffix copy to the shortname. $framework->set('shortname', get_string('duplicateditemname', 'core_competency', $framework->get('shortname'))); $framework->set('id', 0); $framework = $framework->create(); // Array that matches the old competencies ids with the new one to use when copying related competencies. $frameworkcompetency = competency::get_framework_tree($id); $matchids = self::duplicate_competency_tree($framework->get('id'), $frameworkcompetency, 0, 0); // Copy the related competencies. $relcomps = related_competency::get_multiple_relations(array_keys($matchids)); foreach ($relcomps as $relcomp) { $compid = $relcomp->get('competencyid'); $relcompid = $relcomp->get('relatedcompetencyid'); if (isset($matchids[$compid]) && isset($matchids[$relcompid])) { $newcompid = $matchids[$compid]->get('id'); $newrelcompid = $matchids[$relcompid]->get('id'); if ($newcompid < $newrelcompid) { $relcomp->set('competencyid', $newcompid); $relcomp->set('relatedcompetencyid', $newrelcompid); } else { $relcomp->set('competencyid', $newrelcompid); $relcomp->set('relatedcompetencyid', $newcompid); } $relcomp->set('id', 0); $relcomp->create(); } else { // Debugging message when there is no match found. debugging('related competency id not found'); } } // Setting rules on duplicated competencies. self::migrate_competency_tree_rules($frameworkcompetency, $matchids); $transaction->allow_commit(); } catch (\Exception $e) { $transaction->rollback($e); } // Trigger a competency framework created event. \core\event\competency_framework_created::create_from_framework($framework)->trigger(); return $framework; } /** * Delete a competency framework by id. * * Requires moodle/competency:competencymanage capability at the system context. * * @param int $id The record to delete. This will delete alot of related data - you better be sure. * @return boolean */ public static function delete_framework($id) { global $DB; static::require_enabled(); $framework = new competency_framework($id); require_capability('moodle/competency:competencymanage', $framework->get_context()); $events = array(); $competenciesid = competency::get_ids_by_frameworkid($id); $contextid = $framework->get('contextid'); if (!competency::can_all_be_deleted($competenciesid)) { return false; } $transaction = $DB->start_delegated_transaction(); try { if (!empty($competenciesid)) { // Delete competencies. competency::delete_by_frameworkid($id); // Delete the related competencies. related_competency::delete_multiple_relations($competenciesid); // Delete the evidences for competencies. user_evidence_competency::delete_by_competencyids($competenciesid); } // Create a competency framework deleted event. $event = \core\event\competency_framework_deleted::create_from_framework($framework); $result = $framework->delete(); // Register the deleted events competencies. $events = \core\event\competency_deleted::create_multiple_from_competencyids($competenciesid, $contextid); } catch (\Exception $e) { $transaction->rollback($e); } // Commit the transaction. $transaction->allow_commit(); // If all operations are successful then trigger the delete event. $event->trigger(); // Trigger deleted event competencies. foreach ($events as $event) { $event->trigger(); } return $result; } /** * Update the details for a competency framework. * * Requires moodle/competency:competencymanage capability at the system context. * * @param stdClass $record The new details for the framework. Note - must contain an id that points to the framework to update. * @return boolean */ public static function update_framework($record) { static::require_enabled(); $framework = new competency_framework($record->id); // Check the permissions before update. require_capability('moodle/competency:competencymanage', $framework->get_context()); // Account for different formats of taxonomies. $framework->from_record($record); if (isset($record->taxonomies)) { $framework->set('taxonomies', $record->taxonomies); } // Trigger a competency framework updated event. \core\event\competency_framework_updated::create_from_framework($framework)->trigger(); return $framework->update(); } /** * Read the details for a single competency framework and return a record. * * Requires moodle/competency:competencyview capability at the system context. * * @param int $id The id of the framework to read. * @return competency_framework */ public static function read_framework($id) { static::require_enabled(); $framework = new competency_framework($id); if (!has_any_capability(array('moodle/competency:competencyview', 'moodle/competency:competencymanage'), $framework->get_context())) { throw new required_capability_exception($framework->get_context(), 'moodle/competency:competencyview', 'nopermissions', ''); } return $framework; } /** * Log the competency framework viewed event. * * @param competency_framework|int $frameworkorid The competency_framework object or competency framework id * @return bool */ public static function competency_framework_viewed($frameworkorid) { static::require_enabled(); $framework = $frameworkorid; if (!is_object($framework)) { $framework = new competency_framework($framework); } if (!has_any_capability(array('moodle/competency:competencyview', 'moodle/competency:competencymanage'), $framework->get_context())) { throw new required_capability_exception($framework->get_context(), 'moodle/competency:competencyview', 'nopermissions', ''); } \core\event\competency_framework_viewed::create_from_framework($framework)->trigger(); return true; } /** * Log the competency viewed event. * * @param competency|int $competencyorid The competency object or competency id * @return bool */ public static function competency_viewed($competencyorid) { static::require_enabled(); $competency = $competencyorid; if (!is_object($competency)) { $competency = new competency($competency); } if (!has_any_capability(array('moodle/competency:competencyview', 'moodle/competency:competencymanage'), $competency->get_context())) { throw new required_capability_exception($competency->get_context(), 'moodle/competency:competencyview', 'nopermissions', ''); } \core\event\competency_viewed::create_from_competency($competency)->trigger(); return true; } /** * Perform a search based on the provided filters and return a paginated list of records. * * Requires moodle/competency:competencyview capability at the system context. * * @param string $sort The column to sort on * @param string $order ('ASC' or 'DESC') * @param int $skip Number of records to skip (pagination) * @param int $limit Max of records to return (pagination) * @param context $context The parent context of the frameworks. * @param string $includes Defines what other contexts to fetch frameworks from. * Accepted values are: * - children: All descendants * - parents: All parents, grand parents, etc... * - self: Context passed only. * @param bool $onlyvisible If true return only visible frameworks * @param string $query A string to use to filter down the frameworks. * @return array of competency_framework */ public static function list_frameworks($sort, $order, $skip, $limit, $context, $includes = 'children', $onlyvisible = false, $query = '') { global $DB; static::require_enabled(); // Get all the relevant contexts. $contexts = self::get_related_contexts($context, $includes, array('moodle/competency:competencyview', 'moodle/competency:competencymanage')); if (empty($contexts)) { throw new required_capability_exception($context, 'moodle/competency:competencyview', 'nopermissions', ''); } // OK - all set. list($insql, $inparams) = $DB->get_in_or_equal(array_keys($contexts), SQL_PARAMS_NAMED); $select = "contextid $insql"; if ($onlyvisible) { $select .= " AND visible = :visible"; $inparams['visible'] = 1; } if (!empty($query) || is_numeric($query)) { $sqlnamelike = $DB->sql_like('shortname', ':namelike', false); $sqlidnlike = $DB->sql_like('idnumber', ':idnlike', false); $select .= " AND ($sqlnamelike OR $sqlidnlike) "; $inparams['namelike'] = '%' . $DB->sql_like_escape($query) . '%'; $inparams['idnlike'] = '%' . $DB->sql_like_escape($query) . '%'; } return competency_framework::get_records_select($select, $inparams, $sort . ' ' . $order, '*', $skip, $limit); } /** * Perform a search based on the provided filters and return a paginated list of records. * * Requires moodle/competency:competencyview capability at the system context. * * @param context $context The parent context of the frameworks. * @param string $includes Defines what other contexts to fetch frameworks from. * Accepted values are: * - children: All descendants * - parents: All parents, grand parents, etc... * - self: Context passed only. * @return int */ public static function count_frameworks($context, $includes) { global $DB; static::require_enabled(); // Get all the relevant contexts. $contexts = self::get_related_contexts($context, $includes, array('moodle/competency:competencyview', 'moodle/competency:competencymanage')); if (empty($contexts)) { throw new required_capability_exception($context, 'moodle/competency:competencyview', 'nopermissions', ''); } // OK - all set. list($insql, $inparams) = $DB->get_in_or_equal(array_keys($contexts), SQL_PARAMS_NAMED); return competency_framework::count_records_select("contextid $insql", $inparams); } /** * Fetches all the relevant contexts. * * Note: This currently only supports system, category and user contexts. However user contexts * behave a bit differently and will fallback on the system context. This is what makes the most * sense because a user context does not have descendants, and only has system as a parent. * * @param context $context The context to start from. * @param string $includes Defines what other contexts to find. * Accepted values are: * - children: All descendants * - parents: All parents, grand parents, etc... * - self: Context passed only. * @param array $hasanycapability Array of capabilities passed to {@link has_any_capability()} in each context. * @return context[] An array of contexts where keys are context IDs. */ public static function get_related_contexts($context, $includes, ?array $hasanycapability = null) { global $DB; static::require_enabled(); if (!in_array($includes, array('children', 'parents', 'self'))) { throw new coding_exception('Invalid parameter value for \'includes\'.'); } // If context user swap it for the context_system. if ($context->contextlevel == CONTEXT_USER) { $context = context_system::instance(); } $contexts = array($context->id => $context); if ($includes == 'children') { $params = array('coursecatlevel' => CONTEXT_COURSECAT, 'path' => $context->path . '/%'); $pathlike = $DB->sql_like('path', ':path'); $sql = "contextlevel = :coursecatlevel AND $pathlike"; $rs = $DB->get_recordset_select('context', $sql, $params); foreach ($rs as $record) { $ctxid = $record->id; context_helper::preload_from_record($record); $contexts[$ctxid] = context::instance_by_id($ctxid); } $rs->close(); } else if ($includes == 'parents') { $children = $context->get_parent_contexts(); foreach ($children as $ctx) { $contexts[$ctx->id] = $ctx; } } // Filter according to the capabilities required. if (!empty($hasanycapability)) { foreach ($contexts as $key => $ctx) { if (!has_any_capability($hasanycapability, $ctx)) { unset($contexts[$key]); } } } return $contexts; } /** * Count all the courses using a competency. * * @param int $competencyid The id of the competency to check. * @return int */ public static function count_courses_using_competency($competencyid) { static::require_enabled(); // OK - all set. $courses = course_competency::list_courses_min($competencyid); $count = 0; // Now check permissions on each course. foreach ($courses as $course) { if (!self::validate_course($course, false)) { continue; } $context = context_course::instance($course->id); $capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage'); if (!has_any_capability($capabilities, $context)) { continue; } $count++; } return $count; } /** * List all the courses modules using a competency in a course. * * @param int $competencyid The id of the competency to check. * @param int $courseid The id of the course to check. * @return array[int] Array of course modules ids. */ public static function list_course_modules_using_competency($competencyid, $courseid) { static::require_enabled(); $result = array(); self::validate_course($courseid); $coursecontext = context_course::instance($courseid); // We will not check each module - course permissions should be enough. $capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage'); if (!has_any_capability($capabilities, $coursecontext)) { throw new required_capability_exception($coursecontext, 'moodle/competency:coursecompetencyview', 'nopermissions', ''); } $cmlist = course_module_competency::list_course_modules($competencyid, $courseid); foreach ($cmlist as $cmid) { if (self::validate_course_module($cmid, false)) { array_push($result, $cmid); } } return $result; } /** * List all the competencies linked to a course module. * * @param mixed $cmorid The course module, or its ID. * @return array[competency] Array of competency records. */ public static function list_course_module_competencies_in_course_module($cmorid) { static::require_enabled(); $cm = $cmorid; if (!is_object($cmorid)) { $cm = get_coursemodule_from_id('', $cmorid, 0, true, MUST_EXIST); } // Check the user has access to the course module. self::validate_course_module($cm); $context = context_module::instance($cm->id); $capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage'); if (!has_any_capability($capabilities, $context)) { throw new required_capability_exception($context, 'moodle/competency:coursecompetencyview', 'nopermissions', ''); } $result = array(); $cmclist = course_module_competency::list_course_module_competencies($cm->id); foreach ($cmclist as $id => $cmc) { array_push($result, $cmc); } return $result; } /** * List all the courses using a competency. * * @param int $competencyid The id of the competency to check. * @return array[stdClass] Array of stdClass containing id and shortname. */ public static function list_courses_using_competency($competencyid) { static::require_enabled(); // OK - all set. $courses = course_competency::list_courses($competencyid); $result = array(); // Now check permissions on each course. foreach ($courses as $id => $course) { $context = context_course::instance($course->id); $capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage'); if (!has_any_capability($capabilities, $context)) { unset($courses[$id]); continue; } if (!self::validate_course($course, false)) { unset($courses[$id]); continue; } array_push($result, $course); } return $result; } /** * Count the proficient competencies in a course for one user. * * @param int $courseid The id of the course to check. * @param int $userid The id of the user to check. * @return int */ public static function count_proficient_competencies_in_course_for_user($courseid, $userid) { static::require_enabled(); // Check the user has access to the course. self::validate_course($courseid); // First we do a permissions check. $context = context_course::instance($courseid); $capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage'); if (!has_any_capability($capabilities, $context)) { throw new required_capability_exception($context, 'moodle/competency:coursecompetencyview', 'nopermissions', ''); } // OK - all set. return user_competency_course::count_proficient_competencies($courseid, $userid); } /** * Count all the competencies in a course. * * @param int $courseid The id of the course to check. * @return int */ public static function count_competencies_in_course($courseid) { static::require_enabled(); // Check the user has access to the course. self::validate_course($courseid); // First we do a permissions check. $context = context_course::instance($courseid); $capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage'); if (!has_any_capability($capabilities, $context)) { throw new required_capability_exception($context, 'moodle/competency:coursecompetencyview', 'nopermissions', ''); } // OK - all set. return course_competency::count_competencies($courseid); } /** * List the competencies associated to a course. * * @param mixed $courseorid The course, or its ID. * @return array( array( * 'competency' => \core_competency\competency, * 'coursecompetency' => \core_competency\course_competency * )) */ public static function list_course_competencies($courseorid) { static::require_enabled(); $course = $courseorid; if (!is_object($courseorid)) { $course = get_course($courseorid); } // Check the user has access to the course. self::validate_course($course); $context = context_course::instance($course->id); $capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage'); if (!has_any_capability($capabilities, $context)) { throw new required_capability_exception($context, 'moodle/competency:coursecompetencyview', 'nopermissions', ''); } $result = array(); // TODO We could improve the performance of this into one single query. $coursecompetencies = course_competency::list_course_competencies($course->id); $competencies = course_competency::list_competencies($course->id); // Build the return values. foreach ($coursecompetencies as $key => $coursecompetency) { $result[] = array( 'competency' => $competencies[$coursecompetency->get('competencyid')], 'coursecompetency' => $coursecompetency ); } return $result; } /** * Get a user competency. * * @param int $userid The user ID. * @param int $competencyid The competency ID. * @return user_competency */ public static function get_user_competency($userid, $competencyid) { static::require_enabled(); $existing = user_competency::get_multiple($userid, array($competencyid)); $uc = array_pop($existing); if (!$uc) { $uc = user_competency::create_relation($userid, $competencyid); $uc->create(); } if (!$uc->can_read()) { throw new required_capability_exception($uc->get_context(), 'moodle/competency:usercompetencyview', 'nopermissions', ''); } return $uc; } /** * Get a user competency by ID. * * @param int $usercompetencyid The user competency ID. * @return user_competency */ public static function get_user_competency_by_id($usercompetencyid) { static::require_enabled(); $uc = new user_competency($usercompetencyid); if (!$uc->can_read()) { throw new required_capability_exception($uc->get_context(), 'moodle/competency:usercompetencyview', 'nopermissions', ''); } return $uc; } /** * Count the competencies associated to a course module. * * @param mixed $cmorid The course module, or its ID. * @return int */ public static function count_course_module_competencies($cmorid) { static::require_enabled(); $cm = $cmorid; if (!is_object($cmorid)) { $cm = get_coursemodule_from_id('', $cmorid, 0, true, MUST_EXIST); } // Check the user has access to the course module. self::validate_course_module($cm); $context = context_module::instance($cm->id); $capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage'); if (!has_any_capability($capabilities, $context)) { throw new required_capability_exception($context, 'moodle/competency:coursecompetencyview', 'nopermissions', ''); } return course_module_competency::count_competencies($cm->id); } /** * List the competencies associated to a course module. * * @param mixed $cmorid The course module, or its ID. * @return array( array( * 'competency' => \core_competency\competency, * 'coursemodulecompetency' => \core_competency\course_module_competency * )) */ public static function list_course_module_competencies($cmorid) { static::require_enabled(); $cm = $cmorid; if (!is_object($cmorid)) { $cm = get_coursemodule_from_id('', $cmorid, 0, true, MUST_EXIST); } // Check the user has access to the course module. self::validate_course_module($cm); $context = context_module::instance($cm->id); $capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage'); if (!has_any_capability($capabilities, $context)) { throw new required_capability_exception($context, 'moodle/competency:coursecompetencyview', 'nopermissions', ''); } $result = array(); // TODO We could improve the performance of this into one single query. $coursemodulecompetencies = course_module_competency::list_course_module_competencies($cm->id); $competencies = course_module_competency::list_competencies($cm->id); // Build the return values. foreach ($coursemodulecompetencies as $key => $coursemodulecompetency) { $result[] = array( 'competency' => $competencies[$coursemodulecompetency->get('competencyid')], 'coursemodulecompetency' => $coursemodulecompetency ); } return $result; } /** * Get a user competency in a course. * * @param int $courseid The id of the course to check. * @param int $userid The id of the course to check. * @param int $competencyid The id of the competency. * @return user_competency_course */ public static function get_user_competency_in_course($courseid, $userid, $competencyid) { static::require_enabled(); // First we do a permissions check. $context = context_course::instance($courseid); $capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage'); if (!has_any_capability($capabilities, $context)) { throw new required_capability_exception($context, 'moodle/competency:coursecompetencyview', 'nopermissions', ''); } else if (!user_competency::can_read_user_in_course($userid, $courseid)) { throw new required_capability_exception($context, 'moodle/competency:usercompetencyview', 'nopermissions', ''); } // This will throw an exception if the competency does not belong to the course. $competency = course_competency::get_competency($courseid, $competencyid); $params = array('courseid' => $courseid, 'userid' => $userid, 'competencyid' => $competencyid); $exists = user_competency_course::get_record($params); // Create missing. if ($exists) { $ucc = $exists; } else { $ucc = user_competency_course::create_relation($userid, $competency->get('id'), $courseid); $ucc->create(); } return $ucc; } /** * List all the user competencies in a course. * * @param int $courseid The id of the course to check. * @param int $userid The id of the course to check. * @return array of user_competency_course objects */ public static function list_user_competencies_in_course($courseid, $userid) { static::require_enabled(); // First we do a permissions check. $context = context_course::instance($courseid); $onlyvisible = 1; $capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage'); if (!has_any_capability($capabilities, $context)) { throw new required_capability_exception($context, 'moodle/competency:coursecompetencyview', 'nopermissions', ''); } else if (!user_competency::can_read_user_in_course($userid, $courseid)) { throw new required_capability_exception($context, 'moodle/competency:usercompetencyview', 'nopermissions', ''); } // OK - all set. $competencylist = course_competency::list_competencies($courseid, false); $existing = user_competency_course::get_multiple($userid, $courseid, $competencylist); // Create missing. $orderedusercompetencycourses = array(); $somemissing = false; foreach ($competencylist as $coursecompetency) { $found = false; foreach ($existing as $usercompetencycourse) { if ($usercompetencycourse->get('competencyid') == $coursecompetency->get('id')) { $found = true; $orderedusercompetencycourses[$usercompetencycourse->get('id')] = $usercompetencycourse; break; } } if (!$found) { $ucc = user_competency_course::create_relation($userid, $coursecompetency->get('id'), $courseid); $ucc->create(); $orderedusercompetencycourses[$ucc->get('id')] = $ucc; } } return $orderedusercompetencycourses; } /** * List the user competencies to review. * * The method returns values in this format: * * array( * 'competencies' => array( * (stdClass)( * 'usercompetency' => (user_competency), * 'competency' => (competency), * 'user' => (user) * ) * ), * 'count' => (int) * ) * * @param int $skip The number of records to skip. * @param int $limit The number of results to return. * @param int $userid The user we're getting the competencies to review for. * @return array Containing the keys 'count', and 'competencies'. The 'competencies' key contains an object * which contains 'competency', 'usercompetency' and 'user'. */ public static function list_user_competencies_to_review($skip = 0, $limit = 50, $userid = null) { global $DB, $USER; static::require_enabled(); if ($userid === null) { $userid = $USER->id; } $capability = 'moodle/competency:usercompetencyreview'; $ucfields = user_competency::get_sql_fields('uc', 'uc_'); $compfields = competency::get_sql_fields('c', 'c_'); $usercols = array('id') + get_user_fieldnames(); $userfields = array(); foreach ($usercols as $field) { $userfields[] = "u." . $field . " AS usr_" . $field; } $userfields = implode(',', $userfields); $select = "SELECT $ucfields, $compfields, $userfields"; $countselect = "SELECT COUNT('x')"; $sql = " FROM {" . user_competency::TABLE . "} uc JOIN {" . competency::TABLE . "} c ON c.id = uc.competencyid JOIN {user} u ON u.id = uc.userid WHERE (uc.status = :waitingforreview OR (uc.status = :inreview AND uc.reviewerid = :reviewerid)) AND u.deleted = 0"; $ordersql = " ORDER BY c.shortname ASC"; $params = array( 'inreview' => user_competency::STATUS_IN_REVIEW, 'reviewerid' => $userid, 'waitingforreview' => user_competency::STATUS_WAITING_FOR_REVIEW, ); $countsql = $countselect . $sql; // Primary check to avoid the hard work of getting the users in which the user has permission. $count = $DB->count_records_sql($countselect . $sql, $params); if ($count < 1) { return array('count' => 0, 'competencies' => array()); } // TODO MDL-52243 Use core function. list($insql, $inparams) = self::filter_users_with_capability_on_user_context_sql( $capability, $userid, SQL_PARAMS_NAMED); $params += $inparams; $countsql = $countselect . $sql . " AND uc.userid $insql"; $getsql = $select . $sql . " AND uc.userid $insql " . $ordersql; // Extracting the results. $competencies = array(); $records = $DB->get_recordset_sql($getsql, $params, $skip, $limit); foreach ($records as $record) { $objects = (object) array( 'usercompetency' => new user_competency(0, user_competency::extract_record($record, 'uc_')), 'competency' => new competency(0, competency::extract_record($record, 'c_')), 'user' => persistent::extract_record($record, 'usr_'), ); $competencies[] = $objects; } $records->close(); return array( 'count' => $DB->count_records_sql($countsql, $params), 'competencies' => $competencies ); } /** * Add a competency to this course module. * * @param mixed $cmorid The course module, or id of the course module * @param int $competencyid The id of the competency * @return bool */ public static function add_competency_to_course_module($cmorid, $competencyid) { static::require_enabled(); $cm = $cmorid; if (!is_object($cmorid)) { $cm = get_coursemodule_from_id('', $cmorid, 0, true, MUST_EXIST); } // Check the user has access to the course module. self::validate_course_module($cm); // First we do a permissions check. $context = context_module::instance($cm->id); require_capability('moodle/competency:coursecompetencymanage', $context); // Check that the competency belongs to the course. $exists = course_competency::get_records(array('courseid' => $cm->course, 'competencyid' => $competencyid)); if (!$exists) { throw new coding_exception('Cannot add a competency to a module if it does not belong to the course'); } $record = new stdClass(); $record->cmid = $cm->id; $record->competencyid = $competencyid; $coursemodulecompetency = new course_module_competency(); $exists = $coursemodulecompetency->get_records(array('cmid' => $cm->id, 'competencyid' => $competencyid)); if (!$exists) { $coursemodulecompetency->from_record($record); if ($coursemodulecompetency->create()) { return true; } } return false; } /** * Remove a competency from this course module. * * @param mixed $cmorid The course module, or id of the course module * @param int $competencyid The id of the competency * @return bool */ public static function remove_competency_from_course_module($cmorid, $competencyid) { static::require_enabled(); $cm = $cmorid; if (!is_object($cmorid)) { $cm = get_coursemodule_from_id('', $cmorid, 0, true, MUST_EXIST); } // Check the user has access to the course module. self::validate_course_module($cm); // First we do a permissions check. $context = context_module::instance($cm->id); require_capability('moodle/competency:coursecompetencymanage', $context); $record = new stdClass(); $record->cmid = $cm->id; $record->competencyid = $competencyid; $competency = new competency($competencyid); $exists = course_module_competency::get_record(array('cmid' => $cm->id, 'competencyid' => $competencyid)); if ($exists) { return $exists->delete(); } return false; } /** * Move the course module competency up or down in the display list. * * Requires moodle/competency:coursecompetencymanage capability at the course module context. * * @param mixed $cmorid The course module, or id of the course module * @param int $competencyidfrom The id of the competency we are moving. * @param int $competencyidto The id of the competency we are moving to. * @return boolean */ public static function reorder_course_module_competency($cmorid, $competencyidfrom, $competencyidto) { static::require_enabled(); $cm = $cmorid; if (!is_object($cmorid)) { $cm = get_coursemodule_from_id('', $cmorid, 0, true, MUST_EXIST); } // Check the user has access to the course module. self::validate_course_module($cm); // First we do a permissions check. $context = context_module::instance($cm->id); require_capability('moodle/competency:coursecompetencymanage', $context); $down = true; $matches = course_module_competency::get_records(array('cmid' => $cm->id, 'competencyid' => $competencyidfrom)); if (count($matches) == 0) { throw new coding_exception('The link does not exist'); } $competencyfrom = array_pop($matches); $matches = course_module_competency::get_records(array('cmid' => $cm->id, 'competencyid' => $competencyidto)); if (count($matches) == 0) { throw new coding_exception('The link does not exist'); } $competencyto = array_pop($matches); $all = course_module_competency::get_records(array('cmid' => $cm->id), 'sortorder', 'ASC', 0, 0); if ($competencyfrom->get('sortorder') > $competencyto->get('sortorder')) { // We are moving up, so put it before the "to" item. $down = false; } foreach ($all as $id => $coursemodulecompetency) { $sort = $coursemodulecompetency->get('sortorder'); if ($down && $sort > $competencyfrom->get('sortorder') && $sort <= $competencyto->get('sortorder')) { $coursemodulecompetency->set('sortorder', $coursemodulecompetency->get('sortorder') - 1); $coursemodulecompetency->update(); } else if (!$down && $sort >= $competencyto->get('sortorder') && $sort < $competencyfrom->get('sortorder')) { $coursemodulecompetency->set('sortorder', $coursemodulecompetency->get('sortorder') + 1); $coursemodulecompetency->update(); } } $competencyfrom->set('sortorder', $competencyto->get('sortorder')); return $competencyfrom->update(); } /** * Update ruleoutcome value for a course module competency. * * @param int|course_module_competency $coursemodulecompetencyorid The course_module_competency, or its ID. * @param int $ruleoutcome The value of ruleoutcome. * @param bool $overridegrade If true, will override existing grades in related competencies. * @return bool True on success. */ public static function set_course_module_competency_ruleoutcome($coursemodulecompetencyorid, $ruleoutcome, $overridegrade = false) { static::require_enabled(); $coursemodulecompetency = $coursemodulecompetencyorid; if (!is_object($coursemodulecompetency)) { $coursemodulecompetency = new course_module_competency($coursemodulecompetencyorid); } $cm = get_coursemodule_from_id('', $coursemodulecompetency->get('cmid'), 0, true, MUST_EXIST); self::validate_course_module($cm); $context = context_module::instance($cm->id); require_capability('moodle/competency:coursecompetencymanage', $context); $coursemodulecompetency->set('ruleoutcome', $ruleoutcome); $coursemodulecompetency->set('overridegrade', $overridegrade); return $coursemodulecompetency->update(); } /** * Add a competency to this course. * * @param int $courseid The id of the course * @param int $competencyid The id of the competency * @return bool */ public static function add_competency_to_course($courseid, $competencyid) { static::require_enabled(); // Check the user has access to the course. self::validate_course($courseid); // First we do a permissions check. $context = context_course::instance($courseid); require_capability('moodle/competency:coursecompetencymanage', $context); $record = new stdClass(); $record->courseid = $courseid; $record->competencyid = $competencyid; $competency = new competency($competencyid); // Can not add a competency that belongs to a hidden framework. if ($competency->get_framework()->get('visible') == false) { throw new coding_exception('A competency belonging to hidden framework can not be linked to course'); } $coursecompetency = new course_competency(); $exists = $coursecompetency->get_records(array('courseid' => $courseid, 'competencyid' => $competencyid)); if (!$exists) { $coursecompetency->from_record($record); if ($coursecompetency->create()) { return true; } } return false; } /** * Remove a competency from this course. * * @param int $courseid The id of the course * @param int $competencyid The id of the competency * @return bool */ public static function remove_competency_from_course($courseid, $competencyid) { static::require_enabled(); // Check the user has access to the course. self::validate_course($courseid); // First we do a permissions check. $context = context_course::instance($courseid); require_capability('moodle/competency:coursecompetencymanage', $context); $record = new stdClass(); $record->courseid = $courseid; $record->competencyid = $competencyid; $coursecompetency = new course_competency(); $exists = course_competency::get_record(array('courseid' => $courseid, 'competencyid' => $competencyid)); if ($exists) { // Delete all course_module_competencies for this competency in this course. $cmcs = course_module_competency::get_records_by_competencyid_in_course($competencyid, $courseid); foreach ($cmcs as $cmc) { $cmc->delete(); } return $exists->delete(); } return false; } /** * Move the course competency up or down in the display list. * * Requires moodle/competency:coursecompetencymanage capability at the course context. * * @param int $courseid The course * @param int $competencyidfrom The id of the competency we are moving. * @param int $competencyidto The id of the competency we are moving to. * @return boolean */ public static function reorder_course_competency($courseid, $competencyidfrom, $competencyidto) { static::require_enabled(); // Check the user has access to the course. self::validate_course($courseid); // First we do a permissions check. $context = context_course::instance($courseid); require_capability('moodle/competency:coursecompetencymanage', $context); $down = true; $coursecompetency = new course_competency(); $matches = $coursecompetency->get_records(array('courseid' => $courseid, 'competencyid' => $competencyidfrom)); if (count($matches) == 0) { throw new coding_exception('The link does not exist'); } $competencyfrom = array_pop($matches); $matches = $coursecompetency->get_records(array('courseid' => $courseid, 'competencyid' => $competencyidto)); if (count($matches) == 0) { throw new coding_exception('The link does not exist'); } $competencyto = array_pop($matches); $all = $coursecompetency->get_records(array('courseid' => $courseid), 'sortorder', 'ASC', 0, 0); if ($competencyfrom->get('sortorder') > $competencyto->get('sortorder')) { // We are moving up, so put it before the "to" item. $down = false; } foreach ($all as $id => $coursecompetency) { $sort = $coursecompetency->get('sortorder'); if ($down && $sort > $competencyfrom->get('sortorder') && $sort <= $competencyto->get('sortorder')) { $coursecompetency->set('sortorder', $coursecompetency->get('sortorder') - 1); $coursecompetency->update(); } else if (!$down && $sort >= $competencyto->get('sortorder') && $sort < $competencyfrom->get('sortorder')) { $coursecompetency->set('sortorder', $coursecompetency->get('sortorder') + 1); $coursecompetency->update(); } } $competencyfrom->set('sortorder', $competencyto->get('sortorder')); return $competencyfrom->update(); } /** * Update ruleoutcome value for a course competency. * * @param int|course_competency $coursecompetencyorid The course_competency, or its ID. * @param int $ruleoutcome The value of ruleoutcome. * @return bool True on success. */ public static function set_course_competency_ruleoutcome($coursecompetencyorid, $ruleoutcome) { static::require_enabled(); $coursecompetency = $coursecompetencyorid; if (!is_object($coursecompetency)) { $coursecompetency = new course_competency($coursecompetencyorid); } $courseid = $coursecompetency->get('courseid'); self::validate_course($courseid); $coursecontext = context_course::instance($courseid); require_capability('moodle/competency:coursecompetencymanage', $coursecontext); $coursecompetency->set('ruleoutcome', $ruleoutcome); return $coursecompetency->update(); } /** * Create a learning plan template from a record containing all the data for the class. * * Requires moodle/competency:templatemanage capability. * * @param stdClass $record Record containing all the data for an instance of the class. * @return template */ public static function create_template(stdClass $record) { static::require_enabled(); $template = new template(0, $record); // First we do a permissions check. if (!$template->can_manage()) { throw new required_capability_exception($template->get_context(), 'moodle/competency:templatemanage', 'nopermissions', ''); } // OK - all set. $template = $template->create(); // Trigger a template created event. \core\event\competency_template_created::create_from_template($template)->trigger(); return $template; } /** * Duplicate a learning plan template. * * Requires moodle/competency:templatemanage capability at the template context. * * @param int $id the template id. * @return template */ public static function duplicate_template($id) { static::require_enabled(); $template = new template($id); // First we do a permissions check. if (!$template->can_manage()) { throw new required_capability_exception($template->get_context(), 'moodle/competency:templatemanage', 'nopermissions', ''); } // OK - all set. $competencies = template_competency::list_competencies($id, false); // Adding the suffix copy. $template->set('shortname', get_string('duplicateditemname', 'core_competency', $template->get('shortname'))); $template->set('id', 0); $duplicatedtemplate = $template->create(); // Associate each competency for the duplicated template. foreach ($competencies as $competency) { self::add_competency_to_template($duplicatedtemplate->get('id'), $competency->get('id')); } // Trigger a template created event. \core\event\competency_template_created::create_from_template($duplicatedtemplate)->trigger(); return $duplicatedtemplate; } /** * Delete a learning plan template by id. * If the learning plan template has associated cohorts they will be deleted. * * Requires moodle/competency:templatemanage capability. * * @param int $id The record to delete. * @param boolean $deleteplans True to delete plans associaated to template, false to unlink them. * @return boolean */ public static function delete_template($id, $deleteplans = true) { global $DB; static::require_enabled(); $template = new template($id); // First we do a permissions check. if (!$template->can_manage()) { throw new required_capability_exception($template->get_context(), 'moodle/competency:templatemanage', 'nopermissions', ''); } $transaction = $DB->start_delegated_transaction(); $success = true; // Check if there are cohorts associated. $templatecohorts = template_cohort::get_relations_by_templateid($template->get('id')); foreach ($templatecohorts as $templatecohort) { $success = $templatecohort->delete(); if (!$success) { break; } } // Still OK, delete or unlink the plans from the template. if ($success) { $plans = plan::get_records(array('templateid' => $template->get('id'))); foreach ($plans as $plan) { $success = $deleteplans ? self::delete_plan($plan->get('id')) : self::unlink_plan_from_template($plan); if (!$success) { break; } } } // Still OK, delete the template comptencies. if ($success) { $success = template_competency::delete_by_templateid($template->get('id')); } // OK - all set. if ($success) { // Create a template deleted event. $event = \core\event\competency_template_deleted::create_from_template($template); $success = $template->delete(); } if ($success) { // Trigger a template deleted event. $event->trigger(); // Commit the transaction. $transaction->allow_commit(); } else { $transaction->rollback(new moodle_exception('Error while deleting the template.')); } return $success; } /** * Update the details for a learning plan template. * * Requires moodle/competency:templatemanage capability. * * @param stdClass $record The new details for the template. Note - must contain an id that points to the template to update. * @return boolean */ public static function update_template($record) { global $DB; static::require_enabled(); $template = new template($record->id); // First we do a permissions check. if (!$template->can_manage()) { throw new required_capability_exception($template->get_context(), 'moodle/competency:templatemanage', 'nopermissions', ''); } else if (isset($record->contextid) && $record->contextid != $template->get('contextid')) { // We can never change the context of a template. throw new coding_exception('Changing the context of an existing tempalte is forbidden.'); } $updateplans = false; $before = $template->to_record(); $template->from_record($record); $after = $template->to_record(); // Should we update the related plans? if ($before->duedate != $after->duedate || $before->shortname != $after->shortname || $before->description != $after->description || $before->descriptionformat != $after->descriptionformat) { $updateplans = true; } $transaction = $DB->start_delegated_transaction(); $success = $template->update(); if (!$success) { $transaction->rollback(new moodle_exception('Error while updating the template.')); return $success; } // Trigger a template updated event. \core\event\competency_template_updated::create_from_template($template)->trigger(); if ($updateplans) { plan::update_multiple_from_template($template); } $transaction->allow_commit(); return $success; } /** * Read the details for a single learning plan template and return a record. * * Requires moodle/competency:templateview capability at the system context. * * @param int $id The id of the template to read. * @return template */ public static function read_template($id) { static::require_enabled(); $template = new template($id); $context = $template->get_context(); // First we do a permissions check. if (!$template->can_read()) { throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview', 'nopermissions', ''); } // OK - all set. return $template; } /** * Perform a search based on the provided filters and return a paginated list of records. * * Requires moodle/competency:templateview capability at the system context. * * @param string $sort The column to sort on * @param string $order ('ASC' or 'DESC') * @param int $skip Number of records to skip (pagination) * @param int $limit Max of records to return (pagination) * @param context $context The parent context of the frameworks. * @param string $includes Defines what other contexts to fetch frameworks from. * Accepted values are: * - children: All descendants * - parents: All parents, grand parents, etc... * - self: Context passed only. * @param bool $onlyvisible If should list only visible templates * @return array of competency_framework */ public static function list_templates($sort, $order, $skip, $limit, $context, $includes = 'children', $onlyvisible = false) { global $DB; static::require_enabled(); // Get all the relevant contexts. $contexts = self::get_related_contexts($context, $includes, array('moodle/competency:templateview', 'moodle/competency:templatemanage')); // First we do a permissions check. if (empty($contexts)) { throw new required_capability_exception($context, 'moodle/competency:templateview', 'nopermissions', ''); } // Make the order by. $orderby = ''; if (!empty($sort)) { $orderby = $sort . ' ' . $order; } // OK - all set. $template = new template(); list($insql, $params) = $DB->get_in_or_equal(array_keys($contexts), SQL_PARAMS_NAMED); $select = "contextid $insql"; if ($onlyvisible) { $select .= " AND visible = :visible"; $params['visible'] = 1; } return $template->get_records_select($select, $params, $orderby, '*', $skip, $limit); } /** * Perform a search based on the provided filters and return how many results there are. * * Requires moodle/competency:templateview capability at the system context. * * @param context $context The parent context of the frameworks. * @param string $includes Defines what other contexts to fetch frameworks from. * Accepted values are: * - children: All descendants * - parents: All parents, grand parents, etc... * - self: Context passed only. * @return int */ public static function count_templates($context, $includes) { global $DB; static::require_enabled(); // First we do a permissions check. $contexts = self::get_related_contexts($context, $includes, array('moodle/competency:templateview', 'moodle/competency:templatemanage')); if (empty($contexts)) { throw new required_capability_exception($context, 'moodle/competency:templateview', 'nopermissions', ''); } // OK - all set. $template = new template(); list($insql, $inparams) = $DB->get_in_or_equal(array_keys($contexts), SQL_PARAMS_NAMED); return $template->count_records_select("contextid $insql", $inparams); } /** * Count all the templates using a competency. * * @param int $competencyid The id of the competency to check. * @return int */ public static function count_templates_using_competency($competencyid) { static::require_enabled(); // First we do a permissions check. $context = context_system::instance(); $onlyvisible = 1; $capabilities = array('moodle/competency:templateview', 'moodle/competency:templatemanage'); if (!has_any_capability($capabilities, $context)) { throw new required_capability_exception($context, 'moodle/competency:templateview', 'nopermissions', ''); } if (has_capability('moodle/competency:templatemanage', $context)) { $onlyvisible = 0; } // OK - all set. return template_competency::count_templates($competencyid, $onlyvisible); } /** * List all the learning plan templatesd using a competency. * * @param int $competencyid The id of the competency to check. * @return array[stdClass] Array of stdClass containing id and shortname. */ public static function list_templates_using_competency($competencyid) { static::require_enabled(); // First we do a permissions check. $context = context_system::instance(); $onlyvisible = 1; $capabilities = array('moodle/competency:templateview', 'moodle/competency:templatemanage'); if (!has_any_capability($capabilities, $context)) { throw new required_capability_exception($context, 'moodle/competency:templateview', 'nopermissions', ''); } if (has_capability('moodle/competency:templatemanage', $context)) { $onlyvisible = 0; } // OK - all set. return template_competency::list_templates($competencyid, $onlyvisible); } /** * Count all the competencies in a learning plan template. * * @param template|int $templateorid The template or its ID. * @return int */ public static function count_competencies_in_template($templateorid) { static::require_enabled(); // First we do a permissions check. $template = $templateorid; if (!is_object($template)) { $template = new template($template); } if (!$template->can_read()) { throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview', 'nopermissions', ''); } // OK - all set. return template_competency::count_competencies($template->get('id')); } /** * Count all the competencies in a learning plan template with no linked courses. * * @param template|int $templateorid The template or its ID. * @return int */ public static function count_competencies_in_template_with_no_courses($templateorid) { // First we do a permissions check. $template = $templateorid; if (!is_object($template)) { $template = new template($template); } if (!$template->can_read()) { throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview', 'nopermissions', ''); } // OK - all set. return template_competency::count_competencies_with_no_courses($template->get('id')); } /** * List all the competencies in a template. * * @param template|int $templateorid The template or its ID. * @return array of competencies */ public static function list_competencies_in_template($templateorid) { static::require_enabled(); // First we do a permissions check. $template = $templateorid; if (!is_object($template)) { $template = new template($template); } if (!$template->can_read()) { throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview', 'nopermissions', ''); } // OK - all set. return template_competency::list_competencies($template->get('id')); } /** * Add a competency to this template. * * @param int $templateid The id of the template * @param int $competencyid The id of the competency * @return bool */ public static function add_competency_to_template($templateid, $competencyid) { static::require_enabled(); // First we do a permissions check. $template = new template($templateid); if (!$template->can_manage()) { throw new required_capability_exception($template->get_context(), 'moodle/competency:templatemanage', 'nopermissions', ''); } $record = new stdClass(); $record->templateid = $templateid; $record->competencyid = $competencyid; $competency = new competency($competencyid); // Can not add a competency that belongs to a hidden framework. if ($competency->get_framework()->get('visible') == false) { throw new coding_exception('A competency belonging to hidden framework can not be added'); } $exists = template_competency::get_records(array('templateid' => $templateid, 'competencyid' => $competencyid)); if (!$exists) { $templatecompetency = new template_competency(0, $record); $templatecompetency->create(); return true; } return false; } /** * Remove a competency from this template. * * @param int $templateid The id of the template * @param int $competencyid The id of the competency * @return bool */ public static function remove_competency_from_template($templateid, $competencyid) { static::require_enabled(); // First we do a permissions check. $template = new template($templateid); if (!$template->can_manage()) { throw new required_capability_exception($template->get_context(), 'moodle/competency:templatemanage', 'nopermissions', ''); } $record = new stdClass(); $record->templateid = $templateid; $record->competencyid = $competencyid; $competency = new competency($competencyid); $exists = template_competency::get_records(array('templateid' => $templateid, 'competencyid' => $competencyid)); if ($exists) { $link = array_pop($exists); return $link->delete(); } return false; } /** * Move the template competency up or down in the display list. * * Requires moodle/competency:templatemanage capability at the system context. * * @param int $templateid The template id * @param int $competencyidfrom The id of the competency we are moving. * @param int $competencyidto The id of the competency we are moving to. * @return boolean */ public static function reorder_template_competency($templateid, $competencyidfrom, $competencyidto) { static::require_enabled(); $template = new template($templateid); // First we do a permissions check. if (!$template->can_manage()) { throw new required_capability_exception($template->get_context(), 'moodle/competency:templatemanage', 'nopermissions', ''); } $down = true; $matches = template_competency::get_records(array('templateid' => $templateid, 'competencyid' => $competencyidfrom)); if (count($matches) == 0) { throw new coding_exception('The link does not exist'); } $competencyfrom = array_pop($matches); $matches = template_competency::get_records(array('templateid' => $templateid, 'competencyid' => $competencyidto)); if (count($matches) == 0) { throw new coding_exception('The link does not exist'); } $competencyto = array_pop($matches); $all = template_competency::get_records(array('templateid' => $templateid), 'sortorder', 'ASC', 0, 0); if ($competencyfrom->get('sortorder') > $competencyto->get('sortorder')) { // We are moving up, so put it before the "to" item. $down = false; } foreach ($all as $id => $templatecompetency) { $sort = $templatecompetency->get('sortorder'); if ($down && $sort > $competencyfrom->get('sortorder') && $sort <= $competencyto->get('sortorder')) { $templatecompetency->set('sortorder', $templatecompetency->get('sortorder') - 1); $templatecompetency->update(); } else if (!$down && $sort >= $competencyto->get('sortorder') && $sort < $competencyfrom->get('sortorder')) { $templatecompetency->set('sortorder', $templatecompetency->get('sortorder') + 1); $templatecompetency->update(); } } $competencyfrom->set('sortorder', $competencyto->get('sortorder')); return $competencyfrom->update(); } /** * Create a relation between a template and a cohort. * * This silently ignores when the relation already existed. * * @param template|int $templateorid The template or its ID. * @param stdClass|int $cohortorid The cohort ot its ID. * @return template_cohort */ public static function create_template_cohort($templateorid, $cohortorid) { global $DB; static::require_enabled(); $template = $templateorid; if (!is_object($template)) { $template = new template($template); } require_capability('moodle/competency:templatemanage', $template->get_context()); $cohort = $cohortorid; if (!is_object($cohort)) { $cohort = $DB->get_record('cohort', array('id' => $cohort), '*', MUST_EXIST); } // Replicate logic in cohort_can_view_cohort() because we can't use it directly as we don't have a course context. $cohortcontext = context::instance_by_id($cohort->contextid); if (!$cohort->visible && !has_capability('moodle/cohort:view', $cohortcontext)) { throw new required_capability_exception($cohortcontext, 'moodle/cohort:view', 'nopermissions', ''); } $tplcohort = template_cohort::get_relation($template->get('id'), $cohort->id); if (!$tplcohort->get('id')) { $tplcohort->create(); } return $tplcohort; } /** * Remove a relation between a template and a cohort. * * @param template|int $templateorid The template or its ID. * @param stdClass|int $cohortorid The cohort ot its ID. * @return boolean True on success or when the relation did not exist. */ public static function delete_template_cohort($templateorid, $cohortorid) { global $DB; static::require_enabled(); $template = $templateorid; if (!is_object($template)) { $template = new template($template); } require_capability('moodle/competency:templatemanage', $template->get_context()); $cohort = $cohortorid; if (!is_object($cohort)) { $cohort = $DB->get_record('cohort', array('id' => $cohort), '*', MUST_EXIST); } $tplcohort = template_cohort::get_relation($template->get('id'), $cohort->id); if (!$tplcohort->get('id')) { return true; } return $tplcohort->delete(); } /** * Lists user plans. * * @param int $userid * @return \core_competency\plan[] */ public static function list_user_plans($userid) { global $DB, $USER; static::require_enabled(); $select = 'userid = :userid'; $params = array('userid' => $userid); $context = context_user::instance($userid); // Check that we can read something here. if (!plan::can_read_user($userid) && !plan::can_read_user_draft($userid)) { throw new required_capability_exception($context, 'moodle/competency:planview', 'nopermissions', ''); } // The user cannot view the drafts. if (!plan::can_read_user_draft($userid)) { list($insql, $inparams) = $DB->get_in_or_equal(plan::get_draft_statuses(), SQL_PARAMS_NAMED, 'param', false); $select .= " AND status $insql"; $params += $inparams; } // The user cannot view the non-drafts. if (!plan::can_read_user($userid)) { list($insql, $inparams) = $DB->get_in_or_equal(array(plan::STATUS_ACTIVE, plan::STATUS_COMPLETE), SQL_PARAMS_NAMED, 'param', false); $select .= " AND status $insql"; $params += $inparams; } return plan::get_records_select($select, $params, 'name ASC'); } /** * List the plans to review. * * The method returns values in this format: * * array( * 'plans' => array( * (stdClass)( * 'plan' => (plan), * 'template' => (template), * 'owner' => (stdClass) * ) * ), * 'count' => (int) * ) * * @param int $skip The number of records to skip. * @param int $limit The number of results to return. * @param int $userid The user we're getting the plans to review for. * @return array Containing the keys 'count', and 'plans'. The 'plans' key contains an object * which contains 'plan', 'template' and 'owner'. */ public static function list_plans_to_review($skip = 0, $limit = 100, $userid = null) { global $DB, $USER; static::require_enabled(); if ($userid === null) { $userid = $USER->id; } $planfields = plan::get_sql_fields('p', 'plan_'); $tplfields = template::get_sql_fields('t', 'tpl_'); $usercols = array('id') + get_user_fieldnames(); $userfields = array(); foreach ($usercols as $field) { $userfields[] = "u." . $field . " AS usr_" . $field; } $userfields = implode(',', $userfields); $select = "SELECT $planfields, $tplfields, $userfields"; $countselect = "SELECT COUNT('x')"; $sql = " FROM {" . plan::TABLE . "} p JOIN {user} u ON u.id = p.userid LEFT JOIN {" . template::TABLE . "} t ON t.id = p.templateid WHERE (p.status = :waitingforreview OR (p.status = :inreview AND p.reviewerid = :reviewerid)) AND p.userid != :userid"; $params = array( 'waitingforreview' => plan::STATUS_WAITING_FOR_REVIEW, 'inreview' => plan::STATUS_IN_REVIEW, 'reviewerid' => $userid, 'userid' => $userid ); // Primary check to avoid the hard work of getting the users in which the user has permission. $count = $DB->count_records_sql($countselect . $sql, $params); if ($count < 1) { return array('count' => 0, 'plans' => array()); } // TODO MDL-52243 Use core function. list($insql, $inparams) = self::filter_users_with_capability_on_user_context_sql('moodle/competency:planreview', $userid, SQL_PARAMS_NAMED); $sql .= " AND p.userid $insql"; $params += $inparams; // Order by ID just to have some ordering in place. $ordersql = " ORDER BY p.id ASC"; $plans = array(); $records = $DB->get_recordset_sql($select . $sql . $ordersql, $params, $skip, $limit); foreach ($records as $record) { $plan = new plan(0, plan::extract_record($record, 'plan_')); $template = null; if ($plan->is_based_on_template()) { $template = new template(0, template::extract_record($record, 'tpl_')); } $plans[] = (object) array( 'plan' => $plan, 'template' => $template, 'owner' => persistent::extract_record($record, 'usr_'), ); } $records->close(); return array( 'count' => $DB->count_records_sql($countselect . $sql, $params), 'plans' => $plans ); } /** * Creates a learning plan based on the provided data. * * @param stdClass $record * @return \core_competency\plan */ public static function create_plan(stdClass $record) { global $USER; static::require_enabled(); $plan = new plan(0, $record); if ($plan->is_based_on_template()) { throw new coding_exception('To create a plan from a template use api::create_plan_from_template().'); } else if ($plan->get('status') == plan::STATUS_COMPLETE) { throw new coding_exception('A plan cannot be created as complete.'); } if (!$plan->can_manage()) { $context = context_user::instance($plan->get('userid')); throw new required_capability_exception($context, 'moodle/competency:planmanage', 'nopermissions', ''); } $plan->create(); // Trigger created event. \core\event\competency_plan_created::create_from_plan($plan)->trigger(); return $plan; } /** * Create a learning plan from a template. * * @param mixed $templateorid The template object or ID. * @param int $userid * @return false|\core_competency\plan Returns false when the plan already exists. */ public static function create_plan_from_template($templateorid, $userid) { static::require_enabled(); $template = $templateorid; if (!is_object($template)) { $template = new template($template); } // The user must be able to view the template to use it as a base for a plan. if (!$template->can_read()) { throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview', 'nopermissions', ''); } // Can not create plan from a hidden template. if ($template->get('visible') == false) { throw new coding_exception('A plan can not be created from a hidden template'); } // Convert the template to a plan. $record = $template->to_record(); $record->templateid = $record->id; $record->userid = $userid; $record->name = $record->shortname; $record->status = plan::STATUS_ACTIVE; unset($record->id); unset($record->timecreated); unset($record->timemodified); unset($record->usermodified); // Remove extra keys. $properties = plan::properties_definition(); foreach ($record as $key => $value) { if (!array_key_exists($key, $properties)) { unset($record->$key); } } $plan = new plan(0, $record); if (!$plan->can_manage()) { throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', ''); } // We first apply the permission checks as we wouldn't want to leak information by returning early that // the plan already exists. if (plan::record_exists_select('templateid = :templateid AND userid = :userid', array( 'templateid' => $template->get('id'), 'userid' => $userid))) { return false; } $plan->create(); // Trigger created event. \core\event\competency_plan_created::create_from_plan($plan)->trigger(); return $plan; } /** * Create learning plans from a template and cohort. * * @param mixed $templateorid The template object or ID. * @param int $cohortid The cohort ID. * @param bool $recreateunlinked When true the plans that were unlinked from this template will be re-created. * @return int The number of plans created. */ public static function create_plans_from_template_cohort($templateorid, $cohortid, $recreateunlinked = false) { global $DB, $CFG; static::require_enabled(); require_once($CFG->dirroot . '/cohort/lib.php'); $template = $templateorid; if (!is_object($template)) { $template = new template($template); } // The user must be able to view the template to use it as a base for a plan. if (!$template->can_read()) { throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview', 'nopermissions', ''); } // Can not create plan from a hidden template. if ($template->get('visible') == false) { throw new coding_exception('A plan can not be created from a hidden template'); } // Replicate logic in cohort_can_view_cohort() because we can't use it directly as we don't have a course context. $cohort = $DB->get_record('cohort', array('id' => $cohortid), '*', MUST_EXIST); $cohortcontext = context::instance_by_id($cohort->contextid); if (!$cohort->visible && !has_capability('moodle/cohort:view', $cohortcontext)) { throw new required_capability_exception($cohortcontext, 'moodle/cohort:view', 'nopermissions', ''); } // Convert the template to a plan. $recordbase = $template->to_record(); $recordbase->templateid = $recordbase->id; $recordbase->name = $recordbase->shortname; $recordbase->status = plan::STATUS_ACTIVE; unset($recordbase->id); unset($recordbase->timecreated); unset($recordbase->timemodified); unset($recordbase->usermodified); // Remove extra keys. $properties = plan::properties_definition(); foreach ($recordbase as $key => $value) { if (!array_key_exists($key, $properties)) { unset($recordbase->$key); } } // Create the plans. $created = 0; $userids = template_cohort::get_missing_plans($template->get('id'), $cohortid, $recreateunlinked); foreach ($userids as $userid) { $record = (object) (array) $recordbase; $record->userid = $userid; $plan = new plan(0, $record); if (!$plan->can_manage()) { // Silently skip members where permissions are lacking. continue; } $plan->create(); // Trigger created event. \core\event\competency_plan_created::create_from_plan($plan)->trigger(); $created++; } return $created; } /** * Unlink a plan from its template. * * @param \core_competency\plan|int $planorid The plan or its ID. * @return bool */ public static function unlink_plan_from_template($planorid) { global $DB; static::require_enabled(); $plan = $planorid; if (!is_object($planorid)) { $plan = new plan($planorid); } // The user must be allowed to manage the plans of the user, nothing about the template. if (!$plan->can_manage()) { throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', ''); } // Only plan with status DRAFT or ACTIVE can be unlinked.. if ($plan->get('status') == plan::STATUS_COMPLETE) { throw new coding_exception('Only draft or active plan can be unlinked from a template'); } // Early exit, it's already done... if (!$plan->is_based_on_template()) { return true; } // Fetch the template. $template = new template($plan->get('templateid')); // Now, proceed by copying all competencies to the plan, then update the plan. $transaction = $DB->start_delegated_transaction(); $competencies = template_competency::list_competencies($template->get('id'), false); $i = 0; foreach ($competencies as $competency) { $record = (object) array( 'planid' => $plan->get('id'), 'competencyid' => $competency->get('id'), 'sortorder' => $i++ ); $pc = new plan_competency(null, $record); $pc->create(); } $plan->set('origtemplateid', $template->get('id')); $plan->set('templateid', null); $success = $plan->update(); $transaction->allow_commit(); // Trigger unlinked event. \core\event\competency_plan_unlinked::create_from_plan($plan)->trigger(); return $success; } /** * Updates a plan. * * @param stdClass $record * @return \core_competency\plan */ public static function update_plan(stdClass $record) { static::require_enabled(); $plan = new plan($record->id); // Validate that the plan as it is can be managed. if (!$plan->can_manage()) { throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', ''); } else if ($plan->get('status') == plan::STATUS_COMPLETE) { // A completed plan cannot be edited. throw new coding_exception('Completed plan cannot be edited.'); } else if ($plan->is_based_on_template()) { // Prevent a plan based on a template to be edited. throw new coding_exception('Cannot update a plan that is based on a template.'); } else if (isset($record->templateid) && $plan->get('templateid') != $record->templateid) { // Prevent a plan to be based on a template. throw new coding_exception('Cannot base a plan on a template.'); } else if (isset($record->userid) && $plan->get('userid') != $record->userid) { // Prevent change of ownership as the capabilities are checked against that. throw new coding_exception('A plan cannot be transfered to another user'); } else if (isset($record->status) && $plan->get('status') != $record->status) { // Prevent change of status. throw new coding_exception('To change the status of a plan use the appropriate methods.'); } $plan->from_record($record); $plan->update(); // Trigger updated event. \core\event\competency_plan_updated::create_from_plan($plan)->trigger(); return $plan; } /** * Returns a plan data. * * @param int $id * @return \core_competency\plan */ public static function read_plan($id) { static::require_enabled(); $plan = new plan($id); if (!$plan->can_read()) { $context = context_user::instance($plan->get('userid')); throw new required_capability_exception($context, 'moodle/competency:planview', 'nopermissions', ''); } return $plan; } /** * Plan event viewed. * * @param mixed $planorid The id or the plan. * @return boolean */ public static function plan_viewed($planorid) { static::require_enabled(); $plan = $planorid; if (!is_object($plan)) { $plan = new plan($plan); } // First we do a permissions check. if (!$plan->can_read()) { $context = context_user::instance($plan->get('userid')); throw new required_capability_exception($context, 'moodle/competency:planview', 'nopermissions', ''); } // Trigger a template viewed event. \core\event\competency_plan_viewed::create_from_plan($plan)->trigger(); return true; } /** * Deletes a plan. * * Plans based on a template can be removed just like any other one. * * @param int $id * @return bool Success? */ public static function delete_plan($id) { global $DB; static::require_enabled(); $plan = new plan($id); if (!$plan->can_manage()) { $context = context_user::instance($plan->get('userid')); throw new required_capability_exception($context, 'moodle/competency:planmanage', 'nopermissions', ''); } // Wrap the suppression in a DB transaction. $transaction = $DB->start_delegated_transaction(); // Delete plan competencies. $plancomps = plan_competency::get_records(array('planid' => $plan->get('id'))); foreach ($plancomps as $plancomp) { $plancomp->delete(); } // Delete archive user competencies if the status of the plan is complete. if ($plan->get('status') == plan::STATUS_COMPLETE) { self::remove_archived_user_competencies_in_plan($plan); } $event = \core\event\competency_plan_deleted::create_from_plan($plan); $success = $plan->delete(); $transaction->allow_commit(); // Trigger deleted event. $event->trigger(); return $success; } /** * Cancel the review of a plan. * * @param int|plan $planorid The plan, or its ID. * @return bool */ public static function plan_cancel_review_request($planorid) { static::require_enabled(); $plan = $planorid; if (!is_object($plan)) { $plan = new plan($plan); } // We need to be able to view the plan at least. if (!$plan->can_read()) { throw new required_capability_exception($plan->get_context(), 'moodle/competency:planview', 'nopermissions', ''); } if ($plan->is_based_on_template()) { throw new coding_exception('Template plans cannot be reviewed.'); // This should never happen. } else if ($plan->get('status') != plan::STATUS_WAITING_FOR_REVIEW) { throw new coding_exception('The plan review cannot be cancelled at this stage.'); } else if (!$plan->can_request_review()) { throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', ''); } $plan->set('status', plan::STATUS_DRAFT); $result = $plan->update(); // Trigger review request cancelled event. \core\event\competency_plan_review_request_cancelled::create_from_plan($plan)->trigger(); return $result; } /** * Request the review of a plan. * * @param int|plan $planorid The plan, or its ID. * @return bool */ public static function plan_request_review($planorid) { static::require_enabled(); $plan = $planorid; if (!is_object($plan)) { $plan = new plan($plan); } // We need to be able to view the plan at least. if (!$plan->can_read()) { throw new required_capability_exception($plan->get_context(), 'moodle/competency:planview', 'nopermissions', ''); } if ($plan->is_based_on_template()) { throw new coding_exception('Template plans cannot be reviewed.'); // This should never happen. } else if ($plan->get('status') != plan::STATUS_DRAFT) { throw new coding_exception('The plan cannot be sent for review at this stage.'); } else if (!$plan->can_request_review()) { throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', ''); } $plan->set('status', plan::STATUS_WAITING_FOR_REVIEW); $result = $plan->update(); // Trigger review requested event. \core\event\competency_plan_review_requested::create_from_plan($plan)->trigger(); return $result; } /** * Start the review of a plan. * * @param int|plan $planorid The plan, or its ID. * @return bool */ public static function plan_start_review($planorid) { global $USER; static::require_enabled(); $plan = $planorid; if (!is_object($plan)) { $plan = new plan($plan); } // We need to be able to view the plan at least. if (!$plan->can_read()) { throw new required_capability_exception($plan->get_context(), 'moodle/competency:planview', 'nopermissions', ''); } if ($plan->is_based_on_template()) { throw new coding_exception('Template plans cannot be reviewed.'); // This should never happen. } else if ($plan->get('status') != plan::STATUS_WAITING_FOR_REVIEW) { throw new coding_exception('The plan review cannot be started at this stage.'); } else if (!$plan->can_review()) { throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', ''); } $plan->set('status', plan::STATUS_IN_REVIEW); $plan->set('reviewerid', $USER->id); $result = $plan->update(); // Trigger review started event. \core\event\competency_plan_review_started::create_from_plan($plan)->trigger(); return $result; } /** * Stop reviewing a plan. * * @param int|plan $planorid The plan, or its ID. * @return bool */ public static function plan_stop_review($planorid) { static::require_enabled(); $plan = $planorid; if (!is_object($plan)) { $plan = new plan($plan); } // We need to be able to view the plan at least. if (!$plan->can_read()) { throw new required_capability_exception($plan->get_context(), 'moodle/competency:planview', 'nopermissions', ''); } if ($plan->is_based_on_template()) { throw new coding_exception('Template plans cannot be reviewed.'); // This should never happen. } else if ($plan->get('status') != plan::STATUS_IN_REVIEW) { throw new coding_exception('The plan review cannot be stopped at this stage.'); } else if (!$plan->can_review()) { throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', ''); } $plan->set('status', plan::STATUS_DRAFT); $plan->set('reviewerid', null); $result = $plan->update(); // Trigger review stopped event. \core\event\competency_plan_review_stopped::create_from_plan($plan)->trigger(); return $result; } /** * Approve a plan. * * This means making the plan active. * * @param int|plan $planorid The plan, or its ID. * @return bool */ public static function approve_plan($planorid) { static::require_enabled(); $plan = $planorid; if (!is_object($plan)) { $plan = new plan($plan); } // We need to be able to view the plan at least. if (!$plan->can_read()) { throw new required_capability_exception($plan->get_context(), 'moodle/competency:planview', 'nopermissions', ''); } // We can approve a plan that is either a draft, in review, or waiting for review. if ($plan->is_based_on_template()) { throw new coding_exception('Template plans are already approved.'); // This should never happen. } else if (!$plan->is_draft()) { throw new coding_exception('The plan cannot be approved at this stage.'); } else if (!$plan->can_review()) { throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', ''); } $plan->set('status', plan::STATUS_ACTIVE); $plan->set('reviewerid', null); $result = $plan->update(); // Trigger approved event. \core\event\competency_plan_approved::create_from_plan($plan)->trigger(); return $result; } /** * Unapprove a plan. * * This means making the plan draft. * * @param int|plan $planorid The plan, or its ID. * @return bool */ public static function unapprove_plan($planorid) { static::require_enabled(); $plan = $planorid; if (!is_object($plan)) { $plan = new plan($plan); } // We need to be able to view the plan at least. if (!$plan->can_read()) { throw new required_capability_exception($plan->get_context(), 'moodle/competency:planview', 'nopermissions', ''); } if ($plan->is_based_on_template()) { throw new coding_exception('Template plans are always approved.'); // This should never happen. } else if ($plan->get('status') != plan::STATUS_ACTIVE) { throw new coding_exception('The plan cannot be sent back to draft at this stage.'); } else if (!$plan->can_review()) { throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', ''); } $plan->set('status', plan::STATUS_DRAFT); $result = $plan->update(); // Trigger unapproved event. \core\event\competency_plan_unapproved::create_from_plan($plan)->trigger(); return $result; } /** * Complete a plan. * * @param int|plan $planorid The plan, or its ID. * @return bool */ public static function complete_plan($planorid) { global $DB; static::require_enabled(); $plan = $planorid; if (!is_object($planorid)) { $plan = new plan($planorid); } // Validate that the plan can be managed. if (!$plan->can_manage()) { throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', ''); } // Check if the plan was already completed. if ($plan->get('status') == plan::STATUS_COMPLETE) { throw new coding_exception('The plan is already completed.'); } $originalstatus = $plan->get('status'); $plan->set('status', plan::STATUS_COMPLETE); // The user should also be able to manage the plan when it's completed. if (!$plan->can_manage()) { throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', ''); } // Put back original status because archive needs it to extract competencies from the right table. $plan->set('status', $originalstatus); // Do the things. $transaction = $DB->start_delegated_transaction(); self::archive_user_competencies_in_plan($plan); $plan->set('status', plan::STATUS_COMPLETE); $success = $plan->update(); if (!$success) { $transaction->rollback(new moodle_exception('The plan could not be updated.')); return $success; } $transaction->allow_commit(); // Trigger updated event. \core\event\competency_plan_completed::create_from_plan($plan)->trigger(); return $success; } /** * Reopen a plan. * * @param int|plan $planorid The plan, or its ID. * @return bool */ public static function reopen_plan($planorid) { global $DB; static::require_enabled(); $plan = $planorid; if (!is_object($planorid)) { $plan = new plan($planorid); } // Validate that the plan as it is can be managed. if (!$plan->can_manage()) { $context = context_user::instance($plan->get('userid')); throw new required_capability_exception($context, 'moodle/competency:planmanage', 'nopermissions', ''); } $beforestatus = $plan->get('status'); $plan->set('status', plan::STATUS_ACTIVE); // Validate if status can be changed. if (!$plan->can_manage()) { $context = context_user::instance($plan->get('userid')); throw new required_capability_exception($context, 'moodle/competency:planmanage', 'nopermissions', ''); } // Wrap the updates in a DB transaction. $transaction = $DB->start_delegated_transaction(); // Delete archived user competencies if the status of the plan is changed from complete to another status. $mustremovearchivedcompetencies = ($beforestatus == plan::STATUS_COMPLETE && $plan->get('status') != plan::STATUS_COMPLETE); if ($mustremovearchivedcompetencies) { self::remove_archived_user_competencies_in_plan($plan); } // If duedate less than or equal to duedate_threshold unset it. if ($plan->get('duedate') <= time() + plan::DUEDATE_THRESHOLD) { $plan->set('duedate', 0); } $success = $plan->update(); if (!$success) { $transaction->rollback(new moodle_exception('The plan could not be updated.')); return $success; } $transaction->allow_commit(); // Trigger reopened event. \core\event\competency_plan_reopened::create_from_plan($plan)->trigger(); return $success; } /** * Get a single competency from the user plan. * * @param int $planorid The plan, or its ID. * @param int $competencyid The competency id. * @return (object) array( * 'competency' => \core_competency\competency, * 'usercompetency' => \core_competency\user_competency * 'usercompetencyplan' => \core_competency\user_competency_plan * ) * The values of of keys usercompetency and usercompetencyplan cannot be defined at the same time. */ public static function get_plan_competency($planorid, $competencyid) { static::require_enabled(); $plan = $planorid; if (!is_object($planorid)) { $plan = new plan($planorid); } if (!user_competency::can_read_user($plan->get('userid'))) { throw new required_capability_exception($plan->get_context(), 'moodle/competency:usercompetencyview', 'nopermissions', ''); } $competency = $plan->get_competency($competencyid); // Get user competencies from user_competency_plan if the plan status is set to complete. $iscompletedplan = $plan->get('status') == plan::STATUS_COMPLETE; if ($iscompletedplan) { $usercompetencies = user_competency_plan::get_multiple($plan->get('userid'), $plan->get('id'), array($competencyid)); $ucresultkey = 'usercompetencyplan'; } else { $usercompetencies = user_competency::get_multiple($plan->get('userid'), array($competencyid)); $ucresultkey = 'usercompetency'; } $found = count($usercompetencies); if ($found) { $uc = array_pop($usercompetencies); } else { if ($iscompletedplan) { throw new coding_exception('A user competency plan is missing'); } else { $uc = user_competency::create_relation($plan->get('userid'), $competency->get('id')); $uc->create(); } } $plancompetency = (object) array( 'competency' => $competency, 'usercompetency' => null, 'usercompetencyplan' => null ); $plancompetency->$ucresultkey = $uc; return $plancompetency; } /** * List the plans with a competency. * * @param int $userid The user id we want the plans for. * @param int $competencyorid The competency, or its ID. * @return array[plan] Array of learning plans. */ public static function list_plans_with_competency($userid, $competencyorid) { global $USER; static::require_enabled(); $competencyid = $competencyorid; $competency = null; if (is_object($competencyid)) { $competency = $competencyid; $competencyid = $competency->get('id'); } $plans = plan::get_by_user_and_competency($userid, $competencyid); foreach ($plans as $index => $plan) { // Filter plans we cannot read. if (!$plan->can_read()) { unset($plans[$index]); } } return $plans; } /** * List the competencies in a user plan. * * @param int $planorid The plan, or its ID. * @return array((object) array( * 'competency' => \core_competency\competency, * 'usercompetency' => \core_competency\user_competency * 'usercompetencyplan' => \core_competency\user_competency_plan * )) * The values of of keys usercompetency and usercompetencyplan cannot be defined at the same time. */ public static function list_plan_competencies($planorid) { static::require_enabled(); $plan = $planorid; if (!is_object($planorid)) { $plan = new plan($planorid); } if (!$plan->can_read()) { $context = context_user::instance($plan->get('userid')); throw new required_capability_exception($context, 'moodle/competency:planview', 'nopermissions', ''); } $result = array(); $competencies = $plan->get_competencies(); // Get user competencies from user_competency_plan if the plan status is set to complete. $iscompletedplan = $plan->get('status') == plan::STATUS_COMPLETE; if ($iscompletedplan) { $usercompetencies = user_competency_plan::get_multiple($plan->get('userid'), $plan->get('id'), $competencies); $ucresultkey = 'usercompetencyplan'; } else { $usercompetencies = user_competency::get_multiple($plan->get('userid'), $competencies); $ucresultkey = 'usercompetency'; } // Build the return values. foreach ($competencies as $key => $competency) { $found = false; foreach ($usercompetencies as $uckey => $uc) { if ($uc->get('competencyid') == $competency->get('id')) { $found = true; unset($usercompetencies[$uckey]); break; } } if (!$found) { if ($iscompletedplan) { throw new coding_exception('A user competency plan is missing'); } else { $uc = user_competency::create_relation($plan->get('userid'), $competency->get('id')); } } $plancompetency = (object) array( 'competency' => $competency, 'usercompetency' => null, 'usercompetencyplan' => null ); $plancompetency->$ucresultkey = $uc; $result[] = $plancompetency; } return $result; } /** * Add a competency to a plan. * * @param int $planid The id of the plan * @param int $competencyid The id of the competency * @return bool */ public static function add_competency_to_plan($planid, $competencyid) { static::require_enabled(); $plan = new plan($planid); // First we do a permissions check. if (!$plan->can_manage()) { throw new required_capability_exception($plan->get_context(), 'moodle/competency:planmanage', 'nopermissions', ''); } else if ($plan->is_based_on_template()) { throw new coding_exception('A competency can not be added to a learning plan based on a template'); } if (!$plan->can_be_edited()) { throw new coding_exception('A competency can not be added to a completed learning plan'); } $competency = new competency($competencyid); // Can not add a competency that belong to a hidden framework. if ($competency->get_framework()->get('visible') == false) { throw new coding_exception('A competency belonging to hidden framework can not be added'); } $exists = plan_competency::get_record(array('planid' => $planid, 'competencyid' => $competencyid)); if (!$exists) { $record = new stdClass(); $record->planid = $planid; $record->competencyid = $competencyid; $plancompetency = new plan_competency(0, $record); $plancompetency->create(); } return true; } /** * Remove a competency from a plan. * * @param int $planid The plan id * @param int $competencyid The id of the competency * @return bool */ public static function remove_competency_from_plan($planid, $competencyid) { static::require_enabled(); $plan = new plan($planid); // First we do a permissions check. if (!$plan->can_manage()) { $context = context_user::instance($plan->get('userid')); throw new required_capability_exception($context, 'moodle/competency:planmanage', 'nopermissions', ''); } else if ($plan->is_based_on_template()) { throw new coding_exception('A competency can not be removed from a learning plan based on a template'); } if (!$plan->can_be_edited()) { throw new coding_exception('A competency can not be removed from a completed learning plan'); } $link = plan_competency::get_record(array('planid' => $planid, 'competencyid' => $competencyid)); if ($link) { return $link->delete(); } return false; } /** * Move the plan competency up or down in the display list. * * Requires moodle/competency:planmanage capability at the system context. * * @param int $planid The plan id * @param int $competencyidfrom The id of the competency we are moving. * @param int $competencyidto The id of the competency we are moving to. * @return boolean */ public static function reorder_plan_competency($planid, $competencyidfrom, $competencyidto) { static::require_enabled(); $plan = new plan($planid); // First we do a permissions check. if (!$plan->can_manage()) { $context = context_user::instance($plan->get('userid')); throw new required_capability_exception($context, 'moodle/competency:planmanage', 'nopermissions', ''); } else if ($plan->is_based_on_template()) { throw new coding_exception('A competency can not be reordered in a learning plan based on a template'); } if (!$plan->can_be_edited()) { throw new coding_exception('A competency can not be reordered in a completed learning plan'); } $down = true; $matches = plan_competency::get_records(array('planid' => $planid, 'competencyid' => $competencyidfrom)); if (count($matches) == 0) { throw new coding_exception('The link does not exist'); } $competencyfrom = array_pop($matches); $matches = plan_competency::get_records(array('planid' => $planid, 'competencyid' => $competencyidto)); if (count($matches) == 0) { throw new coding_exception('The link does not exist'); } $competencyto = array_pop($matches); $all = plan_competency::get_records(array('planid' => $planid), 'sortorder', 'ASC', 0, 0); if ($competencyfrom->get('sortorder') > $competencyto->get('sortorder')) { // We are moving up, so put it before the "to" item. $down = false; } foreach ($all as $id => $plancompetency) { $sort = $plancompetency->get('sortorder'); if ($down && $sort > $competencyfrom->get('sortorder') && $sort <= $competencyto->get('sortorder')) { $plancompetency->set('sortorder', $plancompetency->get('sortorder') - 1); $plancompetency->update(); } else if (!$down && $sort >= $competencyto->get('sortorder') && $sort < $competencyfrom->get('sortorder')) { $plancompetency->set('sortorder', $plancompetency->get('sortorder') + 1); $plancompetency->update(); } } $competencyfrom->set('sortorder', $competencyto->get('sortorder')); return $competencyfrom->update(); } /** * Cancel a user competency review request. * * @param int $userid The user ID. * @param int $competencyid The competency ID. * @return bool */ public static function user_competency_cancel_review_request($userid, $competencyid) { static::require_enabled(); $context = context_user::instance($userid); $uc = user_competency::get_record(array('userid' => $userid, 'competencyid' => $competencyid)); if (!$uc || !$uc->can_read()) { throw new required_capability_exception($context, 'moodle/competency:usercompetencyview', 'nopermissions', ''); } else if ($uc->get('status') != user_competency::STATUS_WAITING_FOR_REVIEW) { throw new coding_exception('The competency review request can not be cancelled at this stage.'); } else if (!$uc->can_request_review()) { throw new required_capability_exception($context, 'moodle/competency:usercompetencyrequestreview', 'nopermissions', ''); } $uc->set('status', user_competency::STATUS_IDLE); $result = $uc->update(); if ($result) { \core\event\competency_user_competency_review_request_cancelled::create_from_user_competency($uc)->trigger(); } return $result; } /** * Request a user competency review. * * @param int $userid The user ID. * @param int $competencyid The competency ID. * @return bool */ public static function user_competency_request_review($userid, $competencyid) { static::require_enabled(); $uc = user_competency::get_record(array('userid' => $userid, 'competencyid' => $competencyid)); if (!$uc) { $uc = user_competency::create_relation($userid, $competencyid); $uc->create(); } if (!$uc->can_read()) { throw new required_capability_exception($uc->get_context(), 'moodle/competency:usercompetencyview', 'nopermissions', ''); } else if ($uc->get('status') != user_competency::STATUS_IDLE) { throw new coding_exception('The competency can not be sent for review at this stage.'); } else if (!$uc->can_request_review()) { throw new required_capability_exception($uc->get_context(), 'moodle/competency:usercompetencyrequestreview', 'nopermissions', ''); } $uc->set('status', user_competency::STATUS_WAITING_FOR_REVIEW); $result = $uc->update(); if ($result) { \core\event\competency_user_competency_review_requested::create_from_user_competency($uc)->trigger(); } return $result; } /** * Start a user competency review. * * @param int $userid The user ID. * @param int $competencyid The competency ID. * @return bool */ public static function user_competency_start_review($userid, $competencyid) { global $USER; static::require_enabled(); $context = context_user::instance($userid); $uc = user_competency::get_record(array('userid' => $userid, 'competencyid' => $competencyid)); if (!$uc || !$uc->can_read()) { throw new required_capability_exception($context, 'moodle/competency:usercompetencyview', 'nopermissions', ''); } else if ($uc->get('status') != user_competency::STATUS_WAITING_FOR_REVIEW) { throw new coding_exception('The competency review can not be started at this stage.'); } else if (!$uc->can_review()) { throw new required_capability_exception($context, 'moodle/competency:usercompetencyreview', 'nopermissions', ''); } $uc->set('status', user_competency::STATUS_IN_REVIEW); $uc->set('reviewerid', $USER->id); $result = $uc->update(); if ($result) { \core\event\competency_user_competency_review_started::create_from_user_competency($uc)->trigger(); } return $result; } /** * Stop a user competency review. * * @param int $userid The user ID. * @param int $competencyid The competency ID. * @return bool */ public static function user_competency_stop_review($userid, $competencyid) { static::require_enabled(); $context = context_user::instance($userid); $uc = user_competency::get_record(array('userid' => $userid, 'competencyid' => $competencyid)); if (!$uc || !$uc->can_read()) { throw new required_capability_exception($context, 'moodle/competency:usercompetencyview', 'nopermissions', ''); } else if ($uc->get('status') != user_competency::STATUS_IN_REVIEW) { throw new coding_exception('The competency review can not be stopped at this stage.'); } else if (!$uc->can_review()) { throw new required_capability_exception($context, 'moodle/competency:usercompetencyreview', 'nopermissions', ''); } $uc->set('status', user_competency::STATUS_IDLE); $result = $uc->update(); if ($result) { \core\event\competency_user_competency_review_stopped::create_from_user_competency($uc)->trigger(); } return $result; } /** * Log user competency viewed event. * * @param user_competency|int $usercompetencyorid The user competency object or user competency id * @return bool */ public static function user_competency_viewed($usercompetencyorid) { static::require_enabled(); $uc = $usercompetencyorid; if (!is_object($uc)) { $uc = new user_competency($uc); } if (!$uc || !$uc->can_read()) { throw new required_capability_exception($uc->get_context(), 'moodle/competency:usercompetencyview', 'nopermissions', ''); } \core\event\competency_user_competency_viewed::create_from_user_competency_viewed($uc)->trigger(); return true; } /** * Log user competency viewed in plan event. * * @param user_competency|int $usercompetencyorid The user competency object or user competency id * @param int $planid The plan ID * @return bool */ public static function user_competency_viewed_in_plan($usercompetencyorid, $planid) { static::require_enabled(); $uc = $usercompetencyorid; if (!is_object($uc)) { $uc = new user_competency($uc); } if (!$uc || !$uc->can_read()) { throw new required_capability_exception($uc->get_context(), 'moodle/competency:usercompetencyview', 'nopermissions', ''); } $plan = new plan($planid); if ($plan->get('status') == plan::STATUS_COMPLETE) { throw new coding_exception('To log the user competency in a completed plan use the user_competency_plan_viewed method.'); } \core\event\competency_user_competency_viewed_in_plan::create_from_user_competency_viewed_in_plan($uc, $planid)->trigger(); return true; } /** * Log user competency viewed in course event. * * @param user_competency_course|int $usercoursecompetencyorid The user competency course object or its ID. * @param int $courseid The course ID * @return bool */ public static function user_competency_viewed_in_course($usercoursecompetencyorid) { static::require_enabled(); $ucc = $usercoursecompetencyorid; if (!is_object($ucc)) { $ucc = new user_competency_course($ucc); } if (!$ucc || !user_competency::can_read_user_in_course($ucc->get('userid'), $ucc->get('courseid'))) { throw new required_capability_exception($ucc->get_context(), 'moodle/competency:usercompetencyview', 'nopermissions', ''); } // Validate the course, this will throw an exception if not valid. self::validate_course($ucc->get('courseid')); \core\event\competency_user_competency_viewed_in_course::create_from_user_competency_viewed_in_course($ucc)->trigger(); return true; } /** * Log user competency plan viewed event. * * @param user_competency_plan|int $usercompetencyplanorid The user competency plan object or user competency plan id * @return bool */ public static function user_competency_plan_viewed($usercompetencyplanorid) { static::require_enabled(); $ucp = $usercompetencyplanorid; if (!is_object($ucp)) { $ucp = new user_competency_plan($ucp); } if (!$ucp || !user_competency::can_read_user($ucp->get('userid'))) { throw new required_capability_exception($ucp->get_context(), 'moodle/competency:usercompetencyview', 'nopermissions', ''); } $plan = new plan($ucp->get('planid')); if ($plan->get('status') != plan::STATUS_COMPLETE) { throw new coding_exception('To log the user competency in non-completed plan use ' . 'user_competency_viewed_in_plan method.'); } \core\event\competency_user_competency_plan_viewed::create_from_user_competency_plan($ucp)->trigger(); return true; } /** * Check if template has related data. * * @param int $templateid The id of the template to check. * @return boolean */ public static function template_has_related_data($templateid) { static::require_enabled(); // First we do a permissions check. $template = new template($templateid); if (!$template->can_read()) { throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview', 'nopermissions', ''); } // OK - all set. return $template->has_plans(); } /** * List all the related competencies. * * @param int $competencyid The id of the competency to check. * @return competency[] */ public static function list_related_competencies($competencyid) { static::require_enabled(); $competency = new competency($competencyid); if (!has_any_capability(array('moodle/competency:competencyview', 'moodle/competency:competencymanage'), $competency->get_context())) { throw new required_capability_exception($competency->get_context(), 'moodle/competency:competencyview', 'nopermissions', ''); } return $competency->get_related_competencies(); } /** * Add a related competency. * * @param int $competencyid The id of the competency * @param int $relatedcompetencyid The id of the related competency. * @return bool False when create failed, true on success, or if the relation already existed. */ public static function add_related_competency($competencyid, $relatedcompetencyid) { static::require_enabled(); $competency1 = new competency($competencyid); $competency2 = new competency($relatedcompetencyid); require_capability('moodle/competency:competencymanage', $competency1->get_context()); $relatedcompetency = related_competency::get_relation($competency1->get('id'), $competency2->get('id')); if (!$relatedcompetency->get('id')) { $relatedcompetency->create(); return true; } return true; } /** * Remove a related competency. * * @param int $competencyid The id of the competency. * @param int $relatedcompetencyid The id of the related competency. * @return bool True when it was deleted, false when it wasn't or the relation doesn't exist. */ public static function remove_related_competency($competencyid, $relatedcompetencyid) { static::require_enabled(); $competency = new competency($competencyid); // This only checks if we have the permission in either competency because both competencies // should belong to the same framework. require_capability('moodle/competency:competencymanage', $competency->get_context()); $relatedcompetency = related_competency::get_relation($competencyid, $relatedcompetencyid); if ($relatedcompetency->get('id')) { return $relatedcompetency->delete(); } return false; } /** * Read a user evidence. * * @param int $id * @return user_evidence */ public static function read_user_evidence($id) { static::require_enabled(); $userevidence = new user_evidence($id); if (!$userevidence->can_read()) { $context = $userevidence->get_context(); throw new required_capability_exception($context, 'moodle/competency:userevidenceview', 'nopermissions', ''); } return $userevidence; } /** * Create a new user evidence. * * @param object $data The data. * @param int $draftitemid The draft ID in which files have been saved. * @return user_evidence */ public static function create_user_evidence($data, $draftitemid = null) { static::require_enabled(); $userevidence = new user_evidence(null, $data); $context = $userevidence->get_context(); if (!$userevidence->can_manage()) { throw new required_capability_exception($context, 'moodle/competency:userevidencemanage', 'nopermissions', ''); } $userevidence->create(); if (!empty($draftitemid)) { $fileareaoptions = array('subdirs' => true); $itemid = $userevidence->get('id'); file_save_draft_area_files($draftitemid, $context->id, 'core_competency', 'userevidence', $itemid, $fileareaoptions); } // Trigger an evidence of prior learning created event. \core\event\competency_user_evidence_created::create_from_user_evidence($userevidence)->trigger(); return $userevidence; } /** * Create a new user evidence. * * @param object $data The data. * @param int $draftitemid The draft ID in which files have been saved. * @return user_evidence */ public static function update_user_evidence($data, $draftitemid = null) { static::require_enabled(); $userevidence = new user_evidence($data->id); $context = $userevidence->get_context(); if (!$userevidence->can_manage()) { throw new required_capability_exception($context, 'moodle/competency:userevidencemanage', 'nopermissions', ''); } else if (property_exists($data, 'userid') && $data->userid != $userevidence->get('userid')) { throw new coding_exception('Can not change the userid of a user evidence.'); } $userevidence->from_record($data); $userevidence->update(); if (!empty($draftitemid)) { $fileareaoptions = array('subdirs' => true); $itemid = $userevidence->get('id'); file_save_draft_area_files($draftitemid, $context->id, 'core_competency', 'userevidence', $itemid, $fileareaoptions); } // Trigger an evidence of prior learning updated event. \core\event\competency_user_evidence_updated::create_from_user_evidence($userevidence)->trigger(); return $userevidence; } /** * Delete a user evidence. * * @param int $id The user evidence ID. * @return bool */ public static function delete_user_evidence($id) { static::require_enabled(); $userevidence = new user_evidence($id); $context = $userevidence->get_context(); if (!$userevidence->can_manage()) { throw new required_capability_exception($context, 'moodle/competency:userevidencemanage', 'nopermissions', ''); } // Delete the user evidence. $userevidence->delete(); // Delete associated files. $fs = get_file_storage(); $fs->delete_area_files($context->id, 'core_competency', 'userevidence', $id); // Delete relation between evidence and competencies. $userevidence->set('id', $id); // Restore the ID to fully mock the object. $competencies = user_evidence_competency::get_competencies_by_userevidenceid($id); foreach ($competencies as $competency) { static::delete_user_evidence_competency($userevidence, $competency->get('id')); } // Trigger an evidence of prior learning deleted event. \core\event\competency_user_evidence_deleted::create_from_user_evidence($userevidence)->trigger(); $userevidence->set('id', 0); // Restore the object. return true; } /** * List the user evidence of a user. * * @param int $userid The user ID. * @return user_evidence[] */ public static function list_user_evidence($userid) { static::require_enabled(); if (!user_evidence::can_read_user($userid)) { $context = context_user::instance($userid); throw new required_capability_exception($context, 'moodle/competency:userevidenceview', 'nopermissions', ''); } $evidence = user_evidence::get_records(array('userid' => $userid), 'name'); return $evidence; } /** * Link a user evidence with a competency. * * @param user_evidence|int $userevidenceorid User evidence or its ID. * @param int $competencyid Competency ID. * @return user_evidence_competency */ public static function create_user_evidence_competency($userevidenceorid, $competencyid) { global $USER; static::require_enabled(); $userevidence = $userevidenceorid; if (!is_object($userevidence)) { $userevidence = self::read_user_evidence($userevidence); } // Perform user evidence capability checks. if (!$userevidence->can_manage()) { $context = $userevidence->get_context(); throw new required_capability_exception($context, 'moodle/competency:userevidencemanage', 'nopermissions', ''); } // Perform competency capability checks. $competency = self::read_competency($competencyid); // Get (and create) the relation. $relation = user_evidence_competency::get_relation($userevidence->get('id'), $competency->get('id')); if (!$relation->get('id')) { $relation->create(); $link = url::user_evidence($userevidence->get('id')); self::add_evidence( $userevidence->get('userid'), $competency, $userevidence->get_context(), evidence::ACTION_LOG, 'evidence_evidenceofpriorlearninglinked', 'core_competency', $userevidence->get('name'), false, $link->out(false), null, $USER->id ); } return $relation; } /** * Delete a relationship between a user evidence and a competency. * * @param user_evidence|int $userevidenceorid User evidence or its ID. * @param int $competencyid Competency ID. * @return bool */ public static function delete_user_evidence_competency($userevidenceorid, $competencyid) { global $USER; static::require_enabled(); $userevidence = $userevidenceorid; if (!is_object($userevidence)) { $userevidence = self::read_user_evidence($userevidence); } // Perform user evidence capability checks. if (!$userevidence->can_manage()) { $context = $userevidence->get_context(); throw new required_capability_exception($context, 'moodle/competency:userevidencemanage', 'nopermissions', ''); } // Get (and delete) the relation. $relation = user_evidence_competency::get_relation($userevidence->get('id'), $competencyid); if (!$relation->get('id')) { return true; } $success = $relation->delete(); if ($success) { self::add_evidence( $userevidence->get('userid'), $competencyid, $userevidence->get_context(), evidence::ACTION_LOG, 'evidence_evidenceofpriorlearningunlinked', 'core_competency', $userevidence->get('name'), false, null, null, $USER->id ); } return $success; } /** * Send request review for user evidence competencies. * * @param int $id The user evidence ID. * @return bool */ public static function request_review_of_user_evidence_linked_competencies($id) { $userevidence = new user_evidence($id); $context = $userevidence->get_context(); $userid = $userevidence->get('userid'); if (!$userevidence->can_manage()) { throw new required_capability_exception($context, 'moodle/competency:userevidencemanage', 'nopermissions', ''); } $usercompetencies = user_evidence_competency::get_user_competencies_by_userevidenceid($id); foreach ($usercompetencies as $usercompetency) { if ($usercompetency->get('status') == user_competency::STATUS_IDLE) { static::user_competency_request_review($userid, $usercompetency->get('competencyid')); } } return true; } /** * Recursively duplicate competencies from a tree, we start duplicating from parents to children to have a correct path. * This method does not copy the related competencies. * * @param int $frameworkid - framework id * @param stdClass[] $tree - list of framework competency nodes * @param int $oldparent - old parent id * @param int $newparent - new parent id * @return competency[] $matchids - List of old competencies ids matched with new competencies object. */ protected static function duplicate_competency_tree($frameworkid, $tree, $oldparent = 0, $newparent = 0) { $matchids = array(); foreach ($tree as $node) { if ($node->competency->get('parentid') == $oldparent) { $parentid = $node->competency->get('id'); // Create the competency. $competency = new competency(0, $node->competency->to_record()); $competency->set('competencyframeworkid', $frameworkid); $competency->set('parentid', $newparent); $competency->set('path', ''); $competency->set('id', 0); $competency->reset_rule(); $competency->create(); // Trigger the created event competency. \core\event\competency_created::create_from_competency($competency)->trigger(); // Match the old id with the new one. $matchids[$parentid] = $competency; if (!empty($node->children)) { // Duplicate children competency. $childrenids = self::duplicate_competency_tree($frameworkid, $node->children, $parentid, $competency->get('id')); // Array_merge does not keep keys when merging so we use the + operator. $matchids = $matchids + $childrenids; } } } return $matchids; } /** * Recursively migrate competency rules. * * @param array $tree - array of competencies object * @param competency[] $matchids - List of old competencies ids matched with new competencies object */ protected static function migrate_competency_tree_rules($tree, $matchids) { foreach ($tree as $node) { $oldcompid = $node->competency->get('id'); if ($node->competency->get('ruletype') && array_key_exists($oldcompid, $matchids)) { try { // Get the new competency. $competency = $matchids[$oldcompid]; $class = $node->competency->get('ruletype'); $newruleconfig = $class::migrate_config($node->competency->get('ruleconfig'), $matchids); $competency->set('ruleconfig', $newruleconfig); $competency->set('ruletype', $class); $competency->set('ruleoutcome', $node->competency->get('ruleoutcome')); $competency->update(); } catch (\Exception $e) { debugging('Could not migrate competency rule from: ' . $oldcompid . ' to: ' . $competency->get('id') . '.' . ' Exception: ' . $e->getMessage(), DEBUG_DEVELOPER); $competency->reset_rule(); } } if (!empty($node->children)) { self::migrate_competency_tree_rules($node->children, $matchids); } } } /** * Archive user competencies in a plan. * * @param plan $plan The plan object. * @return void */ protected static function archive_user_competencies_in_plan($plan) { // Check if the plan was already completed. if ($plan->get('status') == plan::STATUS_COMPLETE) { throw new coding_exception('The plan is already completed.'); } $competencies = $plan->get_competencies(); $usercompetencies = user_competency::get_multiple($plan->get('userid'), $competencies); $i = 0; foreach ($competencies as $competency) { $found = false; foreach ($usercompetencies as $uckey => $uc) { if ($uc->get('competencyid') == $competency->get('id')) { $found = true; $ucprecord = $uc->to_record(); $ucprecord->planid = $plan->get('id'); $ucprecord->sortorder = $i; unset($ucprecord->id); unset($ucprecord->status); unset($ucprecord->reviewerid); $usercompetencyplan = new user_competency_plan(0, $ucprecord); $usercompetencyplan->create(); unset($usercompetencies[$uckey]); break; } } // If the user competency doesn't exist, we create a new relation in user_competency_plan. if (!$found) { $usercompetencyplan = user_competency_plan::create_relation($plan->get('userid'), $competency->get('id'), $plan->get('id')); $usercompetencyplan->set('sortorder', $i); $usercompetencyplan->create(); } $i++; } } /** * Delete archived user competencies in a plan. * * @param plan $plan The plan object. * @return void */ protected static function remove_archived_user_competencies_in_plan($plan) { $competencies = $plan->get_competencies(); $usercompetenciesplan = user_competency_plan::get_multiple($plan->get('userid'), $plan->get('id'), $competencies); foreach ($usercompetenciesplan as $ucpkey => $ucp) { $ucp->delete(); } } /** * List all the evidence for a user competency. * * @param int $userid The user id - only used if usercompetencyid is 0. * @param int $competencyid The competency id - only used it usercompetencyid is 0. * @param int $planid The plan id - not used yet - but can be used to only list archived evidence if a plan is completed. * @param string $sort The field to sort the evidence by. * @param string $order The ordering of the sorting. * @param int $skip Number of records to skip. * @param int $limit Number of records to return. * @return \core_competency\evidence[] * @return array of \core_competency\evidence */ public static function list_evidence($userid = 0, $competencyid = 0, $planid = 0, $sort = 'timecreated', $order = 'DESC', $skip = 0, $limit = 0) { static::require_enabled(); if (!user_competency::can_read_user($userid)) { $context = context_user::instance($userid); throw new required_capability_exception($context, 'moodle/competency:usercompetencyview', 'nopermissions', ''); } $usercompetency = user_competency::get_record(array('userid' => $userid, 'competencyid' => $competencyid)); if (!$usercompetency) { return array(); } $plancompleted = false; if ($planid != 0) { $plan = new plan($planid); if ($plan->get('status') == plan::STATUS_COMPLETE) { $plancompleted = true; } } $select = 'usercompetencyid = :usercompetencyid'; $params = array('usercompetencyid' => $usercompetency->get('id')); if ($plancompleted) { $select .= ' AND timecreated <= :timecompleted'; $params['timecompleted'] = $plan->get('timemodified'); } $orderby = $sort . ' ' . $order; $orderby .= !empty($orderby) ? ', id DESC' : 'id DESC'; // Prevent random ordering. $evidence = evidence::get_records_select($select, $params, $orderby, '*', $skip, $limit); return $evidence; } /** * List all the evidence for a user competency in a course. * * @param int $userid The user ID. * @param int $courseid The course ID. * @param int $competencyid The competency ID. * @param string $sort The field to sort the evidence by. * @param string $order The ordering of the sorting. * @param int $skip Number of records to skip. * @param int $limit Number of records to return. * @return \core_competency\evidence[] */ public static function list_evidence_in_course($userid = 0, $courseid = 0, $competencyid = 0, $sort = 'timecreated', $order = 'DESC', $skip = 0, $limit = 0) { static::require_enabled(); if (!user_competency::can_read_user_in_course($userid, $courseid)) { $context = context_user::instance($userid); throw new required_capability_exception($context, 'moodle/competency:usercompetencyview', 'nopermissions', ''); } $usercompetency = user_competency::get_record(array('userid' => $userid, 'competencyid' => $competencyid)); if (!$usercompetency) { return array(); } $context = context_course::instance($courseid); return evidence::get_records_for_usercompetency($usercompetency->get('id'), $context, $sort, $order, $skip, $limit); } /** * Create an evidence from a list of parameters. * * Requires no capability because evidence can be added in many situations under any user. * * @param int $userid The user id for which evidence is added. * @param competency|int $competencyorid The competency, or its id for which evidence is added. * @param context|int $contextorid The context in which the evidence took place. * @param int $action The type of action to take on the competency. \core_competency\evidence::ACTION_*. * @param string $descidentifier The strings identifier. * @param string $desccomponent The strings component. * @param mixed $desca Any arguments the string requires. * @param bool $recommend When true, the user competency will be sent for review. * @param string $url The url the evidence may link to. * @param int $grade The grade, or scale ID item. * @param int $actionuserid The ID of the user who took the action of adding the evidence. Null when system. * This should be used when the action was taken by a real person, this will allow * to keep track of all the evidence given by a certain person. * @param string $note A note to attach to the evidence. * @return evidence * @throws coding_exception * @throws invalid_persistent_exception * @throws moodle_exception */ public static function add_evidence($userid, $competencyorid, $contextorid, $action, $descidentifier, $desccomponent, $desca = null, $recommend = false, $url = null, $grade = null, $actionuserid = null, $note = null, $overridegrade = false) { global $DB; static::require_enabled(); // Some clearly important variable assignments right there. $competencyid = $competencyorid; $competency = null; if (is_object($competencyid)) { $competency = $competencyid; $competencyid = $competency->get('id'); } $contextid = $contextorid; $context = $contextorid; if (is_object($contextorid)) { $contextid = $contextorid->id; } else { $context = context::instance_by_id($contextorid); } $setucgrade = false; $ucgrade = null; $ucproficiency = null; $usercompetencycourse = null; // Fetch or create the user competency. $usercompetency = user_competency::get_record(array('userid' => $userid, 'competencyid' => $competencyid)); if (!$usercompetency) { $usercompetency = user_competency::create_relation($userid, $competencyid); $usercompetency->create(); } // What should we be doing? switch ($action) { // Completing a competency. case evidence::ACTION_COMPLETE: // The logic here goes like this: // // if rating outside a course // - set the default grade and proficiency ONLY if there is no current grade // else we are in a course // - set the default grade and proficiency in the course ONLY if there is no current grade in the course // - then check the course settings to see if we should push the rating outside the course // - if we should push it // --- push it only if the user_competency (outside the course) has no grade // Done. if ($grade !== null) { throw new coding_exception("The grade MUST NOT be set with a 'completing' evidence."); } // Fetch the default grade to attach to the evidence. if (empty($competency)) { $competency = new competency($competencyid); } list($grade, $proficiency) = $competency->get_default_grade(); // Add user_competency_course record when in a course or module. if (in_array($context->contextlevel, array(CONTEXT_COURSE, CONTEXT_MODULE))) { $coursecontext = $context->get_course_context(); $courseid = $coursecontext->instanceid; $filterparams = array( 'userid' => $userid, 'competencyid' => $competencyid, 'courseid' => $courseid ); // Fetch or create user competency course. $usercompetencycourse = user_competency_course::get_record($filterparams); if (!$usercompetencycourse) { $usercompetencycourse = user_competency_course::create_relation($userid, $competencyid, $courseid); $usercompetencycourse->create(); } // Only update the grade and proficiency if there is not already a grade or the override option is enabled. if ($usercompetencycourse->get('grade') === null || $overridegrade) { // Set grade. $usercompetencycourse->set('grade', $grade); // Set proficiency. $usercompetencycourse->set('proficiency', $proficiency); } // Check the course settings to see if we should push to user plans. $coursesettings = course_competency_settings::get_by_courseid($courseid); $setucgrade = $coursesettings->get('pushratingstouserplans'); if ($setucgrade) { // Only push to user plans if there is not already a grade or the override option is enabled. if ($usercompetency->get('grade') !== null && !$overridegrade) { $setucgrade = false; } else { $ucgrade = $grade; $ucproficiency = $proficiency; } } } else { // When completing the competency we fetch the default grade from the competency. But we only mark // the user competency when a grade has not been set yet or if override option is enabled. // Complete is an action to use with automated systems. if ($usercompetency->get('grade') === null || $overridegrade) { $setucgrade = true; $ucgrade = $grade; $ucproficiency = $proficiency; } } break; // We override the grade, even overriding back to not set. case evidence::ACTION_OVERRIDE: $setucgrade = true; $ucgrade = $grade; if (empty($competency)) { $competency = new competency($competencyid); } if ($ucgrade !== null) { $ucproficiency = $competency->get_proficiency_of_grade($ucgrade); } // Add user_competency_course record when in a course or module. if (in_array($context->contextlevel, array(CONTEXT_COURSE, CONTEXT_MODULE))) { $coursecontext = $context->get_course_context(); $courseid = $coursecontext->instanceid; $filterparams = array( 'userid' => $userid, 'competencyid' => $competencyid, 'courseid' => $courseid ); // Fetch or create user competency course. $usercompetencycourse = user_competency_course::get_record($filterparams); if (!$usercompetencycourse) { $usercompetencycourse = user_competency_course::create_relation($userid, $competencyid, $courseid); $usercompetencycourse->create(); } // Get proficiency. $proficiency = $ucproficiency; if ($proficiency === null) { if (empty($competency)) { $competency = new competency($competencyid); } $proficiency = $competency->get_proficiency_of_grade($grade); } // Set grade. $usercompetencycourse->set('grade', $grade); // Set proficiency. $usercompetencycourse->set('proficiency', $proficiency); $coursesettings = course_competency_settings::get_by_courseid($courseid); if (!$coursesettings->get('pushratingstouserplans')) { $setucgrade = false; } } break; // Simply logging an evidence. case evidence::ACTION_LOG: if ($grade !== null) { throw new coding_exception("The grade MUST NOT be set when 'logging' an evidence."); } break; // Whoops, this is not expected. default: throw new coding_exception('Unexpected action parameter when registering an evidence.'); break; } // Should we recommend? if ($recommend && $usercompetency->get('status') == user_competency::STATUS_IDLE) { $usercompetency->set('status', user_competency::STATUS_WAITING_FOR_REVIEW); } // Setting the grade and proficiency for the user competency. $wascompleted = false; if ($setucgrade == true) { if (!$usercompetency->get('proficiency') && $ucproficiency) { $wascompleted = true; } $usercompetency->set('grade', $ucgrade); $usercompetency->set('proficiency', $ucproficiency); } // Prepare the evidence. $record = new stdClass(); $record->usercompetencyid = $usercompetency->get('id'); $record->contextid = $contextid; $record->action = $action; $record->descidentifier = $descidentifier; $record->desccomponent = $desccomponent; $record->grade = $grade; $record->actionuserid = $actionuserid; $record->note = $note; $evidence = new evidence(0, $record); $evidence->set('desca', $desca); $evidence->set('url', $url); // Validate both models, we should not operate on one if the other will not save. if (!$usercompetency->is_valid()) { throw new invalid_persistent_exception($usercompetency->get_errors()); } else if (!$evidence->is_valid()) { throw new invalid_persistent_exception($evidence->get_errors()); } // Save the user_competency_course record. if ($usercompetencycourse !== null) { // Validate and update. if (!$usercompetencycourse->is_valid()) { throw new invalid_persistent_exception($usercompetencycourse->get_errors()); } $usercompetencycourse->update(); } // Finally save. Pheww! $usercompetency->update(); $evidence->create(); // Trigger the evidence_created event. \core\event\competency_evidence_created::create_from_evidence($evidence, $usercompetency, $recommend)->trigger(); // The competency was marked as completed, apply the rules. if ($wascompleted) { self::apply_competency_rules_from_usercompetency($usercompetency, $competency, $overridegrade); } return $evidence; } /** * Read an evidence. * @param int $evidenceid The evidence ID. * @return evidence */ public static function read_evidence($evidenceid) { static::require_enabled(); $evidence = new evidence($evidenceid); $uc = new user_competency($evidence->get('usercompetencyid')); if (!$uc->can_read()) { throw new required_capability_exception($uc->get_context(), 'moodle/competency:usercompetencyview', 'nopermissions', ''); } return $evidence; } /** * Delete an evidence. * * @param evidence|int $evidenceorid The evidence, or its ID. * @return bool */ public static function delete_evidence($evidenceorid) { $evidence = $evidenceorid; if (!is_object($evidence)) { $evidence = new evidence($evidenceorid); } $uc = new user_competency($evidence->get('usercompetencyid')); if (!evidence::can_delete_user($uc->get('userid'))) { throw new required_capability_exception($uc->get_context(), 'moodle/competency:evidencedelete', 'nopermissions', ''); } return $evidence->delete(); } /** * Apply the competency rules from a user competency. * * The user competency passed should be one that was recently marked as complete. * A user competency is considered 'complete' when it's proficiency value is true. * * This method will check if the parent of this usercompetency's competency has any * rules and if so will see if they match. When matched it will take the required * step to add evidence and trigger completion, etc... * * @param user_competency $usercompetency The user competency recently completed. * @param competency|null $competency The competency of the user competency, useful to avoid unnecessary read. * @return void */ protected static function apply_competency_rules_from_usercompetency(user_competency $usercompetency, ?competency $competency = null, $overridegrade = false) { // Perform some basic checks. if (!$usercompetency->get('proficiency')) { throw new coding_exception('The user competency passed is not completed.'); } if ($competency === null) { $competency = $usercompetency->get_competency(); } if ($competency->get('id') != $usercompetency->get('competencyid')) { throw new coding_exception('Mismatch between user competency and competency.'); } // Fetch the parent. $parent = $competency->get_parent(); if ($parent === null) { return; } // The parent should have a rule, and a meaningful outcome. $ruleoutcome = $parent->get('ruleoutcome'); if ($ruleoutcome == competency::OUTCOME_NONE) { return; } $rule = $parent->get_rule_object(); if ($rule === null) { return; } // Fetch or create the user competency for the parent. $userid = $usercompetency->get('userid'); $parentuc = user_competency::get_record(array('userid' => $userid, 'competencyid' => $parent->get('id'))); if (!$parentuc) { $parentuc = user_competency::create_relation($userid, $parent->get('id')); $parentuc->create(); } // Does the rule match? if (!$rule->matches($parentuc)) { return; } // Figuring out what to do. $recommend = false; if ($ruleoutcome == competency::OUTCOME_EVIDENCE) { $action = evidence::ACTION_LOG; } else if ($ruleoutcome == competency::OUTCOME_RECOMMEND) { $action = evidence::ACTION_LOG; $recommend = true; } else if ($ruleoutcome == competency::OUTCOME_COMPLETE) { $action = evidence::ACTION_COMPLETE; } else { throw new moodle_exception('Unexpected rule outcome: ' + $ruleoutcome); } // Finally add an evidence. static::add_evidence( $userid, $parent, $parent->get_context()->id, $action, 'evidence_competencyrule', 'core_competency', null, $recommend, null, null, null, null, $overridegrade ); } /** * Observe when a course module is marked as completed. * * Note that the user being logged in while this happens may be anyone. * Do not rely on capability checks here! * * @param \core\event\course_module_completion_updated $event * @return void */ public static function observe_course_module_completion_updated(\core\event\course_module_completion_updated $event) { if (!static::is_enabled()) { return; } $eventdata = $event->get_record_snapshot('course_modules_completion', $event->objectid); if ($eventdata->completionstate == COMPLETION_COMPLETE || $eventdata->completionstate == COMPLETION_COMPLETE_PASS) { $coursemodulecompetencies = course_module_competency::list_course_module_competencies($eventdata->coursemoduleid); $cm = get_coursemodule_from_id(null, $eventdata->coursemoduleid); $fastmodinfo = get_fast_modinfo($cm->course)->cms[$cm->id]; $cmname = $fastmodinfo->name; $url = $fastmodinfo->url; foreach ($coursemodulecompetencies as $coursemodulecompetency) { $outcome = $coursemodulecompetency->get('ruleoutcome'); $action = null; $recommend = false; $strdesc = 'evidence_coursemodulecompleted'; $overridegrade = $coursemodulecompetency->get('overridegrade'); if ($outcome == course_module_competency::OUTCOME_NONE) { continue; } if ($outcome == course_module_competency::OUTCOME_EVIDENCE) { $action = evidence::ACTION_LOG; } else if ($outcome == course_module_competency::OUTCOME_RECOMMEND) { $action = evidence::ACTION_LOG; $recommend = true; } else if ($outcome == course_module_competency::OUTCOME_COMPLETE) { $action = evidence::ACTION_COMPLETE; } else { throw new moodle_exception('Unexpected rule outcome: ' + $outcome); } static::add_evidence( $event->relateduserid, $coursemodulecompetency->get('competencyid'), $event->contextid, $action, $strdesc, 'core_competency', $cmname, $recommend, $url, null, null, null, $overridegrade ); } } } /** * Observe when a course is marked as completed. * * Note that the user being logged in while this happens may be anyone. * Do not rely on capability checks here! * * @param \core\event\course_completed $event * @return void */ public static function observe_course_completed(\core\event\course_completed $event) { if (!static::is_enabled()) { return; } $sql = 'courseid = :courseid AND ruleoutcome != :nooutcome'; $params = array( 'courseid' => $event->courseid, 'nooutcome' => course_competency::OUTCOME_NONE ); $coursecompetencies = course_competency::get_records_select($sql, $params); $course = get_course($event->courseid); $courseshortname = format_string($course->shortname, null, array('context' => $event->contextid)); foreach ($coursecompetencies as $coursecompetency) { $outcome = $coursecompetency->get('ruleoutcome'); $action = null; $recommend = false; $strdesc = 'evidence_coursecompleted'; if ($outcome == course_module_competency::OUTCOME_NONE) { continue; } if ($outcome == course_competency::OUTCOME_EVIDENCE) { $action = evidence::ACTION_LOG; } else if ($outcome == course_competency::OUTCOME_RECOMMEND) { $action = evidence::ACTION_LOG; $recommend = true; } else if ($outcome == course_competency::OUTCOME_COMPLETE) { $action = evidence::ACTION_COMPLETE; } else { throw new moodle_exception('Unexpected rule outcome: ' + $outcome); } static::add_evidence( $event->relateduserid, $coursecompetency->get('competencyid'), $event->contextid, $action, $strdesc, 'core_competency', $courseshortname, $recommend, $event->get_url() ); } } /** * Action to perform when a course module is deleted. * * Do not call this directly, this is reserved for core use. * * @param stdClass $cm The CM object. * @return void */ public static function hook_course_module_deleted(stdClass $cm) { global $DB; $DB->delete_records(course_module_competency::TABLE, array('cmid' => $cm->id)); $context = context_module::instance($cm->id); $DB->delete_records(evidence::TABLE, ['contextid' => $context->id]); } /** * Action to perform when a course is deleted. * * Do not call this directly, this is reserved for core use. * * @param stdClass $course The course object. * @return void */ public static function hook_course_deleted(stdClass $course) { global $DB; $DB->delete_records(course_competency::TABLE, array('courseid' => $course->id)); $DB->delete_records(course_competency_settings::TABLE, array('courseid' => $course->id)); $DB->delete_records(user_competency_course::TABLE, array('courseid' => $course->id)); } /** * Action to perform when a course is being reset. * * Do not call this directly, this is reserved for core use. * * @param int $courseid The course ID. * @return void */ public static function hook_course_reset_competency_ratings($courseid) { global $DB; $DB->delete_records(user_competency_course::TABLE, array('courseid' => $courseid)); } /** * Action to perform when a cohort is deleted. * * Do not call this directly, this is reserved for core use. * * @param \stdClass $cohort The cohort object. * @return void */ public static function hook_cohort_deleted(\stdClass $cohort) { global $DB; $DB->delete_records(template_cohort::TABLE, array('cohortid' => $cohort->id)); } /** * Action to perform when a user is deleted. * * @param int $userid The user id. */ public static function hook_user_deleted($userid) { global $DB; $usercompetencies = $DB->get_records(user_competency::TABLE, ['userid' => $userid], '', 'id'); foreach ($usercompetencies as $usercomp) { $DB->delete_records(evidence::TABLE, ['usercompetencyid' => $usercomp->id]); } $DB->delete_records(user_competency::TABLE, ['userid' => $userid]); $DB->delete_records(user_competency_course::TABLE, ['userid' => $userid]); $DB->delete_records(user_competency_plan::TABLE, ['userid' => $userid]); // Delete any associated files. $fs = get_file_storage(); $context = context_user::instance($userid); $userevidences = $DB->get_records(user_evidence::TABLE, ['userid' => $userid], '', 'id'); foreach ($userevidences as $userevidence) { $DB->delete_records(user_evidence_competency::TABLE, ['userevidenceid' => $userevidence->id]); $DB->delete_records(user_evidence::TABLE, ['id' => $userevidence->id]); $fs->delete_area_files($context->id, 'core_competency', 'userevidence', $userevidence->id); } $userplans = $DB->get_records(plan::TABLE, ['userid' => $userid], '', 'id'); foreach ($userplans as $userplan) { $DB->delete_records(plan_competency::TABLE, ['planid' => $userplan->id]); $DB->delete_records(plan::TABLE, ['id' => $userplan->id]); } } /** * Manually grade a user competency. * * @param int $userid * @param int $competencyid * @param int $grade * @param string $note A note to attach to the evidence * @return array of \core_competency\user_competency */ public static function grade_competency($userid, $competencyid, $grade, $note = null) { global $USER; static::require_enabled(); $uc = static::get_user_competency($userid, $competencyid); $context = $uc->get_context(); if (!user_competency::can_grade_user($uc->get('userid'))) { throw new required_capability_exception($context, 'moodle/competency:competencygrade', 'nopermissions', ''); } // Throws exception if competency not in plan. $competency = $uc->get_competency(); $competencycontext = $competency->get_context(); if (!has_any_capability(array('moodle/competency:competencyview', 'moodle/competency:competencymanage'), $competencycontext)) { throw new required_capability_exception($competencycontext, 'moodle/competency:competencyview', 'nopermissions', ''); } $action = evidence::ACTION_OVERRIDE; $desckey = 'evidence_manualoverride'; $result = self::add_evidence($uc->get('userid'), $competency, $context->id, $action, $desckey, 'core_competency', null, false, null, $grade, $USER->id, $note); if ($result) { $uc->read(); $event = \core\event\competency_user_competency_rated::create_from_user_competency($uc); $event->trigger(); } return $result; } /** * Manually grade a user competency from the plans page. * * @param mixed $planorid * @param int $competencyid * @param int $grade * @param string $note A note to attach to the evidence * @return array of \core_competency\user_competency */ public static function grade_competency_in_plan($planorid, $competencyid, $grade, $note = null) { global $USER; static::require_enabled(); $plan = $planorid; if (!is_object($planorid)) { $plan = new plan($planorid); } $context = $plan->get_context(); if (!user_competency::can_grade_user($plan->get('userid'))) { throw new required_capability_exception($context, 'moodle/competency:competencygrade', 'nopermissions', ''); } // Throws exception if competency not in plan. $competency = $plan->get_competency($competencyid); $competencycontext = $competency->get_context(); if (!has_any_capability(array('moodle/competency:competencyview', 'moodle/competency:competencymanage'), $competencycontext)) { throw new required_capability_exception($competencycontext, 'moodle/competency:competencyview', 'nopermissions', ''); } $action = evidence::ACTION_OVERRIDE; $desckey = 'evidence_manualoverrideinplan'; $result = self::add_evidence($plan->get('userid'), $competency, $context->id, $action, $desckey, 'core_competency', $plan->get('name'), false, null, $grade, $USER->id, $note); if ($result) { $uc = static::get_user_competency($plan->get('userid'), $competency->get('id')); $event = \core\event\competency_user_competency_rated_in_plan::create_from_user_competency($uc, $plan->get('id')); $event->trigger(); } return $result; } /** * Manually grade a user course competency from the course page. * * This may push the rating to the user competency * if the course is configured this way. * * @param mixed $courseorid * @param int $userid * @param int $competencyid * @param int $grade * @param string $note A note to attach to the evidence * @return array of \core_competency\user_competency */ public static function grade_competency_in_course($courseorid, $userid, $competencyid, $grade, $note = null) { global $USER, $DB; static::require_enabled(); $course = $courseorid; if (!is_object($courseorid)) { $course = $DB->get_record('course', array('id' => $courseorid)); } $context = context_course::instance($course->id); // Check that we can view the user competency details in the course. if (!user_competency::can_read_user_in_course($userid, $course->id)) { throw new required_capability_exception($context, 'moodle/competency:usercompetencyview', 'nopermissions', ''); } // Validate the permission to grade. if (!user_competency::can_grade_user_in_course($userid, $course->id)) { throw new required_capability_exception($context, 'moodle/competency:competencygrade', 'nopermissions', ''); } // Check that competency is in course and visible to the current user. $competency = course_competency::get_competency($course->id, $competencyid); $competencycontext = $competency->get_context(); if (!has_any_capability(array('moodle/competency:competencyview', 'moodle/competency:competencymanage'), $competencycontext)) { throw new required_capability_exception($competencycontext, 'moodle/competency:competencyview', 'nopermissions', ''); } // Check that the user is enrolled in the course, and is "gradable". if (!is_enrolled($context, $userid, 'moodle/competency:coursecompetencygradable')) { throw new coding_exception('The competency may not be rated at this time.'); } $action = evidence::ACTION_OVERRIDE; $desckey = 'evidence_manualoverrideincourse'; $result = self::add_evidence($userid, $competency, $context->id, $action, $desckey, 'core_competency', $context->get_context_name(), false, null, $grade, $USER->id, $note); if ($result) { $all = user_competency_course::get_multiple($userid, $course->id, array($competency->get('id'))); $uc = reset($all); $event = \core\event\competency_user_competency_rated_in_course::create_from_user_competency_course($uc); $event->trigger(); } return $result; } /** * Count the plans in the template, filtered by status. * * Requires moodle/competency:templateview capability at the system context. * * @param mixed $templateorid The id or the template. * @param int $status One of the plan status constants (or 0 for all plans). * @return int */ public static function count_plans_for_template($templateorid, $status = 0) { static::require_enabled(); $template = $templateorid; if (!is_object($template)) { $template = new template($template); } // First we do a permissions check. if (!$template->can_read()) { throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview', 'nopermissions', ''); } return plan::count_records_for_template($template->get('id'), $status); } /** * Count the user-completency-plans in the template, optionally filtered by proficiency. * * Requires moodle/competency:templateview capability at the system context. * * @param mixed $templateorid The id or the template. * @param mixed $proficiency If true, filter by proficiency, if false filter by not proficient, if null - no filter. * @return int */ public static function count_user_competency_plans_for_template($templateorid, $proficiency = null) { static::require_enabled(); $template = $templateorid; if (!is_object($template)) { $template = new template($template); } // First we do a permissions check. if (!$template->can_read()) { throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview', 'nopermissions', ''); } return user_competency_plan::count_records_for_template($template->get('id'), $proficiency); } /** * List the plans in the template, filtered by status. * * Requires moodle/competency:templateview capability at the system context. * * @param mixed $templateorid The id or the template. * @param int $status One of the plan status constants (or 0 for all plans). * @param int $skip The number of records to skip * @param int $limit The max number of records to return * @return plan[] */ public static function list_plans_for_template($templateorid, $status = 0, $skip = 0, $limit = 100) { $template = $templateorid; if (!is_object($template)) { $template = new template($template); } // First we do a permissions check. if (!$template->can_read()) { throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview', 'nopermissions', ''); } return plan::get_records_for_template($template->get('id'), $status, $skip, $limit); } /** * Get the most often not completed competency for this course. * * Requires moodle/competency:coursecompetencyview capability at the course context. * * @param int $courseid The course id * @param int $skip The number of records to skip * @param int $limit The max number of records to return * @return competency[] */ public static function get_least_proficient_competencies_for_course($courseid, $skip = 0, $limit = 100) { static::require_enabled(); $coursecontext = context_course::instance($courseid); if (!has_any_capability(array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage'), $coursecontext)) { throw new required_capability_exception($coursecontext, 'moodle/competency:coursecompetencyview', 'nopermissions', ''); } return user_competency_course::get_least_proficient_competencies_for_course($courseid, $skip, $limit); } /** * Get the most often not completed competency for this template. * * Requires moodle/competency:templateview capability at the system context. * * @param mixed $templateorid The id or the template. * @param int $skip The number of records to skip * @param int $limit The max number of records to return * @return competency[] */ public static function get_least_proficient_competencies_for_template($templateorid, $skip = 0, $limit = 100) { static::require_enabled(); $template = $templateorid; if (!is_object($template)) { $template = new template($template); } // First we do a permissions check. if (!$template->can_read()) { throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview', 'nopermissions', ''); } return user_competency_plan::get_least_proficient_competencies_for_template($template->get('id'), $skip, $limit); } /** * Template event viewed. * * Requires moodle/competency:templateview capability at the system context. * * @param mixed $templateorid The id or the template. * @return boolean */ public static function template_viewed($templateorid) { static::require_enabled(); $template = $templateorid; if (!is_object($template)) { $template = new template($template); } // First we do a permissions check. if (!$template->can_read()) { throw new required_capability_exception($template->get_context(), 'moodle/competency:templateview', 'nopermissions', ''); } // Trigger a template viewed event. \core\event\competency_template_viewed::create_from_template($template)->trigger(); return true; } /** * Get the competency settings for a course. * * Requires moodle/competency:coursecompetencyview capability at the course context. * * @param int $courseid The course id * @return course_competency_settings */ public static function read_course_competency_settings($courseid) { static::require_enabled(); // First we do a permissions check. if (!course_competency_settings::can_read($courseid)) { $context = context_course::instance($courseid); throw new required_capability_exception($context, 'moodle/competency:coursecompetencyview', 'nopermissions', ''); } return course_competency_settings::get_by_courseid($courseid); } /** * Update the competency settings for a course. * * Requires moodle/competency:coursecompetencyconfigure capability at the course context. * * @param int $courseid The course id * @param stdClass $settings List of settings. The only valid setting ATM is pushratginstouserplans (boolean). * @return bool */ public static function update_course_competency_settings($courseid, $settings) { static::require_enabled(); $settings = (object) $settings; // Get all the valid settings. $pushratingstouserplans = isset($settings->pushratingstouserplans) ? $settings->pushratingstouserplans : false; // First we do a permissions check. if (!course_competency_settings::can_manage_course($courseid)) { $context = context_course::instance($courseid); throw new required_capability_exception($context, 'moodle/competency:coursecompetencyconfigure', 'nopermissions', ''); } $exists = course_competency_settings::get_record(array('courseid' => $courseid)); // Now update or insert. if ($exists) { $settings = $exists; $settings->set('pushratingstouserplans', $pushratingstouserplans); return $settings->update(); } else { $data = (object) array('courseid' => $courseid, 'pushratingstouserplans' => $pushratingstouserplans); $settings = new course_competency_settings(0, $data); $result = $settings->create(); return !empty($result); } } /** * Function used to return a list of users where the given user has a particular capability. * * This is used e.g. to find all the users where someone is able to manage their learning plans, * it also would be useful for mentees etc. * * @param string $capability - The capability string we are filtering for. If '' is passed, * an always matching filter is returned. * @param int $userid - The user id we are using for the access checks. Defaults to current user. * @param int $type - The type of named params to return (passed to $DB->get_in_or_equal). * @param string $prefix - The type prefix for the db table (passed to $DB->get_in_or_equal). * @return list($sql, $params) Same as $DB->get_in_or_equal(). * @todo MDL-52243 Move this function to lib/accesslib.php */ public static function filter_users_with_capability_on_user_context_sql($capability, $userid = 0, $type = SQL_PARAMS_QM, $prefix='param') { global $USER, $DB; $allresultsfilter = array('> 0', array()); $noresultsfilter = array('= -1', array()); if (empty($capability)) { return $allresultsfilter; } if (!$capinfo = get_capability_info($capability)) { throw new coding_exception('Capability does not exist: ' . $capability); } if (empty($userid)) { $userid = $USER->id; } // Make sure the guest account and not-logged-in users never get any risky caps no matter what the actual settings are. if (($capinfo->captype === 'write') or ($capinfo->riskbitmask & (RISK_XSS | RISK_CONFIG | RISK_DATALOSS))) { if (isguestuser($userid) or $userid == 0) { return $noresultsfilter; } } if (is_siteadmin($userid)) { // No filtering for site admins. return $allresultsfilter; } // Check capability on system level. $syscontext = context_system::instance(); $hassystem = has_capability($capability, $syscontext, $userid); $access = get_user_roles_sitewide_accessdata($userid); // Build up a list of level 2 contexts (candidates to be user context). $filtercontexts = array(); // Build list of roles to check overrides. $roles = array(); foreach ($access['ra'] as $path => $role) { $parts = explode('/', $path); if (count($parts) == 3) { $filtercontexts[$parts[2]] = $parts[2]; } else if (count($parts) > 3) { // We know this is not a user context because there is another path with more than 2 levels. unset($filtercontexts[$parts[2]]); } $roles = array_merge($roles, $role); } // Add all contexts in which a role may be overidden. $rdefs = get_role_definitions($roles); foreach ($rdefs as $roledef) { foreach ($roledef as $path => $caps) { if (!isset($caps[$capability])) { // The capability is not mentioned, we can ignore. continue; } $parts = explode('/', $path); if (count($parts) === 3) { // Only get potential user contexts, they only ever have 2 slashes /parentId/Id. $filtercontexts[$parts[2]] = $parts[2]; } } } // No interesting contexts - return all or no results. if (empty($filtercontexts)) { if ($hassystem) { return $allresultsfilter; } else { return $noresultsfilter; } } // Fetch all interesting contexts for further examination. list($insql, $params) = $DB->get_in_or_equal($filtercontexts, SQL_PARAMS_NAMED); $params['level'] = CONTEXT_USER; $fields = context_helper::get_preload_record_columns_sql('ctx'); $interestingcontexts = $DB->get_recordset_sql('SELECT ' . $fields . ' FROM {context} ctx WHERE ctx.contextlevel = :level AND ctx.id ' . $insql . ' ORDER BY ctx.id', $params); if ($hassystem) { // If allowed at system, search for exceptions prohibiting the capability at user context. $excludeusers = array(); foreach ($interestingcontexts as $contextrecord) { $candidateuserid = $contextrecord->ctxinstance; context_helper::preload_from_record($contextrecord); $usercontext = context_user::instance($candidateuserid); // Has capability should use the data already preloaded. if (!has_capability($capability, $usercontext, $userid)) { $excludeusers[$candidateuserid] = $candidateuserid; } } // Construct SQL excluding users with this role assigned for this user. if (empty($excludeusers)) { $interestingcontexts->close(); return $allresultsfilter; } list($sql, $params) = $DB->get_in_or_equal($excludeusers, $type, $prefix, false); } else { // If not allowed at system, search for exceptions allowing the capability at user context. $allowusers = array(); foreach ($interestingcontexts as $contextrecord) { $candidateuserid = $contextrecord->ctxinstance; context_helper::preload_from_record($contextrecord); $usercontext = context_user::instance($candidateuserid); // Has capability should use the data already preloaded. if (has_capability($capability, $usercontext, $userid)) { $allowusers[$candidateuserid] = $candidateuserid; } } // Construct SQL excluding users with this role assigned for this user. if (empty($allowusers)) { $interestingcontexts->close(); return $noresultsfilter; } list($sql, $params) = $DB->get_in_or_equal($allowusers, $type, $prefix); } $interestingcontexts->close(); // Return the goods!. return array($sql, $params); } } classes/user_competency_plan.php000064400000027367152265256010013153 0ustar00. /** * Class for user_competency_plan persistence. * * @package core_competency * @copyright 2015 Serge Gauthier * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_competency; defined('MOODLE_INTERNAL') || die(); use lang_string; use context_user; /** * Class for loading/storing user_competency_plan from the DB. * * @copyright 2015 Serge Gauthier * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class user_competency_plan extends persistent { /** Table name for user_competency_plan persistency */ const TABLE = 'competency_usercompplan'; /** * Return the definition of the properties of this model. * * @return array */ protected static function define_properties() { return array( 'userid' => array( 'type' => PARAM_INT, ), 'competencyid' => array( 'type' => PARAM_INT, ), 'proficiency' => array( 'type' => PARAM_BOOL, 'default' => null, 'null' => NULL_ALLOWED, ), 'grade' => array( 'type' => PARAM_INT, 'default' => null, 'null' => NULL_ALLOWED, ), 'planid' => array( 'type' => PARAM_INT, ), 'sortorder' => array( 'type' => PARAM_INT, 'default' => 0, ), ); } /** * Return the competency Object. * * @return competency Competency Object */ public function get_competency() { return new competency($this->get('competencyid')); } /** * Get the context. * * @return context The context. */ public function get_context() { return context_user::instance($this->get('userid')); } /** * Validate the user ID. * * @param int $value The value. * @return true|lang_string */ protected function validate_userid($value) { global $DB; if (!$DB->record_exists('user', array('id' => $value))) { return new lang_string('invaliduserid', 'error'); } return true; } /** * Validate the competency ID. * * @param int $value The value. * @return true|lang_string */ protected function validate_competencyid($value) { if (!competency::record_exists($value)) { return new lang_string('errornocompetency', 'core_competency', $value); } return true; } /** * Validate the grade. * * @param int $value The value. * @return true|lang_string */ protected function validate_grade($value) { if ($value !== null) { if ($value <= 0) { return new lang_string('invalidgrade', 'core_competency'); } // TODO MDL-52243 Use a core method to validate the grade_scale item. // Check if grade exist in the scale item values. $competency = $this->get_competency(); if (!array_key_exists($value - 1, $competency->get_scale()->scale_items)) { return new lang_string('invalidgrade', 'core_competency'); } } return true; } /** * Validate the plan ID. * * @param int $value The value. * @return true|lang_string */ protected function validate_planid($value) { if (!plan::record_exists($value) ) { return new lang_string('invalidplan', 'core_competency'); } return true; } /** * Create a new user_competency_plan object. * * Note, this is intended to be used to create a blank relation, for instance when * the record was not found in the database. This does not save the model. * * @param int $userid The user ID. * @param int $competencyid The competency ID. * @param int $planid The plan ID. * @return \core_competency\user_competency_plan */ public static function create_relation($userid, $competencyid, $planid) { $relation = new user_competency_plan(0, (object) array('userid' => $userid, 'competencyid' => $competencyid, 'planid' => $planid)); return $relation; } /** * List the competencies in this plan. * * @param int $planid The plan ID * @param int $userid The user ID * @return competency[] */ public static function list_competencies($planid, $userid) { global $DB; $sql = 'SELECT c.* FROM {' . competency::TABLE . '} c JOIN {' . self::TABLE . '} ucp ON ucp.competencyid = c.id AND ucp.userid = :userid WHERE ucp.planid = :planid ORDER BY ucp.sortorder ASC, ucp.id ASC'; $params = array('userid' => $userid, 'planid' => $planid); $results = $DB->get_recordset_sql($sql, $params); $instances = array(); foreach ($results as $key => $result) { $instances[$key] = new competency(0, $result); } $results->close(); return $instances; } /** * Fetch a competency by plan ID. * * @param int $id The plan ID. * @return competency */ public static function get_competency_by_planid($planid, $competencyid) { global $DB; $sql = "SELECT c.* FROM {" . self::TABLE . "} ucp JOIN {" . competency::TABLE . "} c ON c.id = ucp.competencyid WHERE ucp.planid = ? AND ucp.competencyid = ?"; $record = $DB->get_record_sql($sql, array($planid, $competencyid)); if (!$record) { throw new \coding_exception('The competency does not belong to this plan: ' . $competencyid . ', ' . $planid); } return new competency(0, $record); } /** * Get multiple user_competency_plan for a user. * * @param int $userid The user ID. * @param int $planid The plan ID. * @param array $competenciesorids Limit search to those competencies, or competency IDs. * @return \core_competency\user_competency_plan[] */ public static function get_multiple($userid, $planid, ?array $competenciesorids = null) { global $DB; $params = array(); $params['userid'] = $userid; $params['planid'] = $planid; $sql = '1 = 1'; if (!empty($competenciesorids)) { $test = reset($competenciesorids); if (is_number($test)) { $ids = $competenciesorids; } else { $ids = array(); foreach ($competenciesorids as $comp) { $ids[] = $comp->get('id'); } } list($insql, $inparams) = $DB->get_in_or_equal($ids, SQL_PARAMS_NAMED); $params += $inparams; $sql = "competencyid $insql"; } // Order by ID to prevent random ordering. return static::get_records_select("userid = :userid AND planid = :planid AND $sql", $params, 'id ASC'); } /** * Checks if a competency has user competency plan records. * * @param int $competencyid The competency ID * @return boolean */ public static function has_records_for_competency($competencyid) { return self::record_exists_select('competencyid = ?', array($competencyid)); } /** * Checks if any of the competencies of a framework has a user competency plan record. * * @param int $frameworkid The competency framework ID. * @return boolean */ public static function has_records_for_framework($frameworkid) { global $DB; $sql = "SELECT 'x' FROM {" . self::TABLE . "} ucp JOIN {" . competency::TABLE . "} c ON ucp.competencyid = c.id WHERE c.competencyframeworkid = ?"; $params = array($frameworkid); return $DB->record_exists_sql($sql, $params); } /** * Check if user competency plan has records for competencies. * * @param array $competencyids The competences IDs * @return boolean */ public static function has_records_for_competencies($competencyids) { global $DB; list($insql, $params) = $DB->get_in_or_equal($competencyids, SQL_PARAMS_NAMED); return self::record_exists_select("competencyid $insql", $params); } /** * Count the number of records matching a specific template, optionally filtered by proficient values. * * @param int $templateid * @param mixed $proficiency - If true - filter by proficiency, if false filter by not proficient, if null - do not filter. * @return int */ public static function count_records_for_template($templateid, $proficiency=null) { global $DB; $params = array('templateid' => $templateid); $sql = 'SELECT ' . " COUNT('x') " . 'FROM {' . self::TABLE . '} ucp JOIN {' . plan::TABLE . '} p ON ucp.planid = p.id WHERE p.templateid = :templateid'; if ($proficiency === true) { $sql .= ' AND ucp.proficiency = :proficiency'; $params['proficiency'] = true; } else if ($proficiency === false) { $sql .= ' AND (ucp.proficiency = :proficiency OR ucp.proficiency IS NULL)'; $params['proficiency'] = false; } return $DB->count_records_sql($sql, $params); } /** * Get the list of competencies that were completed the least times (in completed plans) from a template. * * @param int $templateid * @param int $skip The number of competencies to skip * @param int $limit The max number of competencies to return * @return competency[] */ public static function get_least_proficient_competencies_for_template($templateid, $skip = 0, $limit = 0) { global $DB; $fields = competency::get_sql_fields('c', 'c_'); $params = array('templateid' => $templateid, 'notproficient' => false); $sql = 'SELECT ' . $fields . ' FROM (SELECT ucp.competencyid, COUNT(ucp.competencyid) AS timesnotproficient FROM {' . self::TABLE . '} ucp JOIN {' . plan::TABLE . '} p ON p.id = ucp.planid WHERE p.templateid = :templateid AND (ucp.proficiency = :notproficient OR ucp.proficiency IS NULL) GROUP BY ucp.competencyid ) p JOIN {' . competency::TABLE . '} c ON c.id = p.competencyid ORDER BY p.timesnotproficient DESC, c.id ASC'; $results = $DB->get_records_sql($sql, $params, $skip, $limit); $comps = array(); foreach ($results as $r) { $c = competency::extract_record($r, 'c_'); $comps[] = new competency(0, $c); } return $comps; } } classes/external/user_competency_plan_exporter.php000064400000005421152265256010016710 0ustar00. /** * Class for exporting plan competency data. * * @package core_competency * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_competency\external; defined('MOODLE_INTERNAL') || die(); use context_system; use renderer_base; use stdClass; /** * Class for exporting plan competency data. * * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class user_competency_plan_exporter extends \core\external\persistent_exporter { protected static function define_class() { return \core_competency\user_competency_plan::class; } protected static function define_related() { // We cache the scale so it does not need to be retrieved from the framework every time. return array('scale' => 'grade_scale'); } protected function get_other_values(renderer_base $output) { $result = new stdClass(); if ($this->persistent->get('grade') === null) { $gradename = '-'; } else { $gradename = $this->related['scale']->scale_items[$this->persistent->get('grade') - 1]; } $result->gradename = $gradename; if ($this->persistent->get('proficiency') === null) { $proficiencyname = get_string('no'); } else { $proficiencyname = get_string($this->persistent->get('proficiency') ? 'yes' : 'no'); } $result->proficiencyname = $proficiencyname; return (array) $result; } /** * Get the format parameters for gradename. * * @return array */ protected function get_format_parameters_for_gradename() { return [ 'context' => context_system::instance(), // The system context is cached, so we can get it right away. ]; } protected static function define_other_properties() { return array( 'gradename' => array( 'type' => PARAM_TEXT ), 'proficiencyname' => array( 'type' => PARAM_RAW ), ); } } classes/external/performance_helper.php000064400000010373152265256010014404 0ustar00. /** * Performance helper. * * @package core_competency * @copyright 2016 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_competency\external; defined('MOODLE_INTERNAL') || die(); use core_competency\competency; use core_competency\competency_framework; /** * Performance helper class. * * This tool keeps a local cache of certain items, which means that subsequent * calls to get the resource will not query the database. You will want to use * this when many resources could be shared and need to be queried in a loop. * * Note that some of these improvements can only be achieved by knowing the * logic deeper in other modules. For instance we know that a competency's context * is the one of its framework. This tool must be kept in sync with those APIs. * * @package core_competency * @copyright 2016 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class performance_helper { /** @var \context Cache of contexts by framework ID. */ protected $frameworkscontexts = []; /** @var competency_framework Cache of frameworks by framework ID. */ protected $frameworks = []; /** @var \grade_scale[] Cache of scales by scale ID. */ protected $scales = []; /** * Get the context of a competency. * * @param competency $competency The competency. * @return \context */ public function get_context_from_competency(competency $competency) { $frameworkid = $competency->get('competencyframeworkid'); if (!isset($this->frameworkscontexts[$frameworkid])) { $framework = $this->get_framework_from_competency($competency); $this->frameworkscontexts[$frameworkid] = $framework->get_context(); } return $this->frameworkscontexts[$frameworkid]; } /** * Get the framework of a competency. * * @param competency $competency The competency. * @return competency_framework */ public function get_framework_from_competency(competency $competency) { $frameworkid = $competency->get('competencyframeworkid'); if (!isset($this->frameworks[$frameworkid])) { $this->frameworks[$frameworkid] = $competency->get_framework(); } return $this->frameworks[$frameworkid]; } /** * Get the scale of a competency. * * /!\ Make sure that this is always kept in sync with: * - core_competency\competency::get_scale() * - core_competency\competency_framework::get_scale() * * @param competency $competency The competency. * @return \grade_scale */ public function get_scale_from_competency(competency $competency) { $scaleid = $competency->get('scaleid'); if ($scaleid !== null && !isset($this->scales[$scaleid])) { $this->scales[$scaleid] = $competency->get_scale(); } else if ($scaleid === null) { $framework = $this->get_framework_from_competency($competency); $scaleid = $framework->get('scaleid'); if (!isset($this->scales[$scaleid])) { $this->scales[$scaleid] = $framework->get_scale(); } } return $this->scales[$scaleid]; } /** * Ingest a framework to avoid additional fetching. * * @param competency_framework $framework The framework. * @return void */ public function ingest_framework(competency_framework $framework) { $id = $framework->get('id'); $this->frameworks[$id] = $framework; } } classes/external/user_competency_course_exporter.php000064400000005442152265256010017261 0ustar00. /** * Class for exporting user competency course data. * * @package core_competency * @copyright 2016 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_competency\external; defined('MOODLE_INTERNAL') || die(); use context_system; use renderer_base; use stdClass; /** * Class for exporting user competency course data. * * @copyright 2016 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class user_competency_course_exporter extends \core\external\persistent_exporter { protected static function define_class() { return \core_competency\user_competency_course::class; } protected static function define_related() { // We cache the scale so it does not need to be retrieved from the framework every time. return array('scale' => 'grade_scale'); } protected function get_other_values(renderer_base $output) { $result = new stdClass(); if ($this->persistent->get('grade') === null) { $gradename = '-'; } else { $gradename = $this->related['scale']->scale_items[$this->persistent->get('grade') - 1]; } $result->gradename = $gradename; if ($this->persistent->get('proficiency') === null) { $proficiencyname = get_string('no'); } else { $proficiencyname = get_string($this->persistent->get('proficiency') ? 'yes' : 'no'); } $result->proficiencyname = $proficiencyname; return (array) $result; } /** * Get the format parameters for gradename. * * @return array */ protected function get_format_parameters_for_gradename() { return [ 'context' => context_system::instance(), // The system context is cached, so we can get it right away. ]; } protected static function define_other_properties() { return array( 'gradename' => array( 'type' => PARAM_TEXT ), 'proficiencyname' => array( 'type' => PARAM_RAW ) ); } } classes/external/competency_exporter.php000064400000002720152265256010014637 0ustar00. /** * Class for exporting competency data. * * @package core_competency * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_competency\external; defined('MOODLE_INTERNAL') || die(); /** * Class for exporting competency data. * * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class competency_exporter extends \core\external\persistent_exporter { protected static function define_class() { return \core_competency\competency::class; } protected static function define_related() { // We cache the context so it does not need to be retrieved from the framework every time. return array('context' => '\\context'); } } classes/external/user_evidence_competency_exporter.php000064400000002552152265256010017542 0ustar00. /** * User evidence competency exporter. * * @package core_competency * @copyright 2015 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_competency\external; defined('MOODLE_INTERNAL') || die(); /** * User evidence competency exporter class. * * @package core_competency * @copyright 2015 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class user_evidence_competency_exporter extends \core\external\persistent_exporter { protected static function define_class() { return \core_competency\user_evidence_competency::class; } } classes/external/course_competency_exporter.php000064400000002441152265256010016217 0ustar00. /** * Class for exporting course competency data. * * @package core_competency * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_competency\external; defined('MOODLE_INTERNAL') || die(); /** * Class for exporting course competency data. * * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class course_competency_exporter extends \core\external\persistent_exporter { protected static function define_class() { return \core_competency\course_competency::class; } } classes/external/plan_competency_exporter.php000064400000002431152265256010015650 0ustar00. /** * Class for exporting plan competency data. * * @package core_competency * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_competency\external; defined('MOODLE_INTERNAL') || die(); /** * Class for exporting plan competency data. * * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class plan_competency_exporter extends \core\external\persistent_exporter { protected static function define_class() { return \core_competency\plan_competency::class; } } classes/external/template_competency_exporter.php000064400000002451152265256010016533 0ustar00. /** * Class for exporting template competency data. * * @package core_competency * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_competency\external; defined('MOODLE_INTERNAL') || die(); /** * Class for exporting template competency data. * * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class template_competency_exporter extends \core\external\persistent_exporter { protected static function define_class() { return \core_competency\template_competency::class; } } classes/external/plan_exporter.php000064400000015754152265256010013436 0ustar00. /** * Class for exporting plan data. * * @package core_competency * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_competency\external; defined('MOODLE_INTERNAL') || die(); use core_user; use renderer_base; use stdClass; use moodle_url; use core_competency\url; use core_comment\external\comment_area_exporter; use core_user\external\user_summary_exporter; /** * Class for exporting plan data. * * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class plan_exporter extends \core\external\persistent_exporter { protected static function define_class() { return \core_competency\plan::class; } protected static function define_related() { return array('template' => 'core_competency\\template?'); } protected function get_other_values(renderer_base $output) { $classname = static::define_class(); $status = $this->persistent->get('status'); $values = new stdClass(); $values->statusname = $this->persistent->get_statusname(); $values->isbasedontemplate = $this->persistent->is_based_on_template(); $values->canmanage = $this->persistent->can_manage(); $values->canrequestreview = $this->persistent->can_request_review(); $values->canreview = $this->persistent->can_review(); $values->canbeedited = $this->persistent->can_be_edited(); $values->isactive = $status == $classname::STATUS_ACTIVE; $values->isdraft = $status == $classname::STATUS_DRAFT; $values->iscompleted = $status == $classname::STATUS_COMPLETE; $values->isinreview = $status == $classname::STATUS_IN_REVIEW; $values->iswaitingforreview = $status == $classname::STATUS_WAITING_FOR_REVIEW; $values->isreopenallowed = $values->canmanage && $values->iscompleted; $values->iscompleteallowed = $values->canmanage && $values->isactive; $values->isunlinkallowed = $values->canmanage && !$values->iscompleted && $values->isbasedontemplate; $values->isrequestreviewallowed = false; $values->iscancelreviewrequestallowed = false; $values->isstartreviewallowed = false; $values->isstopreviewallowed = false; $values->isapproveallowed = false; $values->isunapproveallowed = false; if (!$values->isbasedontemplate) { $values->isrequestreviewallowed = $values->canrequestreview && $values->isdraft; $values->iscancelreviewrequestallowed = $values->canrequestreview && $values->iswaitingforreview; $values->isstartreviewallowed = $values->canreview && $values->iswaitingforreview; $values->isstopreviewallowed = $values->canreview && $values->isinreview; $values->isapproveallowed = $values->canreview && !$values->iscompleted && !$values->isactive; $values->isunapproveallowed = $values->canreview && $values->isactive; } $values->duedateformatted = userdate($this->persistent->get('duedate')); if ($this->persistent->is_based_on_template()) { $exporter = new template_exporter($this->related['template']); $values->template = $exporter->export($output); } if (!empty($values->isinreview)) { // TODO Make this more efficient. $userexporter = new user_summary_exporter(core_user::get_user($this->persistent->get('reviewerid'), '*', MUST_EXIST)); $values->reviewer = $userexporter->export($output); } $commentareaexporter = new comment_area_exporter($this->persistent->get_comment_object()); $values->commentarea = $commentareaexporter->export($output); $values->url = url::plan($this->persistent->get('id'))->out(false); return (array) $values; } public static function define_other_properties() { return array( 'statusname' => array( 'type' => PARAM_RAW, ), 'isbasedontemplate' => array( 'type' => PARAM_BOOL, ), 'canmanage' => array( 'type' => PARAM_BOOL, ), 'canrequestreview' => array( 'type' => PARAM_BOOL, ), 'canreview' => array( 'type' => PARAM_BOOL, ), 'canbeedited' => array( 'type' => PARAM_BOOL, ), 'isactive' => array( 'type' => PARAM_BOOL ), 'isdraft' => array( 'type' => PARAM_BOOL ), 'iscompleted' => array( 'type' => PARAM_BOOL ), 'isinreview' => array( 'type' => PARAM_BOOL ), 'iswaitingforreview' => array( 'type' => PARAM_BOOL ), 'isreopenallowed' => array( 'type' => PARAM_BOOL ), 'iscompleteallowed' => array( 'type' => PARAM_BOOL ), 'isunlinkallowed' => array( 'type' => PARAM_BOOL ), 'isrequestreviewallowed' => array( 'type' => PARAM_BOOL ), 'iscancelreviewrequestallowed' => array( 'type' => PARAM_BOOL ), 'isstartreviewallowed' => array( 'type' => PARAM_BOOL ), 'isstopreviewallowed' => array( 'type' => PARAM_BOOL ), 'isapproveallowed' => array( 'type' => PARAM_BOOL ), 'isunapproveallowed' => array( 'type' => PARAM_BOOL ), 'duedateformatted' => array( 'type' => PARAM_TEXT ), 'commentarea' => array( 'type' => comment_area_exporter::read_properties_definition(), ), 'reviewer' => array( 'type' => user_summary_exporter::read_properties_definition(), 'optional' => true ), 'template' => array( 'type' => template_exporter::read_properties_definition(), 'optional' => true, ), 'url' => array( 'type' => PARAM_URL ) ); } } classes/external/evidence_exporter.php000064400000011355152265256010014257 0ustar00. /** * Class for exporting evidence data. * * @package core_competency * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_competency\external; defined('MOODLE_INTERNAL') || die(); use context_system; use renderer_base; use core_competency\evidence; use core_competency\user_competency; use core_user\external\user_summary_exporter; /** * Class for exporting evidence data. * * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class evidence_exporter extends \core\external\persistent_exporter { /** * Constructor. * * @param mixed $data The data. * @param array $related Array of relateds. */ public function __construct($data, $related = array()) { if (!isset($related['context'])) { // Previous code was automatically using the system context which was not correct. // We let developers know that they must fix their code without breaking anything, and // fallback on the previous behaviour. This should be removed at a later stage: Moodle 3.5. debugging('Missing related context in evidence_exporter.', DEBUG_DEVELOPER); $related['context'] = context_system::instance(); } parent::__construct($data, $related); } protected static function define_related() { return array( 'actionuser' => 'stdClass?', 'context' => 'context', 'scale' => 'grade_scale', 'usercompetency' => 'core_competency\\user_competency?', 'usercompetencyplan' => 'core_competency\\user_competency_plan?', ); } protected static function define_class() { return evidence::class; } protected function get_other_values(renderer_base $output) { $other = array(); if (!empty($this->related['actionuser'])) { $exporter = new user_summary_exporter($this->related['actionuser']); $actionuser = $exporter->export($output); $other['actionuser'] = $actionuser; } $other['description'] = $this->persistent->get_description(); $other['userdate'] = userdate($this->persistent->get('timecreated')); if ($this->persistent->get('grade') === null) { $gradename = '-'; } else { $gradename = $this->related['scale']->scale_items[$this->persistent->get('grade') - 1]; } $other['gradename'] = $gradename; // Try to guess the user from the user competency. $userid = null; if ($this->related['usercompetency']) { $userid = $this->related['usercompetency']->get('userid'); } else if ($this->related['usercompetencyplan']) { $userid = $this->related['usercompetencyplan']->get('userid'); } else { $uc = user_competency::get_record(['id' => $this->persistent->get('usercompetencyid')]); $userid = $uc->get('userid'); } $other['candelete'] = evidence::can_delete_user($userid); return $other; } /** * Get the format parameters for gradename. * * @return array */ protected function get_format_parameters_for_gradename() { return [ 'context' => context_system::instance(), // The system context is cached, so we can get it right away. ]; } public static function define_other_properties() { return array( 'actionuser' => array( 'type' => user_summary_exporter::read_properties_definition(), 'optional' => true ), 'description' => array( 'type' => PARAM_TEXT, // The description may contain course names, etc.. which may need filtering. ), 'gradename' => array( 'type' => PARAM_TEXT, ), 'userdate' => array( 'type' => PARAM_NOTAGS ), 'candelete' => array( 'type' => PARAM_BOOL ) ); } } classes/external/related_competency_exporter.php000064400000002427152265256010016343 0ustar00. /** * Class for exporting plan data. * * @package core_competency * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_competency\external; defined('MOODLE_INTERNAL') || die(); /** * Class for exporting related competency data. * * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class related_competency_exporter extends \core\external\persistent_exporter { protected static function define_class() { return \core_competency\related_competency::class; } } classes/external/user_evidence_exporter.php000064400000007734152265256010015323 0ustar00. /** * Class for exporting user_evidence data. * * @package core_competency * @copyright 2015 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_competency\external; defined('MOODLE_INTERNAL') || die(); use moodle_url; use renderer_base; use core_competency\external\performance_helper; use core_files\external\stored_file_exporter; /** * Class for exporting user_evidence data. * * @package core_competency * @copyright 2015 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class user_evidence_exporter extends \core\external\persistent_exporter { protected static function define_class() { return \core_competency\user_evidence::class; } protected static function define_other_properties() { return array( 'canmanage' => array( 'type' => PARAM_BOOL ), 'competencycount' => array( 'type' => PARAM_INT ), 'competencies' => array( 'type' => competency_exporter::read_properties_definition(), 'multiple' => true ), 'filecount' => array( 'type' => PARAM_INT ), 'files' => array( 'type' => stored_file_exporter::read_properties_definition(), 'multiple' => true ), 'hasurlorfiles' => array( 'type' => PARAM_BOOL ), 'urlshort' => array( 'type' => PARAM_TEXT ), ); } protected static function define_related() { return array( 'context' => 'context', 'competencies' => 'core_competency\\competency[]' ); } protected function get_other_values(renderer_base $output) { $helper = new performance_helper(); $competencies = array(); foreach ($this->related['competencies'] as $competency) { $context = $helper->get_context_from_competency($competency); $compexporter = new competency_exporter($competency, array('context' => $context)); $competencies[] = $compexporter->export($output); } $urlshort = ''; $url = $this->persistent->get('url'); if (!empty($url)) { $murl = new moodle_url($url); $shorturl = preg_replace('@^https?://(www\.)?@', '', $murl->out(false)); $urlshort = shorten_text($shorturl, 30, true); } $files = array(); $storedfiles = $this->persistent->get_files(); if (!empty($storedfiles)) { foreach ($storedfiles as $storedfile) { $fileexporter = new stored_file_exporter($storedfile, array('context' => $this->related['context'])); $files[] = $fileexporter->export($output); } } $values = array( 'canmanage' => $this->persistent->can_manage(), 'competencycount' => count($competencies), 'competencies' => $competencies, 'filecount' => count($files), 'files' => $files, 'hasurlorfiles' => !empty($files) || !empty($url), 'urlshort' => $urlshort ); return $values; } } classes/external/course_competency_settings_exporter.php000064400000002545152265256010020144 0ustar00. /** * Class for exporting course_competency_settings data. * * @package core_competency * @copyright 2016 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_competency\external; defined('MOODLE_INTERNAL') || die(); /** * Class for exporting course_competency_settings data. * * @package core_competency * @copyright 2016 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class course_competency_settings_exporter extends \core\external\persistent_exporter { protected static function define_class() { return \core_competency\course_competency_settings::class; } } classes/external/competency_framework_exporter.php000064400000006027152265256010016720 0ustar00. /** * Class for exporting competency_framework data. * * @package core_competency * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_competency\external; defined('MOODLE_INTERNAL') || die(); use core_competency\api; use renderer_base; /** * Class for exporting competency_framework data. * * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class competency_framework_exporter extends \core\external\persistent_exporter { /** * Define the name of persistent class. * * @return string */ protected static function define_class() { return \core_competency\competency_framework::class; } /** * Get other values that do not belong to the basic persisent. * * @param renderer_base $output * @return Array */ protected function get_other_values(renderer_base $output) { $filters = array('competencyframeworkid' => $this->persistent->get('id')); $context = $this->persistent->get_context(); $competenciescount = 0; try { $competenciescount = api::count_competencies($filters); } catch (\required_capability_exception $re) { $competenciescount = 0; } return array( 'canmanage' => has_capability('moodle/competency:competencymanage', $context), 'competenciescount' => $competenciescount, 'contextname' => $context->get_context_name(), 'contextnamenoprefix' => $context->get_context_name(false) ); } /** * Define other properties that do not belong to the basic persisent. * * @return Array */ protected static function define_other_properties() { return array( 'canmanage' => array( 'type' => PARAM_BOOL ), 'competenciescount' => array( 'type' => PARAM_INT ), // Both contexts need to be PARAM_RAW because the method context::get_context_name() // already applies the formatting and thus could return HTML content. 'contextname' => array( 'type' => PARAM_RAW ), 'contextnamenoprefix' => array( 'type' => PARAM_RAW ) ); } } classes/external/user_competency_exporter.php000064400000013007152265256010015675 0ustar00. /** * Class for exporting user competency data. * * @package core_competency * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_competency\external; defined('MOODLE_INTERNAL') || die(); use context_system; use core_user; use renderer_base; use stdClass; use core_competency\url; use core_competency\user_competency; use core_user\external\user_summary_exporter; /** * Class for exporting user competency data. * * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class user_competency_exporter extends \core\external\persistent_exporter { protected static function define_class() { return user_competency::class; } protected static function define_related() { // We cache the scale so it does not need to be retrieved from the framework every time. return array('scale' => 'grade_scale'); } protected function get_other_values(renderer_base $output) { $result = new stdClass(); if ($this->persistent->get('grade') === null) { $gradename = '-'; } else { $gradename = $this->related['scale']->scale_items[$this->persistent->get('grade') - 1]; } $result->gradename = $gradename; if ($this->persistent->get('proficiency') === null) { $proficiencyname = get_string('no'); } else { $proficiencyname = get_string($this->persistent->get('proficiency') ? 'yes' : 'no'); } $result->proficiencyname = $proficiencyname; $statusname = '-'; if ($this->persistent->get('status') != user_competency::STATUS_IDLE) { $statusname = (string) user_competency::get_status_name($this->persistent->get('status')); } $result->statusname = $statusname; $result->canrequestreview = $this->persistent->can_request_review(); $result->canreview = $this->persistent->can_review(); $result->isstatusidle = $this->persistent->get('status') == user_competency::STATUS_IDLE; $result->isstatusinreview = $this->persistent->get('status') == user_competency::STATUS_IN_REVIEW; $result->isstatuswaitingforreview = $this->persistent->get('status') == user_competency::STATUS_WAITING_FOR_REVIEW; $result->isrequestreviewallowed = $result->canrequestreview && $result->isstatusidle; $result->iscancelreviewrequestallowed = $result->canrequestreview && $result->isstatuswaitingforreview; $result->isstartreviewallowed = $result->canreview && $result->isstatuswaitingforreview; $result->isstopreviewallowed = $result->canreview && $result->isstatusinreview; if (!empty($result->isstatusinreview)) { // TODO Make this more efficient. $userexporter = new user_summary_exporter(core_user::get_user($this->persistent->get('reviewerid'), '*', MUST_EXIST)); $result->reviewer = $userexporter->export($output); } $result->url = url::user_competency($this->persistent->get('id'))->out(false); return (array) $result; } /** * Get the format parameters for gradename. * * @return array */ protected function get_format_parameters_for_gradename() { return [ 'context' => context_system::instance(), // The system context is cached, so we can get it right away. ]; } protected static function define_other_properties() { return array( 'canrequestreview' => array( 'type' => PARAM_BOOL, ), 'canreview' => array( 'type' => PARAM_BOOL, ), 'gradename' => array( 'type' => PARAM_TEXT ), 'isrequestreviewallowed' => array( 'type' => PARAM_BOOL, ), 'iscancelreviewrequestallowed' => array( 'type' => PARAM_BOOL, ), 'isstartreviewallowed' => array( 'type' => PARAM_BOOL, ), 'isstopreviewallowed' => array( 'type' => PARAM_BOOL, ), 'isstatusidle' => array( 'type' => PARAM_BOOL, ), 'isstatusinreview' => array( 'type' => PARAM_BOOL, ), 'isstatuswaitingforreview' => array( 'type' => PARAM_BOOL, ), 'proficiencyname' => array( 'type' => PARAM_RAW ), 'reviewer' => array( 'type' => user_summary_exporter::read_properties_definition(), 'optional' => true ), 'statusname' => array( 'type' => PARAM_RAW ), 'url' => array( 'type' => PARAM_URL ), ); } } classes/external/course_module_competency_exporter.php000064400000002475152265256010017573 0ustar00. /** * Class for exporting course module competency data. * * @package core_competency * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_competency\external; defined('MOODLE_INTERNAL') || die(); /** * Class for exporting course module competency data. * * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class course_module_competency_exporter extends \core\external\persistent_exporter { protected static function define_class() { return \core_competency\course_module_competency::class; } } classes/external/template_exporter.php000064400000005353152265256010014311 0ustar00. /** * Class for exporting template data. * * @package core_competency * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_competency\external; defined('MOODLE_INTERNAL') || die(); use moodle_url; use renderer_base; use core_competency\plan; use core_competency\template_cohort; /** * Class for exporting template data. * * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class template_exporter extends \core\external\persistent_exporter { protected static function define_class() { return \core_competency\template::class; } protected function get_other_values(renderer_base $output) { $context = $this->persistent->get_context(); return array( 'duedateformatted' => userdate($this->persistent->get('duedate')), 'cohortscount' => template_cohort::count_records(array('templateid' => $this->persistent->get('id'))), 'planscount' => plan::count_records(array('templateid' => $this->persistent->get('id'))), 'canmanage' => $this->persistent->can_manage(), 'canread' => $this->persistent->can_read(), 'contextname' => $context->get_context_name(), 'contextnamenoprefix' => $context->get_context_name(false) ); } protected static function define_other_properties() { return array( 'duedateformatted' => array( 'type' => PARAM_RAW ), 'cohortscount' => array( 'type' => PARAM_INT ), 'planscount' => array( 'type' => PARAM_INT ), 'canmanage' => array( 'type' => PARAM_BOOL ), 'canread' => array( 'type' => PARAM_BOOL ), 'contextname' => array( 'type' => PARAM_TEXT, ), 'contextnamenoprefix' => array( 'type' => PARAM_TEXT, ) ); } } classes/privacy/provider.php000064400000302545152265256010012236 0ustar00. namespace core_competency\privacy; use context; use context_course; use context_helper; use context_module; use context_system; use context_user; use moodle_recordset; use core_competency\api; use core_competency\competency; use core_competency\competency_framework; use core_competency\course_competency; use core_competency\course_competency_settings; use core_competency\course_module_competency; use core_competency\evidence; use core_competency\plan; use core_competency\plan_competency; use core_competency\related_competency; use core_competency\template; use core_competency\template_cohort; use core_competency\template_competency; use core_competency\user_competency; use core_competency\user_competency_course; use core_competency\user_competency_plan; use core_competency\user_evidence; use core_competency\user_evidence_competency; use core_competency\external\performance_helper; use core_privacy\local\metadata\collection; use core_privacy\local\request\approved_userlist; use core_privacy\local\request\contextlist; use core_privacy\local\request\approved_contextlist; use core_privacy\local\request\transform; use core_privacy\local\request\userlist; use core_privacy\local\request\writer; /** * Data provider class. * * @package core_competency * @copyright 2018 Frédéric Massart * @author Frédéric Massart * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class provider implements \core_privacy\local\metadata\provider, \core_privacy\local\request\core_userlist_provider, \core_privacy\local\request\subsystem\provider { /** * Returns metadata. * * @param collection $collection The initialised collection to add items to. * @return collection A listing of user data stored through this system. */ public static function get_metadata(collection $collection): collection { // Tables not related to users aside from the editing information. $collection->add_database_table('competency', [ 'timecreated' => 'privacy:metadata:timecreated', 'timemodified' => 'privacy:metadata:timemodified', 'usermodified' => 'privacy:metadata:usermodified', ], 'privacy:metadata:competency'); $collection->add_database_table('competency_coursecompsetting', [ 'timecreated' => 'privacy:metadata:timecreated', 'timemodified' => 'privacy:metadata:timemodified', 'usermodified' => 'privacy:metadata:usermodified', ], 'privacy:metadata:competency_coursecompsetting'); $collection->add_database_table('competency_framework', [ 'timecreated' => 'privacy:metadata:timecreated', 'timemodified' => 'privacy:metadata:timemodified', 'usermodified' => 'privacy:metadata:usermodified', ], 'privacy:metadata:competency_framework'); $collection->add_database_table('competency_coursecomp', [ 'timecreated' => 'privacy:metadata:timecreated', 'timemodified' => 'privacy:metadata:timemodified', 'usermodified' => 'privacy:metadata:usermodified', ], 'privacy:metadata:competency_coursecomp'); $collection->add_database_table('competency_template', [ 'timecreated' => 'privacy:metadata:timecreated', 'timemodified' => 'privacy:metadata:timemodified', 'usermodified' => 'privacy:metadata:usermodified', ], 'privacy:metadata:competency_template'); $collection->add_database_table('competency_templatecomp', [ 'timecreated' => 'privacy:metadata:timecreated', 'timemodified' => 'privacy:metadata:timemodified', 'usermodified' => 'privacy:metadata:usermodified', ], 'privacy:metadata:competency_templatecomp'); $collection->add_database_table('competency_templatecohort', [ 'timecreated' => 'privacy:metadata:timecreated', 'timemodified' => 'privacy:metadata:timemodified', 'usermodified' => 'privacy:metadata:usermodified', ], 'privacy:metadata:competency_templatecohort'); $collection->add_database_table('competency_relatedcomp', [ 'timecreated' => 'privacy:metadata:timecreated', 'timemodified' => 'privacy:metadata:timemodified', 'usermodified' => 'privacy:metadata:usermodified', ], 'privacy:metadata:competency_relatedcomp'); $collection->add_database_table('competency_modulecomp', [ 'timecreated' => 'privacy:metadata:timecreated', 'timemodified' => 'privacy:metadata:timemodified', 'usermodified' => 'privacy:metadata:usermodified', ], 'privacy:metadata:competency_modulecomp'); // Tables containing user data. $collection->add_database_table('competency_plan', [ 'name' => 'privacy:metadata:plan:name', 'description' => 'privacy:metadata:plan:description', 'userid' => 'privacy:metadata:plan:userid', 'status' => 'privacy:metadata:plan:status', 'duedate' => 'privacy:metadata:plan:duedate', 'reviewerid' => 'privacy:metadata:plan:reviewerid', 'timecreated' => 'privacy:metadata:timecreated', 'timemodified' => 'privacy:metadata:timemodified', 'usermodified' => 'privacy:metadata:usermodified', ], 'privacy:metadata:competency_plan'); $collection->add_database_table('competency_usercomp', [ 'userid' => 'privacy:metadata:usercomp:userid', 'status' => 'privacy:metadata:usercomp:status', 'reviewerid' => 'privacy:metadata:usercomp:reviewerid', 'proficiency' => 'privacy:metadata:usercomp:proficiency', 'grade' => 'privacy:metadata:usercomp:grade', 'timecreated' => 'privacy:metadata:timecreated', 'timemodified' => 'privacy:metadata:timemodified', 'usermodified' => 'privacy:metadata:usermodified', ], 'privacy:metadata:competency_usercomp'); $collection->add_database_table('competency_usercompcourse', [ 'userid' => 'privacy:metadata:usercomp:userid', 'proficiency' => 'privacy:metadata:usercomp:proficiency', 'grade' => 'privacy:metadata:usercomp:grade', 'timecreated' => 'privacy:metadata:timecreated', 'timemodified' => 'privacy:metadata:timemodified', 'usermodified' => 'privacy:metadata:usermodified', ], 'privacy:metadata:competency_usercompcourse'); $collection->add_database_table('competency_usercompplan', [ 'userid' => 'privacy:metadata:usercomp:userid', 'proficiency' => 'privacy:metadata:usercomp:proficiency', 'grade' => 'privacy:metadata:usercomp:grade', 'timecreated' => 'privacy:metadata:timecreated', 'timemodified' => 'privacy:metadata:timemodified', 'usermodified' => 'privacy:metadata:usermodified', ], 'privacy:metadata:competency_usercompplan'); $collection->add_database_table('competency_plancomp', [ 'timecreated' => 'privacy:metadata:timecreated', 'timemodified' => 'privacy:metadata:timemodified', 'usermodified' => 'privacy:metadata:usermodified', ], 'privacy:metadata:competency_plancomp'); $collection->add_database_table('competency_evidence', [ 'action' => 'privacy:metadata:evidence:action', 'actionuserid' => 'privacy:metadata:evidence:actionuserid', 'descidentifier' => 'privacy:metadata:evidence:descidentifier', 'desccomponent' => 'privacy:metadata:evidence:desccomponent', 'desca' => 'privacy:metadata:evidence:desca', 'url' => 'privacy:metadata:evidence:url', 'grade' => 'privacy:metadata:evidence:grade', 'note' => 'privacy:metadata:evidence:note', 'timecreated' => 'privacy:metadata:timecreated', 'timemodified' => 'privacy:metadata:timemodified', 'usermodified' => 'privacy:metadata:usermodified', ], 'privacy:metadata:competency_evidence'); $collection->add_database_table('competency_userevidence', [ 'name' => 'privacy:metadata:userevidence:name', 'description' => 'privacy:metadata:userevidence:description', 'url' => 'privacy:metadata:userevidence:url', 'timecreated' => 'privacy:metadata:timecreated', 'timemodified' => 'privacy:metadata:timemodified', 'usermodified' => 'privacy:metadata:usermodified', ], 'privacy:metadata:competency_userevidence'); $collection->add_database_table('competency_userevidencecomp', [ 'timecreated' => 'privacy:metadata:timecreated', 'timemodified' => 'privacy:metadata:timemodified', 'usermodified' => 'privacy:metadata:usermodified', ], 'privacy:metadata:competency_userevidencecomp'); // Comments can be left on learning plans and competencies. $collection->link_subsystem('core_comment', 'privacy:metadata:core_comments'); return $collection; } /** * Get the list of contexts that contain user information for the specified user. * * @param int $userid The user to search. * @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin. */ public static function get_contexts_for_userid(int $userid): contextlist { global $DB; $contextlist = new \core_privacy\local\request\contextlist(); // Find the contexts of the frameworks, and related data, modified by the user. $sql = " SELECT DISTINCT ctx.id FROM {context} ctx JOIN {" . competency_framework::TABLE . "} cf ON cf.contextid = ctx.id WHERE cf.usermodified = :userid1"; $params = ['userid1' => $userid]; $contextlist->add_from_sql($sql, $params); $sql = " SELECT DISTINCT ctx.id FROM {context} ctx JOIN {" . competency_framework::TABLE . "} cf ON cf.contextid = ctx.id JOIN {" . competency::TABLE . "} c ON c.competencyframeworkid = cf.id WHERE c.usermodified = :userid2"; $params = ['userid2' => $userid]; $contextlist->add_from_sql($sql, $params); $sql = " SELECT DISTINCT ctx.id FROM {context} ctx JOIN {" . competency_framework::TABLE . "} cf ON cf.contextid = ctx.id JOIN {" . competency::TABLE . "} c ON c.competencyframeworkid = cf.id JOIN {" . related_competency::TABLE . "} cr ON cr.competencyid = c.id WHERE cr.usermodified = :userid3"; $params = ['userid3' => $userid]; $contextlist->add_from_sql($sql, $params); // Find the contexts of the templates, and related data, modified by the user. $sql = " SELECT DISTINCT ctx.id FROM {context} ctx JOIN {" . template::TABLE . "} tpl ON tpl.contextid = ctx.id WHERE tpl.usermodified = :userid1"; $params = ['userid1' => $userid]; $contextlist->add_from_sql($sql, $params); $sql = " SELECT DISTINCT ctx.id FROM {context} ctx JOIN {" . template::TABLE . "} tpl ON tpl.contextid = ctx.id JOIN {" . template_cohort::TABLE . "} tch ON tch.templateid = tpl.id AND tch.usermodified = :userid2"; $params = ['userid2' => $userid]; $contextlist->add_from_sql($sql, $params); $sql = " SELECT DISTINCT ctx.id FROM {context} ctx JOIN {" . template::TABLE . "} tpl ON tpl.contextid = ctx.id JOIN {" . template_competency::TABLE . "} tc ON tc.templateid = tpl.id AND tc.usermodified = :userid3"; $params = ['userid3' => $userid]; $contextlist->add_from_sql($sql, $params); // Find the possible course contexts. $sql = " SELECT DISTINCT ctx.id FROM {" . course_competency::TABLE . "} cc JOIN {context} ctx ON ctx.instanceid = cc.courseid AND ctx.contextlevel = :courselevel WHERE cc.usermodified = :userid"; $params = ['courselevel' => CONTEXT_COURSE, 'userid' => $userid]; $contextlist->add_from_sql($sql, $params); $sql = " SELECT DISTINCT ctx.id FROM {" . course_competency_settings::TABLE . "} ccs JOIN {context} ctx ON ctx.instanceid = ccs.courseid AND ctx.contextlevel = :courselevel WHERE ccs.usermodified = :userid"; $params = ['courselevel' => CONTEXT_COURSE, 'userid' => $userid]; $contextlist->add_from_sql($sql, $params); $sql = " SELECT DISTINCT ctx.id FROM {" . user_competency_course::TABLE . "} ucc JOIN {context} ctx ON ctx.instanceid = ucc.courseid AND ctx.contextlevel = :courselevel WHERE ucc.usermodified = :userid"; $params = ['courselevel' => CONTEXT_COURSE, 'userid' => $userid]; $contextlist->add_from_sql($sql, $params); // Find the possible module contexts. $sql = " SELECT DISTINCT ctx.id FROM {" . course_module_competency::TABLE . "} cmc JOIN {context} ctx ON ctx.instanceid = cmc.cmid AND ctx.contextlevel = :modulelevel WHERE cmc.usermodified = :userid"; $params = ['modulelevel' => CONTEXT_MODULE, 'userid' => $userid]; $contextlist->add_from_sql($sql, $params); // Add user contexts through usermodified/reviewing of plan related data. $sql = " SELECT DISTINCT ctx.id FROM {" . plan::TABLE . "} p JOIN {context} ctx ON ctx.instanceid = p.userid AND ctx.contextlevel = :userlevel WHERE p.usermodified = :userid1"; $params = [ 'userlevel' => CONTEXT_USER, 'userid1' => $userid, ]; $contextlist->add_from_sql($sql, $params); $sql = " SELECT DISTINCT ctx.id FROM {" . plan::TABLE . "} p JOIN {context} ctx ON ctx.instanceid = p.userid AND ctx.contextlevel = :userlevel WHERE p.reviewerid = :userid2"; $params = [ 'userlevel' => CONTEXT_USER, 'userid2' => $userid, ]; $contextlist->add_from_sql($sql, $params); $sql = " SELECT DISTINCT ctx.id FROM {" . plan::TABLE . "} p JOIN {context} ctx ON ctx.instanceid = p.userid AND ctx.contextlevel = :userlevel JOIN {" . plan_competency::TABLE . "} pc ON pc.planid = p.id AND pc.usermodified = :userid3"; $params = [ 'userlevel' => CONTEXT_USER, 'userid3' => $userid, ]; $contextlist->add_from_sql($sql, $params); $sql = " SELECT DISTINCT ctx.id FROM {" . plan::TABLE . "} p JOIN {context} ctx ON ctx.instanceid = p.userid AND ctx.contextlevel = :userlevel JOIN {" . user_competency_plan::TABLE . "} upc ON upc.planid = p.id AND upc.usermodified = :userid4"; $params = [ 'userlevel' => CONTEXT_USER, 'userid4' => $userid, ]; $contextlist->add_from_sql($sql, $params); // Add user contexts through usermodified/reviewing of competency data. $sql = " SELECT DISTINCT ctx.id FROM {context} ctx JOIN {" . user_competency::TABLE . "} uc ON uc.userid = ctx.instanceid AND ctx.contextlevel = :userlevel1 WHERE uc.usermodified = :userid1"; $params = [ 'userlevel1' => CONTEXT_USER, 'userid1' => $userid, ]; $contextlist->add_from_sql($sql, $params); $sql = " SELECT DISTINCT ctx.id FROM {context} ctx JOIN {" . user_competency::TABLE . "} uc ON uc.userid = ctx.instanceid AND ctx.contextlevel = :userlevel1 WHERE uc.reviewerid = :userid2"; $params = [ 'userlevel1' => CONTEXT_USER, 'userid2' => $userid, ]; $contextlist->add_from_sql($sql, $params); $sql = " SELECT DISTINCT ctx.id FROM {context} ctx JOIN {" . user_competency::TABLE . "} uc ON uc.userid = ctx.instanceid AND ctx.contextlevel = :userlevel1 JOIN {" . evidence::TABLE . "} e ON e.usercompetencyid = uc.id AND e.usermodified = :userid3"; $params = [ 'userlevel1' => CONTEXT_USER, 'userid3' => $userid, ]; $contextlist->add_from_sql($sql, $params); $sql = " SELECT DISTINCT ctx.id FROM {context} ctx JOIN {" . user_competency::TABLE . "} uc ON uc.userid = ctx.instanceid AND ctx.contextlevel = :userlevel1 JOIN {" . evidence::TABLE . "} e ON e.usercompetencyid = uc.id AND e.actionuserid = :userid4"; $params = [ 'userlevel1' => CONTEXT_USER, 'userid4' => $userid, ]; $contextlist->add_from_sql($sql, $params); $sql = " SELECT DISTINCT ctx.id FROM {context} ctx JOIN {" . user_evidence::TABLE . "} ue ON ue.userid = ctx.instanceid AND ctx.contextlevel = :userlevel2 AND ue.usermodified = :userid5"; $params = [ 'userlevel2' => CONTEXT_USER, 'userid5' => $userid, ]; $contextlist->add_from_sql($sql, $params); $sql = " SELECT DISTINCT ctx.id FROM {context} ctx JOIN {" . user_evidence::TABLE . "} ue ON ue.userid = ctx.instanceid AND ctx.contextlevel = :userlevel2 JOIN {" . user_evidence_competency::TABLE . "} uec ON uec.userevidenceid = ue.id AND uec.usermodified = :userid6"; $params = [ 'userlevel2' => CONTEXT_USER, 'userid6' => $userid, ]; $contextlist->add_from_sql($sql, $params); // Now, the easy part, we fetch the user context for user plans and competencies. // We also fetch the course context for the state of competencies for the user in courses. $sql = " SELECT DISTINCT ctx.id FROM {context} ctx JOIN {" . plan::TABLE . "} p ON p.userid = ctx.instanceid AND ctx.contextlevel = :userlevel1 WHERE p.userid = :userid1"; $params = [ 'userlevel1' => CONTEXT_USER, 'userid1' => $userid, ]; $contextlist->add_from_sql($sql, $params); $sql = " SELECT DISTINCT ctx.id FROM {context} ctx JOIN {" . user_competency::TABLE . "} uc ON uc.userid = ctx.instanceid AND ctx.contextlevel = :userlevel2 AND uc.userid = :userid2"; $params = [ 'userlevel2' => CONTEXT_USER, 'userid2' => $userid, ]; $contextlist->add_from_sql($sql, $params); $sql = " SELECT DISTINCT ctx.id FROM {context} ctx JOIN {" . user_evidence::TABLE . "} ue ON ue.userid = ctx.instanceid AND ctx.contextlevel = :userlevel3 AND ue.userid = :userid3"; $params = [ 'userlevel3' => CONTEXT_USER, 'userid3' => $userid, ]; $contextlist->add_from_sql($sql, $params); $sql = " SELECT DISTINCT ctx.id FROM {context} ctx JOIN {" . user_competency_course::TABLE . "} ucc ON ucc.courseid = ctx.instanceid AND ctx.contextlevel = :courselevel AND ucc.userid = :userid4"; $params = [ 'courselevel' => CONTEXT_COURSE, 'userid4' => $userid, ]; $contextlist->add_from_sql($sql, $params); // Include the user contexts in which the user commented. $sql = " SELECT ctx.id FROM {context} ctx JOIN {comments} c ON c.contextid = ctx.id WHERE c.component = :component AND c.commentarea IN (:planarea, :usercomparea) AND c.userid = :userid"; $params = [ 'component' => 'competency', // Must not be core_competency. 'planarea' => 'plan', 'usercomparea' => 'user_competency', 'userid' => $userid ]; $contextlist->add_from_sql($sql, $params); return $contextlist; } /** * Get the list of users who have data within a context. * * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination. */ public static function get_users_in_context(userlist $userlist) { $context = $userlist->get_context(); $params = ['contextid' => $context->id]; // Add users who have modified the frameworks and related data in the context. $sql = " SELECT cf.usermodified FROM {" . competency_framework::TABLE . "} cf WHERE cf.contextid = :contextid"; $userlist->add_from_sql('usermodified', $sql, $params); $sql = " SELECT c.usermodified FROM {" . competency_framework::TABLE . "} cf JOIN {" . competency::TABLE . "} c ON c.competencyframeworkid = cf.id WHERE cf.contextid = :contextid"; $userlist->add_from_sql('usermodified', $sql, $params); $sql = " SELECT cr.usermodified FROM {" . competency_framework::TABLE . "} cf JOIN {" . competency::TABLE . "} c ON c.competencyframeworkid = cf.id JOIN {" . related_competency::TABLE . "} cr ON cr.competencyid = c.id WHERE cf.contextid = :contextid"; $userlist->add_from_sql('usermodified', $sql, $params); // Add users who have modified the templates and related data in the context. $sql = " SELECT tpl.usermodified FROM {" . template::TABLE . "} tpl WHERE tpl.contextid = :contextid"; $userlist->add_from_sql('usermodified', $sql, $params); $sql = " SELECT tch.usermodified FROM {" . template::TABLE . "} tpl JOIN {" . template_cohort::TABLE . "} tch ON tch.templateid = tpl.id WHERE tpl.contextid = :contextid"; $userlist->add_from_sql('usermodified', $sql, $params); $sql = " SELECT tc.usermodified FROM {" . template::TABLE . "} tpl JOIN {" . template_competency::TABLE . "} tc ON tc.templateid = tpl.id WHERE tpl.contextid = :contextid"; $userlist->add_from_sql('usermodified', $sql, $params); // Add users if userlist is in course context. if (is_a($context, \context_course::class)) { $params = ['courseid' => $context->instanceid]; $sql = " SELECT cc.usermodified FROM {" . course_competency::TABLE . "} cc WHERE cc.courseid = :courseid"; $userlist->add_from_sql('usermodified', $sql, $params); $sql = " SELECT ccs.usermodified FROM {" . course_competency_settings::TABLE . "} ccs WHERE ccs.courseid = :courseid"; $userlist->add_from_sql('usermodified', $sql, $params); $sql = " SELECT ucc.userid, ucc.usermodified FROM {" . user_competency_course::TABLE . "} ucc WHERE ucc.courseid = :courseid"; $userlist->add_from_sql('userid', $sql, $params); $userlist->add_from_sql('usermodified', $sql, $params); } else if (is_a($context, \context_module::class)) { // Add users if userlist is in module context. $params = ['moduleid' => $context->instanceid]; $sql = " SELECT cmc.usermodified FROM {" . course_module_competency::TABLE . "} cmc WHERE cmc.cmid = :moduleid"; $userlist->add_from_sql('usermodified', $sql, $params); } else if (is_a($context, \context_user::class)) { $params = ['userid' => $context->instanceid]; // Add users through plan related data. $sql = " SELECT p.userid, p.usermodified, p.reviewerid FROM {" . plan::TABLE . "} p WHERE p.userid = :userid"; $userlist->add_from_sql('userid', $sql, $params); $userlist->add_from_sql('usermodified', $sql, $params); $userlist->add_from_sql('reviewerid', $sql, $params); $sql = " SELECT pc.usermodified FROM {" . plan::TABLE . "} p JOIN {" . plan_competency::TABLE . "} pc ON pc.planid = p.id WHERE p.userid = :userid"; $userlist->add_from_sql('usermodified', $sql, $params); $sql = " SELECT upc.usermodified FROM {" . user_competency_plan::TABLE . "} upc WHERE upc.userid = :userid"; $userlist->add_from_sql('usermodified', $sql, $params); // Add users through competency data. $sql = " SELECT uc.userid, uc.usermodified, uc.reviewerid FROM {" . user_competency::TABLE . "} uc WHERE uc.userid = :userid"; $userlist->add_from_sql('userid', $sql, $params); $userlist->add_from_sql('usermodified', $sql, $params); $userlist->add_from_sql('reviewerid', $sql, $params); $sql = " SELECT e.usermodified, e.actionuserid FROM {" . user_competency::TABLE . "} uc JOIN {" . evidence::TABLE . "} e ON e.usercompetencyid = uc.id WHERE uc.userid = :userid"; $userlist->add_from_sql('usermodified', $sql, $params); $userlist->add_from_sql('actionuserid', $sql, $params); // Add users through evidence data. $sql = " SELECT ue.userid, ue.usermodified FROM {" . user_evidence::TABLE . "} ue WHERE ue.userid = :userid"; $userlist->add_from_sql('userid', $sql, $params); $userlist->add_from_sql('usermodified', $sql, $params); $sql = " SELECT ue.usermodified FROM {" . user_evidence::TABLE . "} ue JOIN {" . user_evidence_competency::TABLE . "} uec ON uec.userevidenceid = ue.id WHERE ue.userid = :userid"; $userlist->add_from_sql('usermodified', $sql, $params); } // Add users who commented in the context. // Note: Comment component must be competency and not core_competency. \core_comment\privacy\provider::get_users_in_context_from_sql( $userlist, 'com', 'competency', 'plan', $context->id); \core_comment\privacy\provider::get_users_in_context_from_sql( $userlist, 'com', 'competency', 'user_competency', $context->id); } /** * Export all user data for the specified user, in the specified contexts. * * We skip the enabled check for competencies, as there could be historical data. This avoids exceptions thrown from * the {@see api::require_enabled} method, which is called at various points during export via the competency API * * @param approved_contextlist $contextlist The approved contexts to export information for. */ public static function export_user_data(approved_contextlist $contextlist) { // Export even if competencies are not currently enabled. api::skip_enabled(); $user = $contextlist->get_user(); $userid = $user->id; // Re-arrange the contexts by context level. $groupedcontexts = array_reduce($contextlist->get_contexts(), function($carry, $context) { $contextlevel = $context->contextlevel; if (!in_array($contextlevel, [CONTEXT_USER, CONTEXT_COURSE, CONTEXT_MODULE, CONTEXT_SYSTEM, CONTEXT_COURSECAT])) { return $carry; } $carry[$contextlevel][] = $context; return $carry; }, [ CONTEXT_COURSE => [], CONTEXT_COURSECAT => [], CONTEXT_MODULE => [], CONTEXT_SYSTEM => [], CONTEXT_USER => [], ]); // Process module contexts. static::export_user_data_in_module_contexts($userid, $groupedcontexts[CONTEXT_MODULE]); // Process course contexts. static::export_user_data_in_course_contexts($userid, $groupedcontexts[CONTEXT_COURSE]); // Process course categories context. static::export_user_data_in_category_contexts($userid, $groupedcontexts[CONTEXT_COURSECAT]); // Process system context. if (!empty($groupedcontexts[CONTEXT_SYSTEM])) { static::export_user_data_in_system_context($userid); } // Process user contexts. static::export_user_data_in_user_contexts($userid, $groupedcontexts[CONTEXT_USER]); } /** * Delete all data for all users in the specified context. * * @param context $context The specific context to delete data for. */ public static function delete_data_for_all_users_in_context(context $context) { global $DB; switch ($context->contextlevel) { case CONTEXT_USER: $userid = $context->instanceid; static::delete_user_evidence_of_prior_learning($userid); static::delete_user_plans($userid); static::delete_user_competencies($userid); break; case CONTEXT_COURSE: static::delete_user_competencies_in_course($context->instanceid); break; } } /** * Delete all user data for the specified user, in the specified contexts. * * Here we only delete the private data of user, not whether they modified, are reviewing, * or are associated with the record on at a second level. Only data directly linked to the * user will be affected. * * @param approved_contextlist $contextlist The approved contexts and user information to delete information for. */ public static function delete_data_for_user(approved_contextlist $contextlist) { $user = $contextlist->get_user(); $userid = $user->id; foreach ($contextlist as $context) { switch ($context->contextlevel) { case CONTEXT_USER: if ($context->instanceid == $userid) { // Only delete the user's information when they requested their context to be deleted. We // do not take any action on other user's contexts because we don't own the data there. static::delete_user_evidence_of_prior_learning($userid); static::delete_user_plans($userid); static::delete_user_competencies($userid); } break; case CONTEXT_COURSE: static::delete_user_competencies_in_course($context->instanceid, [$userid]); break; } } } /** * Delete multiple users within a single context. * * Here we only delete the private data of users, not whether they modified, are reviewing, * or are associated with the record on at a second level. Only data directly linked to the * user will be affected. * * @param approved_userlist $userlist The approved context and user information to delete information for. */ public static function delete_data_for_users(approved_userlist $userlist) { $context = $userlist->get_context(); $userids = $userlist->get_userids(); switch ($context->contextlevel) { case CONTEXT_USER: // Only delete the user's information when their context is being deleted. // We do not take any action on other user's contexts because we don't own the data there. if (in_array($context->instanceid, $userids)) { static::delete_user_evidence_of_prior_learning($context->instanceid); static::delete_user_plans($context->instanceid); static::delete_user_competencies($context->instanceid); } break; case CONTEXT_COURSE: static::delete_user_competencies_in_course($context->instanceid, $userids); break; } } /** * Delete evidence of prior learning. * * @param int $userid The user ID. * @return void */ protected static function delete_user_evidence_of_prior_learning($userid) { global $DB; $usercontext = context_user::instance($userid); $ueids = $DB->get_fieldset_select(user_evidence::TABLE, 'id', 'userid = :userid', ['userid' => $userid]); if (empty($ueids)) { return; } list($insql, $inparams) = $DB->get_in_or_equal($ueids, SQL_PARAMS_NAMED); // Delete competencies associated with user evidence. $DB->delete_records_select(user_evidence_competency::TABLE, "userevidenceid $insql", $inparams); // Delete the user evidence. $DB->delete_records_select(user_evidence::TABLE, "id $insql", $inparams); // Delete the user evidence files. $fs = get_file_storage(); $fs->delete_area_files($usercontext->id, 'core_competency', 'userevidence'); } /** * User plans. * * @param int $userid The user ID. * @return void */ protected static function delete_user_plans($userid) { global $DB; $usercontext = context_user::instance($userid); // Remove all the comments made on plans. \core_comment\privacy\provider::delete_comments_for_all_users($usercontext, 'competency', 'plan'); // Find the user plan IDs. $planids = $DB->get_fieldset_select(plan::TABLE, 'id', 'userid = :userid', ['userid' => $userid]); if (empty($planids)) { return; } list($insql, $inparams) = $DB->get_in_or_equal($planids, SQL_PARAMS_NAMED); // Delete all the competencies proficiency in the plans. $DB->delete_records_select(user_competency_plan::TABLE, "planid $insql", $inparams); // Delete all the competencies in the plans. $DB->delete_records_select(plan_competency::TABLE, "planid $insql", $inparams); // Delete all the plans. $DB->delete_records_select(plan::TABLE, "id $insql", $inparams); } /** * Delete user competency data. * * @param int $userid The user ID. * @return void */ protected static function delete_user_competencies($userid) { global $DB; $usercontext = context_user::instance($userid); // Remove all the comments made on user competencies. \core_comment\privacy\provider::delete_comments_for_all_users($usercontext, 'competency', 'user_competency'); // Find the user competency IDs. $ucids = $DB->get_fieldset_select(user_competency::TABLE, 'id', 'userid = :userid', ['userid' => $userid]); if (empty($ucids)) { return; } list($insql, $inparams) = $DB->get_in_or_equal($ucids, SQL_PARAMS_NAMED); // Delete all the evidence associated with competencies. $DB->delete_records_select(evidence::TABLE, "usercompetencyid $insql", $inparams); // Delete all the record of competency. $DB->delete_records_select(user_competency::TABLE, "id $insql", $inparams); } /** * Delete the record of competencies for user(s) in a course. * * @param int $courseid The course ID. * @param int[] $userids The user IDs, if deleting for specific user(s). * @return void */ protected static function delete_user_competencies_in_course($courseid, $userids = []) { global $DB; $params = ['courseid' => $courseid]; $where = "courseid = :courseid"; if (!empty($userids)) { list($insql, $inparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED); $params = $params + $inparams; $where .= " AND userid {$insql}"; } $DB->delete_records_select(user_competency_course::TABLE, $where, $params); } /** * Export the user data in user context. * * @param int $userid The user ID. * @param array $contexts The contexts. * @return void */ protected static function export_user_data_in_user_contexts($userid, array $contexts) { global $DB; $mycontext = context_user::instance($userid); $contextids = array_map(function($context) { return $context->id; }, $contexts); $exportowncontext = in_array($mycontext->id, $contextids); $othercontexts = array_filter($contextids, function($contextid) use ($mycontext) { return $contextid != $mycontext->id; }); if ($exportowncontext) { static::export_user_data_learning_plans($mycontext); static::export_user_data_competencies($mycontext); static::export_user_data_user_evidence($mycontext); } foreach ($othercontexts as $contextid) { static::export_user_data_learning_plans_related_to_me($userid, context::instance_by_id($contextid)); static::export_user_data_competencies_related_to_me($userid, context::instance_by_id($contextid)); static::export_user_data_user_evidence_related_to_me($userid, context::instance_by_id($contextid)); } } /** * Export the user data in systen context. * * @param int $userid The user ID. * @return void */ protected static function export_user_data_in_system_context($userid) { static::export_user_data_frameworks_in_context($userid, context_system::instance()); static::export_user_data_templates_in_context($userid, context_system::instance()); } /** * Export the user data in category contexts. * * @param int $userid The user ID. * @param array $contexts The contexts. * @return void */ protected static function export_user_data_in_category_contexts($userid, array $contexts) { $contexts = array_filter($contexts, function($context) { return $context->contextlevel == CONTEXT_COURSECAT; }); if (empty($contexts)) { return; } foreach ($contexts as $context) { static::export_user_data_frameworks_in_context($userid, $context); static::export_user_data_templates_in_context($userid, $context); } } /** * Export the user data in course contexts. * * @param int $userid The user whose data we're exporting. * @param array $contexts A list of contexts. * @return void */ protected static function export_user_data_in_course_contexts($userid, array $contexts) { global $DB; $contexts = array_filter($contexts, function($context) { return $context->contextlevel == CONTEXT_COURSE; }); if (empty($contexts)) { return; } $helper = new performance_helper(); $path = [get_string('competencies', 'core_competency')]; $courseids = array_map(function($context) { return $context->instanceid; }, $contexts); // Fetch all the records of competency proficiency in the course. $ffields = competency_framework::get_sql_fields('f', 'f_'); $compfields = competency::get_sql_fields('c', 'c_'); $uccfields = user_competency_course::get_sql_fields('ucc', 'ucc_'); $ctxfields = context_helper::get_preload_record_columns_sql('ctx'); list($insql, $inparams) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED); $sql = " SELECT $ffields, $compfields, $uccfields, $ctxfields FROM {" . user_competency_course::TABLE . "} ucc JOIN {" . competency::TABLE . "} c ON c.id = ucc.competencyid JOIN {" . competency_framework::TABLE . "} f ON f.id = c.competencyframeworkid JOIN {context} ctx ON ctx.id = f.contextid WHERE ucc.userid = :userid AND ucc.courseid $insql ORDER BY ucc.courseid, c.id"; $params = array_merge($inparams, ['userid' => $userid]); // Export data. $recordset = $DB->get_recordset_sql($sql, $params); static::recordset_loop_and_export($recordset, 'ucc_courseid', [], function($carry, $record) use ($helper) { context_helper::preload_from_record($record); $framework = new competency_framework(null, competency_framework::extract_record($record, 'f_')); $competency = new competency(null, competency::extract_record($record, 'c_')); $ucc = new user_competency_course(null, user_competency_course::extract_record($record, 'ucc_')); $helper->ingest_framework($framework); $carry[] = array_merge(static::transform_competency_brief($competency), [ 'rating' => [ 'rating' => static::transform_competency_grade($competency, $ucc->get('grade'), $helper), 'proficient' => static::transform_proficiency($ucc->get('proficiency')), 'timecreated' => transform::datetime($ucc->get('timecreated')), 'timemodified' => transform::datetime($ucc->get('timemodified')), ] ]); return $carry; }, function($courseid, $data) use ($path) { $context = context_course::instance($courseid); writer::with_context($context)->export_data($path, (object) ['ratings' => $data]); }); // Export usermodified data. static::export_user_data_in_course_contexts_associations($userid, $courseids, $path); static::export_user_data_in_course_contexts_settings($userid, $courseids, $path); static::export_user_data_in_course_contexts_rated_by_me($userid, $courseids, $path, $helper); } /** * Export the ratings given in a course. * * @param int $userid The user ID. * @param array $courseids The course IDs. * @param array $path The root path. * @param performance_helper $helper The performance helper. * @return void */ protected static function export_user_data_in_course_contexts_rated_by_me($userid, $courseids, $path, performance_helper $helper) { global $DB; // Fetch all the records of competency proficiency in the course. $ffields = competency_framework::get_sql_fields('f', 'f_'); $compfields = competency::get_sql_fields('c', 'c_'); $uccfields = user_competency_course::get_sql_fields('ucc', 'ucc_'); $ctxfields = context_helper::get_preload_record_columns_sql('ctx'); list($insql, $inparams) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED); $sql = " SELECT $ffields, $compfields, $uccfields, $ctxfields FROM {" . user_competency_course::TABLE . "} ucc JOIN {" . competency::TABLE . "} c ON c.id = ucc.competencyid JOIN {" . competency_framework::TABLE . "} f ON f.id = c.competencyframeworkid JOIN {context} ctx ON ctx.id = f.contextid WHERE ucc.usermodified = :userid AND ucc.courseid $insql ORDER BY ucc.courseid, ucc.id"; $params = array_merge($inparams, ['userid' => $userid]); // Export the data. static::recordset_loop_and_export($DB->get_recordset_sql($sql, $params), 'ucc_courseid', [], function($carry, $record) use ($helper) { context_helper::preload_from_record($record); $framework = new competency_framework(null, competency_framework::extract_record($record, 'f_')); $competency = new competency(null, competency::extract_record($record, 'c_')); $ucc = new user_competency_course(null, user_competency_course::extract_record($record, 'ucc_')); $helper->ingest_framework($framework); $carry[] = array_merge(static::transform_competency_brief($competency), [ 'rating' => [ 'userid' => transform::user($ucc->get('userid')), 'rating' => static::transform_competency_grade($competency, $ucc->get('grade'), $helper), 'proficient' => static::transform_proficiency($ucc->get('proficiency')), 'timemodified' => transform::datetime($ucc->get('timemodified')), ] ]); return $carry; }, function($courseid, $data) use ($path) { $context = context_course::instance($courseid); writer::with_context($context)->export_related_data($path, 'rated_by_me', (object) [ 'ratings' => $data ]); } ); } /** * Export user data in course contexts related to linked competencies. * * @param int $userid The user ID. * @param array $courseids The course IDs. * @param array $path The root path to export at. * @return void */ protected static function export_user_data_in_course_contexts_associations($userid, $courseids, $path) { global $DB; // Fetch all the courses with associations we created or modified. $compfields = competency::get_sql_fields('c', 'c_'); $ccfields = course_competency::get_sql_fields('cc', 'cc_'); $ctxfields = context_helper::get_preload_record_columns_sql('ctx'); list($insql, $inparams) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED); $sql = " SELECT $compfields, $ccfields, $ctxfields FROM {" . course_competency::TABLE . "} cc JOIN {" . competency::TABLE . "} c ON c.id = cc.competencyid JOIN {" . competency_framework::TABLE . "} f ON f.id = c.competencyframeworkid JOIN {context} ctx ON ctx.id = f.contextid WHERE cc.usermodified = :userid AND cc.courseid $insql ORDER BY cc.courseid, c.id"; $params = array_merge($inparams, ['userid' => $userid]); $recordset = $DB->get_recordset_sql($sql, $params); // Export the data. static::recordset_loop_and_export($recordset, 'cc_courseid', [], function($carry, $record) { context_helper::preload_from_record($record); $competency = new competency(null, competency::extract_record($record, 'c_')); $cc = new course_competency(null, course_competency::extract_record($record, 'cc_')); $carry[] = array_merge(static::transform_competency_brief($competency), [ 'timemodified' => transform::datetime($cc->get('timemodified')), 'created_or_modified_by_you' => transform::yesno(true) ]); return $carry; }, function($courseid, $data) use ($path, $userid, $DB) { $context = context_course::instance($courseid); writer::with_context($context)->export_related_data($path, 'associations', (object) ['competencies' => $data]); }); } /** * Export user data in course contexts related to course settings. * * @param int $userid The user ID. * @param array $courseids The course IDs. * @param array $path The root path to export at. * @return void */ protected static function export_user_data_in_course_contexts_settings($userid, $courseids, $path) { global $DB; // Fetch all the courses with associations we created or modified. $ccsfields = course_competency_settings::get_sql_fields('ccs', 'ccs_'); list($insql, $inparams) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED); $sql = " SELECT $ccsfields FROM {" . course_competency_settings::TABLE . "} ccs WHERE ccs.usermodified = :userid AND ccs.courseid $insql ORDER BY ccs.courseid"; $params = array_merge($inparams, ['userid' => $userid]); $recordset = $DB->get_recordset_sql($sql, $params); // Export the data. static::recordset_loop_and_export($recordset, 'ccs_courseid', [], function($carry, $record) { $ccs = new course_competency_settings(null, course_competency_settings::extract_record($record, 'ccs_')); return [ 'timemodified' => transform::datetime($ccs->get('timemodified')), 'created_or_modified_by_you' => transform::yesno(true) ]; }, function($courseid, $data) use ($path, $userid, $DB) { $context = context_course::instance($courseid); writer::with_context($context)->export_related_data($path, 'settings', (object) $data); }); } /** * Export the user data in module contexts. * * @param int $userid The user whose data we're exporting. * @param array $contexts A list of contexts. * @return void */ protected static function export_user_data_in_module_contexts($userid, array $contexts) { global $DB; $contexts = array_filter($contexts, function($context) { return $context->contextlevel == CONTEXT_MODULE; }); if (empty($contexts)) { return; } $path = [get_string('competencies', 'core_competency')]; $cmids = array_map(function($context) { return $context->instanceid; }, $contexts); // Fetch all the modules with associations we created or modified. $compfields = competency::get_sql_fields('c', 'c_'); $cmcfields = course_module_competency::get_sql_fields('cmc', 'cmc_'); $ctxfields = context_helper::get_preload_record_columns_sql('ctx'); list($insql, $inparams) = $DB->get_in_or_equal($cmids, SQL_PARAMS_NAMED); $sql = " SELECT $compfields, $cmcfields, $ctxfields FROM {" . course_module_competency::TABLE . "} cmc JOIN {" . competency::TABLE . "} c ON c.id = cmc.competencyid JOIN {" . competency_framework::TABLE . "} f ON f.id = c.competencyframeworkid JOIN {context} ctx ON ctx.id = f.contextid WHERE cmc.usermodified = :userid AND cmc.cmid $insql ORDER BY cmc.cmid"; $params = array_merge($inparams, ['userid' => $userid]); // Export the data. $recordset = $DB->get_recordset_sql($sql, $params); static::recordset_loop_and_export($recordset, 'cmc_cmid', [], function($carry, $record) { context_helper::preload_from_record($record); $competency = new competency(null, competency::extract_record($record, 'c_')); $cmc = new course_module_competency(null, course_module_competency::extract_record($record, 'cmc_')); $carry[] = array_merge(static::transform_competency_brief($competency), [ 'timecreated' => transform::datetime($cmc->get('timecreated')), 'timemodified' => transform::datetime($cmc->get('timemodified')), 'created_or_modified_by_you' => transform::yesno(true) ]); return $carry; }, function($cmid, $data) use ($path) { $context = context_module::instance($cmid); writer::with_context($context)->export_data($path, (object) ['associations' => $data]); }); } /** * Export a user's competencies. * * @param context_user $context The context of the user requesting the export. * @return void */ protected static function export_user_data_competencies(context_user $context) { global $DB; $userid = $context->instanceid; $path = [get_string('competencies', 'core_competency'), get_string('competencies', 'core_competency')]; $helper = new performance_helper(); $cfields = competency::get_sql_fields('c', 'c_'); $ucfields = user_competency::get_sql_fields('uc', 'uc_'); $efields = evidence::get_sql_fields('e', 'e_'); $makecomppath = function($competencyid, $data) use ($path) { return array_merge($path, [$data['name'] . ' (' . $competencyid . ')']); }; $sql = " SELECT $cfields, $ucfields, $efields FROM {" . user_competency::TABLE . "} uc JOIN {" . competency::TABLE . "} c ON c.id = uc.competencyid LEFT JOIN {" . evidence::TABLE . "} e ON uc.id = e.usercompetencyid WHERE uc.userid = :userid ORDER BY c.id, e.timecreated DESC, e.id DESC"; $params = ['userid' => $userid]; $recordset = $DB->get_recordset_sql($sql, $params); static::recordset_loop_and_export($recordset, 'c_id', null, function($carry, $record) use ($context, $userid, $helper, $makecomppath) { $competency = new competency(null, competency::extract_record($record, 'c_')); if ($carry === null) { $uc = new user_competency(null, user_competency::extract_record($record, 'uc_')); $carry = array_merge(static::transform_competency_brief($competency), [ 'rating' => static::transform_user_competency($userid, $uc, $competency, $helper), 'evidence' => [] ]); \core_comment\privacy\provider::export_comments($context, 'competency', 'user_competency', $uc->get('id'), $makecomppath($competency->get('id'), $carry), false); } // There is an evidence in this record. if (!empty($record->e_id)) { $evidence = new evidence(null, evidence::extract_record($record, 'e_')); $carry['evidence'][] = static::transform_evidence($userid, $evidence, $competency, $helper); } return $carry; }, function($competencyid, $data) use ($makecomppath, $context) { writer::with_context($context)->export_data($makecomppath($competencyid, $data), (object) $data); }); } /** * Export a user's learning plans. * * @param context_user $context The context of the user requesting the export. * @return void */ protected static function export_user_data_learning_plans(context_user $context) { global $DB; $userid = $context->instanceid; $path = [get_string('competencies', 'core_competency'), get_string('privacy:path:plans', 'core_competency')]; $helper = new performance_helper(); $pfields = plan::get_sql_fields('p', 'p_'); $pcfields = plan_competency::get_sql_fields('pc', 'pc_'); $cfields = competency::get_sql_fields('c', 'c_'); $ucfields = user_competency::get_sql_fields('uc', 'uc_'); $ucpfields = user_competency_plan::get_sql_fields('ucp', 'ucp_'); // The user's learning plans. $sql = " SELECT $pfields, $pcfields, $cfields, $ucfields, $ucpfields FROM {" . plan::TABLE . "} p LEFT JOIN {" . plan_competency::TABLE . "} pc ON p.id = pc.planid AND p.templateid IS NULL AND p.status != :complete1 LEFT JOIN {" . template_competency::TABLE . "} tc ON tc.templateid = p.templateid AND p.templateid IS NOT NULL AND p.status != :complete2 LEFT JOIN {" . user_competency_plan::TABLE . "} ucp ON ucp.planid = p.id AND p.status = :complete3 LEFT JOIN {" . competency::TABLE . "} c ON c.id = pc.competencyid OR c.id = tc.competencyid OR c.id = ucp.competencyid LEFT JOIN {" . user_competency::TABLE . "} uc ON uc.userid = p.userid AND (uc.competencyid = pc.competencyid OR uc.competencyid = tc.competencyid) WHERE p.userid = :userid ORDER BY p.id, c.id"; $params = [ 'userid' => $userid, 'complete1' => plan::STATUS_COMPLETE, 'complete2' => plan::STATUS_COMPLETE, 'complete3' => plan::STATUS_COMPLETE, ]; $recordset = $DB->get_recordset_sql($sql, $params); static::recordset_loop_and_export($recordset, 'p_id', null, function($carry, $record) use ($userid, $helper, $context) { $iscomplete = $record->p_status == plan::STATUS_COMPLETE; if ($carry === null) { $plan = new plan(null, plan::extract_record($record, 'p_')); $options = ['context' => $context]; $carry = [ 'name' => format_string($plan->get('name'), true, $options), 'description' => format_text($plan->get('description'), $plan->get('descriptionformat'), $options), 'status' => $plan->get_statusname(), 'duedate' => $plan->get('duedate') ? transform::datetime($plan->get('duedate')) : '-', 'reviewerid' => $plan->get('reviewerid') ? transform::user($plan->get('reviewerid')) : '-', 'timecreated' => transform::datetime($plan->get('timecreated')), 'timemodified' => transform::datetime($plan->get('timemodified')), 'competencies' => [], ]; } // The plan is empty. if (empty($record->c_id)) { return $carry; } $competency = new competency(null, competency::extract_record($record, 'c_')); $rating = null; if ($iscomplete) { // When the plan is complete, we should always found the user_competency_plan. $ucp = new user_competency_plan(null, user_competency_plan::extract_record($record, 'ucp_')); $rating = static::transform_user_competency($userid, $ucp, $competency, $helper); } else if (!empty($record->uc_id)) { // When the plan is complete, there are still records of user_competency but we do not // export them here, we export them as part of the competencies structure. The reason why // we try to get the user_competency when the plan is not complete is to give the most accurate // representation of the plan as possible. $uc = new user_competency(null, user_competency::extract_record($record, 'uc_')); $rating = static::transform_user_competency($userid, $uc, $competency, $helper); } $carry['competencies'][] = array_merge(static::transform_competency_brief($competency), ['rating' => $rating]); return $carry; }, function($planid, $data) use ($context, $path) { $planpath = array_merge($path, [$data['name'] . ' (' . $planid . ')']); \core_comment\privacy\provider::export_comments($context, 'competency', 'plan', $planid, $planpath, false); writer::with_context($context)->export_data($planpath, (object) $data); }); } /** * Export a user's data related to learning plans. * * @param int $userid The user ID we're exporting for. * @param context_user $context The context of the user in which we're gathering data. * @return void */ protected static function export_user_data_learning_plans_related_to_me($userid, context_user $context) { global $DB; $path = [ get_string('competencies', 'core_competency'), get_string('privacy:path:relatedtome', 'core_competency'), get_string('privacy:path:plans', 'core_competency'), ]; $plans = []; $helper = new performance_helper(); $pfields = plan::get_sql_fields('p', 'p_'); $pcfields = plan_competency::get_sql_fields('pc', 'pc_'); $cfields = competency::get_sql_fields('c', 'c_'); $ucpfields = user_competency_plan::get_sql_fields('ucp', 'ucp_'); // Function to initialise a plan record. $initplan = function($record) use ($context, $userid, &$plans) { $plan = new plan(null, plan::extract_record($record, 'p_')); $options = ['context' => $context]; $plans[$plan->get('id')] = [ 'name' => format_string($plan->get('name'), true, $options), 'reviewer_is_you' => transform::yesno($plan->get('reviewerid') == $userid), 'timecreated' => transform::datetime($plan->get('timecreated')), 'timemodified' => transform::datetime($plan->get('timemodified')), 'created_or_modified_by_you' => transform::yesno($plan->get('usermodified') == $userid), 'competencies' => [], ]; }; $initcompetency = function($record, $planid) use (&$plans) { $competency = new competency(null, competency::extract_record($record, 'c_')); $plans[$planid]['competencies'][$competency->get('id')] = static::transform_competency_brief($competency); }; // Look for associations that were created. $sql = " SELECT $pfields, $pcfields, $cfields FROM {" . plan_competency::TABLE . "} pc JOIN {" . plan::TABLE . "} p ON p.id = pc.planid JOIN {" . competency::TABLE . "} c ON c.id = pc.competencyid WHERE p.userid = :targetuserid AND pc.usermodified = :userid ORDER BY p.id, c.id"; $params = [ 'targetuserid' => $context->instanceid, 'userid' => $userid, ]; $recordset = $DB->get_recordset_sql($sql, $params); foreach ($recordset as $record) { $planid = $record->p_id; if (!isset($plans[$planid])) { $initplan($record); } $initcompetency($record, $planid); $pc = new plan_competency(null, plan_competency::extract_record($record, 'pc_')); $plans[$planid]['competencies'][$pc->get('competencyid')] = array_merge( $plans[$planid]['competencies'][$pc->get('competencyid')], [ 'timemodified' => $pc->get('timemodified') ? transform::datetime($pc->get('timemodified')) : '-', 'timecreated' => $pc->get('timecreated') ? transform::datetime($pc->get('timecreated')) : '-', 'created_or_modified_by_you' => transform::yesno($pc->get('usermodified') == $userid), ] ); } $recordset->close(); // Look for final grades that were given. $sql = " SELECT $pfields, $ucpfields, $cfields FROM {" . user_competency_plan::TABLE . "} ucp JOIN {" . plan::TABLE . "} p ON p.id = ucp.planid JOIN {" . competency::TABLE . "} c ON c.id = ucp.competencyid WHERE p.userid = :targetuserid AND ucp.usermodified = :userid ORDER BY p.id, c.id"; $params = [ 'targetuserid' => $context->instanceid, 'userid' => $userid, ]; $recordset = $DB->get_recordset_sql($sql, $params); foreach ($recordset as $record) { $planid = $record->p_id; $competencyid = $record->c_id; if (!isset($plans[$planid])) { $initplan($record); } if (!isset($plans[$planid]['competencies'][$competencyid])) { $initcompetency($record, $planid); } $competency = new competency(null, competency::extract_record($record, 'c_')); $ucp = new user_competency_plan(null, user_competency_plan::extract_record($record, 'ucp_')); $plans[$planid]['competencies'][$competencyid]['rating'] = static::transform_user_competency($userid, $ucp, $competency, $helper); } $recordset->close(); // Find the plans that were modified or reviewed. $insql = " > 0"; $inparams = []; if (!empty($plans)) { list($insql, $inparams) = $DB->get_in_or_equal(array_keys($plans), SQL_PARAMS_NAMED, 'param', false); } $sql = " SELECT $pfields FROM {" . plan::TABLE . "} p LEFT JOIN {comments} c ON c.contextid = :contextid AND c.commentarea = :planarea AND c.component = :competency AND c.itemid = p.id WHERE p.userid = :targetuserid AND (p.usermodified = :userid1 OR p.reviewerid = :userid2 OR c.userid = :userid3) AND p.id $insql ORDER BY p.id"; $params = array_merge($inparams, [ 'targetuserid' => $context->instanceid, 'userid1' => $userid, 'userid2' => $userid, 'userid3' => $userid, 'contextid' => $context->id, 'planarea' => 'plan', 'competency' => 'competency' ]); $recordset = $DB->get_recordset_sql($sql, $params); foreach ($recordset as $record) { $planid = $record->p_id; if (!isset($plans[$planid])) { $initplan($record); } } $recordset->close(); // Export each plan on its own. foreach ($plans as $planid => $plan) { $planpath = array_merge($path, ["{$plan['name']} ({$planid})"]); $plan['competencies'] = array_values($plan['competencies']); // Drop the keys. writer::with_context($context)->export_data($planpath, (object) $plan); \core_comment\privacy\provider::export_comments($context, 'competency', 'plan', $planid, $planpath, true); } } /** * Export a user's data related to competencies. * * @param int $userid The user ID we're exporting for. * @param context_user $context The context of the user in which we're gathering data. * @return void */ protected static function export_user_data_competencies_related_to_me($userid, context_user $context) { global $DB; $path = [ get_string('competencies', 'core_competency'), get_string('privacy:path:relatedtome', 'core_competency'), get_string('competencies', 'core_competency'), ]; $competencies = []; $helper = new performance_helper(); $cfields = competency::get_sql_fields('c', 'c_'); $ucfields = user_competency::get_sql_fields('uc', 'uc_'); $efields = evidence::get_sql_fields('e', 'e_'); $initcompetency = function($record) use (&$competencies) { $competency = new competency(null, competency::extract_record($record, 'c_')); $competencies[$competency->get('id')] = array_merge(static::transform_competency_brief($competency), [ 'evidence' => [] ]); }; $initusercomp = function($competency, $record) use (&$competencies, $userid, $helper) { $competencyid = $competency->get('id'); $uc = new user_competency(null, user_competency::extract_record($record, 'uc_')); $competencies[$competencyid]['uc_id'] = $uc->get('id'); $competencies[$competencyid]['rating'] = static::transform_user_competency($userid, $uc, $competency, $helper); }; // Look for evidence. $sql = " SELECT $efields, $ucfields, $cfields FROM {" . evidence::TABLE . "} e JOIN {" . user_competency::TABLE . "} uc ON uc.id = e.usercompetencyid JOIN {" . competency::TABLE . "} c ON c.id = uc.competencyid WHERE uc.userid = :targetuserid AND (e.usermodified = :userid1 OR e.actionuserid = :userid2) ORDER BY c.id, e.id"; $params = [ 'targetuserid' => $context->instanceid, 'userid1' => $userid, 'userid2' => $userid, ]; $recordset = $DB->get_recordset_sql($sql, $params); foreach ($recordset as $record) { $competencyid = $record->c_id; $competency = new competency(null, competency::extract_record($record, 'c_')); if (!isset($competencies[$competencyid])) { $initcompetency($record); } if (!array_key_exists('rating', $competencies[$competencyid])) { $competencies[$competencyid]['rating'] = null; if ($record->uc_reviewerid == $userid || $record->uc_usermodified == $userid) { $initusercomp($competency, $record); } } $evidence = new evidence(null, evidence::extract_record($record, 'e_')); $competencies[$competencyid]['evidence'][] = static::transform_evidence($userid, $evidence, $competency, $helper); } $recordset->close(); // Look for user competency we modified and didn't catch. $insql = ' > 0'; $inparams = []; if (!empty($competencies)) { list($insql, $inparams) = $DB->get_in_or_equal(array_keys($competencies), SQL_PARAMS_NAMED, 'param', false); } $sql = " SELECT $ucfields, $cfields FROM {" . user_competency::TABLE . "} uc JOIN {" . competency::TABLE . "} c ON c.id = uc.competencyid LEFT JOIN {comments} cmt ON cmt.contextid = :contextid AND cmt.commentarea = :ucarea AND cmt.component = :competency AND cmt.itemid = uc.id WHERE uc.userid = :targetuserid AND (uc.usermodified = :userid1 OR uc.reviewerid = :userid2 OR cmt.userid = :userid3) AND uc.competencyid $insql ORDER BY c.id, uc.id"; $params = array_merge($inparams, [ 'targetuserid' => $context->instanceid, 'userid1' => $userid, 'userid2' => $userid, 'userid3' => $userid, 'contextid' => $context->id, 'ucarea' => 'user_competency', 'competency' => 'competency', ]); $recordset = $DB->get_recordset_sql($sql, $params); foreach ($recordset as $record) { $competency = new competency(null, competency::extract_record($record, 'c_')); if (!isset($competencies[$competency->get('id')])) { $initcompetency($record); $initusercomp($competency, $record); } } $recordset->close(); // Export each competency on its own. foreach ($competencies as $competencyid => $competency) { $comppath = array_merge($path, ["{$competency['name']} ({$competencyid})"]); $ucid = isset($competency['uc_id']) ? $competency['uc_id'] : null; unset($competency['uc_id']); // Send to writer. writer::with_context($context)->export_data($comppath, (object) $competency); if ($ucid) { \core_comment\privacy\provider::export_comments($context, 'competency', 'user_competency', $ucid, $comppath, true); } } } /** * Export a user's data related to evidence of prior learning. * * @param int $userid The user ID we're exporting for. * @param context_user $context The context of the user in which we're gathering data. * @return void */ protected static function export_user_data_user_evidence_related_to_me($userid, context_user $context) { global $DB; $path = [ get_string('competencies', 'core_competency'), get_string('privacy:path:relatedtome', 'core_competency'), get_string('privacy:path:userevidence', 'core_competency'), ]; $evidence = []; $helper = new performance_helper(); $cfields = competency::get_sql_fields('c', 'c_'); $uecfields = user_evidence_competency::get_sql_fields('uec', 'uec_'); $uefields = user_evidence::get_sql_fields('ue', 'ue_'); $initevidence = function($record) use (&$evidence, $userid) { $ue = new user_evidence(null, user_evidence::extract_record($record, 'ue_')); $evidence[$ue->get('id')] = static::transform_user_evidence($userid, $ue); }; // Look for evidence. $sql = " SELECT $uefields, $uecfields, $cfields FROM {" . user_evidence_competency::TABLE . "} uec JOIN {" . user_evidence::TABLE . "} ue ON ue.id = uec.userevidenceid JOIN {" . competency::TABLE . "} c ON c.id = uec.competencyid WHERE ue.userid = :targetuserid AND uec.usermodified = :userid ORDER BY ue.id, c.id"; $params = [ 'targetuserid' => $context->instanceid, 'userid' => $userid, ]; $recordset = $DB->get_recordset_sql($sql, $params); foreach ($recordset as $record) { $ueid = $record->ue_id; if (!isset($evidence[$ueid])) { $initevidence($record); } $competency = new competency(null, competency::extract_record($record, 'c_')); $uec = new user_evidence_competency(null, user_evidence_competency::extract_record($record, 'uec_')); $evidence[$ueid]['competencies'][] = array_merge(static::transform_competency_brief($competency), [ 'timemodified' => $uec->get('timemodified') ? transform::datetime($uec->get('timemodified')) : '-', 'timecreated' => $uec->get('timecreated') ? transform::datetime($uec->get('timecreated')) : '-', 'created_or_modified_by_you' => transform::yesno($uec->get('usermodified')) ]); } $recordset->close(); // Look for user evidence we modified or reviewed and didn't catch. $insql = ' > 0'; $inparams = []; if (!empty($evidence)) { list($insql, $inparams) = $DB->get_in_or_equal(array_keys($evidence), SQL_PARAMS_NAMED, 'param', false); } $sql = " SELECT $uefields FROM {" . user_evidence::TABLE . "} ue WHERE ue.userid = :targetuserid AND ue.usermodified = :userid AND ue.id $insql ORDER BY ue.id"; $params = array_merge($inparams, [ 'targetuserid' => $context->instanceid, 'userid' => $userid, ]); $recordset = $DB->get_recordset_sql($sql, $params); foreach ($recordset as $record) { $initevidence($record); } $recordset->close(); // Export files, then content. foreach ($evidence as $ueid => $data) { $uepath = array_merge($path, ["{$data['name']} ({$ueid})"]); writer::with_context($context)->export_area_files($uepath, 'core_competency', 'userevidence', $ueid); writer::with_context($context)->export_data($uepath, (object) $data); } } /** * Export the evidence of prior learning of a user. * * @param context_user $context The context of the user we're exporting for. * @return void */ protected static function export_user_data_user_evidence(context_user $context) { global $DB; $userid = $context->instanceid; $path = [get_string('competencies', 'core_competency'), get_string('privacy:path:userevidence', 'core_competency')]; $uefields = user_evidence::get_sql_fields('ue', 'ue_'); $cfields = competency::get_sql_fields('c', 'c_'); $sql = " SELECT $uefields, $cfields FROM {" . user_evidence::TABLE . "} ue LEFT JOIN {" . user_evidence_competency::TABLE . "} uec ON uec.userevidenceid = ue.id LEFT JOIN {" . competency::TABLE . "} c ON c.id = uec.competencyid WHERE ue.userid = :userid ORDER BY ue.id"; $params = ['userid' => $userid]; $recordset = $DB->get_recordset_sql($sql, $params); static::recordset_loop_and_export($recordset, 'ue_id', null, function($carry, $record) use ($userid, $context){ if ($carry === null) { $ue = new user_evidence(null, user_evidence::extract_record($record, 'ue_')); $carry = static::transform_user_evidence($userid, $ue); } if (!empty($record->c_id)) { $competency = new competency(null, competency::extract_record($record, 'c_')); $carry['competencies'][] = static::transform_competency_brief($competency); } return $carry; }, function($ueid, $data) use ($context, $path) { $finalpath = array_merge($path, [$data['name'] . ' (' . $ueid . ')']); writer::with_context($context)->export_area_files($finalpath, 'core_competency', 'userevidence', $ueid); writer::with_context($context)->export_data($finalpath, (object) $data); }); } /** * Export the user data related to frameworks in context. * * @param int $userid The user ID. * @param context $context The context. * @return void */ protected static function export_user_data_frameworks_in_context($userid, context $context) { global $DB; $ffields = competency_framework::get_sql_fields('f', 'f_'); $cfields = competency::get_sql_fields('c', 'c_'); $c2fields = competency::get_sql_fields('c2', 'c2_'); $rcfields = related_competency::get_sql_fields('rc', 'rc_'); $frameworks = []; $initframework = function($record) use (&$frameworks, $userid) { $framework = new competency_framework(null, competency_framework::extract_record($record, 'f_')); $frameworks[$framework->get('id')] = array_merge(static::transform_framework_brief($framework), [ 'timemodified' => transform::datetime($framework->get('timemodified')), 'created_or_modified_by_you' => transform::yesno($framework->get('usermodified') == $userid), 'competencies' => [] ]); }; $initcompetency = function($record, $prefix) use (&$frameworks, $userid) { $competency = new competency(null, competency::extract_record($record, $prefix)); $frameworks[$competency->get('competencyframeworkid')]['competencies'][$competency->get('id')] = array_merge( static::transform_competency_brief($competency), [ 'timemodified' => transform::datetime($competency->get('timemodified')), 'created_or_modified_by_you' => transform::yesno($competency->get('usermodified') == $userid), 'related_competencies' => [] ] ); }; // Start by looking for related competencies. $sql = " SELECT $ffields, $cfields, $c2fields, $rcfields FROM {" . related_competency::TABLE . "} rc JOIN {" . competency::TABLE . "} c ON c.id = rc.competencyid JOIN {" . competency::TABLE . "} c2 ON c2.id = rc.relatedcompetencyid JOIN {" . competency_framework::TABLE . "} f ON f.id = c.competencyframeworkid WHERE rc.usermodified = :userid AND f.contextid = :contextid ORDER BY rc.id, c.id"; $params = ['userid' => $userid, 'contextid' => $context->id]; $recordset = $DB->get_recordset_sql($sql, $params); foreach ($recordset as $record) { $frameworkid = $record->f_id; $comp1id = $record->c_id; $comp2id = $record->c2_id; if (!isset($frameworks[$frameworkid])) { $initframework($record); } foreach (['c_', 'c2_'] as $key) { $competencyid = $record->{$key . 'id'}; if (!isset($frameworks[$frameworkid]['competencies'][$competencyid])) { $initcompetency($record, $key); } } $relcomp = new related_competency(null, related_competency::extract_record($record, 'rc_')); foreach (['c_' => 'c2_', 'c2_' => 'c_'] as $key => $relatedkey) { $competencyid = $record->{$key . 'id'}; $competency = new competency(null, competency::extract_record($record, $relatedkey)); $frameworks[$frameworkid]['competencies'][$competencyid]['related_competencies'][] = [ 'name' => $competency->get('shortname'), 'idnumber' => $competency->get('idnumber'), 'timemodified' => transform::datetime($relcomp->get('timemodified')), 'created_or_modified_by_you' => transform::yesno($relcomp->get('usermodified') == $userid), ]; } } $recordset->close(); // Now look for competencies, but skip the ones we've already seen. $competencyids = array_reduce($frameworks, function($carry, $framework) { return array_merge($carry, array_keys($framework['competencies'])); }, []); $insql = ' IS NOT NULL'; $inparams = []; if (!empty($competencyids)) { list($insql, $inparams) = $DB->get_in_or_equal($competencyids, SQL_PARAMS_NAMED, 'param', false); } $sql = " SELECT $ffields, $cfields FROM {" . competency::TABLE . "} c JOIN {" . competency_framework::TABLE . "} f ON f.id = c.competencyframeworkid WHERE c.usermodified = :userid AND f.contextid = :contextid AND c.id $insql ORDER BY c.id"; $params = array_merge($inparams, ['userid' => $userid, 'contextid' => $context->id]); $recordset = $DB->get_recordset_sql($sql, $params); foreach ($recordset as $record) { $frameworkid = $record->f_id; if (!isset($frameworks[$frameworkid])) { $initframework($record); } $initcompetency($record, 'c_'); } $recordset->close(); // Now look for frameworks, but skip the ones we've already seen. $frameworkids = array_keys($frameworks); $insql = ' IS NOT NULL'; $inparams = []; if (!empty($frameworkids)) { list($insql, $inparams) = $DB->get_in_or_equal($frameworkids, SQL_PARAMS_NAMED, 'param', false); } $sql = " SELECT $ffields FROM {" . competency_framework::TABLE . "} f WHERE f.usermodified = :userid AND f.contextid = :contextid AND f.id $insql ORDER BY f.id"; $params = array_merge($inparams, ['userid' => $userid, 'contextid' => $context->id]); $recordset = $DB->get_recordset_sql($sql, $params); foreach ($recordset as $record) { context_helper::preload_from_record($record); $initframework($record); } $recordset->close(); // Export all the things! writer::with_context($context)->export_related_data( [get_string('competencies', 'core_competency')], 'frameworks', (object) [ // Drop the temporary IDs. 'frameworks' => array_reduce($frameworks, function($carry, $item) { $item['competencies'] = array_values($item['competencies']); $carry[] = $item; return $carry; }, []) ] ); } /** * Export the user data related to templates in contexts. * * @param int $userid The user ID. * @param context $context The context. * @return void */ protected static function export_user_data_templates_in_context($userid, context $context) { global $DB; $tfields = template::get_sql_fields('t', 't_'); $cfields = competency::get_sql_fields('c', 'c_'); $tcfields = template_competency::get_sql_fields('tc', 'tc_'); $tchfields = template_cohort::get_sql_fields('tch', 'tch_'); $templates = []; $inittemplate = function($record) use (&$templates, $userid) { $template = new template(null, template::extract_record($record, 't_')); $templates[$template->get('id')] = array_merge(static::transform_template_brief($template), [ 'timemodified' => transform::datetime($template->get('timemodified')), 'created_or_modified_by_you' => transform::yesno($template->get('usermodified') == $userid), 'competencies' => [], 'cohorts' => [] ]); }; // Find the template competencies. $sql = " SELECT $tfields, $cfields, $tcfields FROM {" . template_competency::TABLE . "} tc JOIN {" . template::TABLE . "} t ON t.id = tc.templateid JOIN {" . competency::TABLE . "} c ON c.id = tc.competencyid WHERE t.contextid = :contextid AND tc.usermodified = :userid ORDER BY t.id, tc.id"; $params = ['userid' => $userid, 'contextid' => $context->id]; $recordset = $DB->get_recordset_sql($sql, $params); foreach ($recordset as $record) { $templateid = $record->t_id; if (!isset($templates[$templateid])) { $inittemplate($record); } $tplcomp = new template_competency(null, template_competency::extract_record($record, 'tc_')); $competency = new competency(null, competency::extract_record($record, 'c_')); $templates[$templateid]['competencies'][] = array_merge( static::transform_competency_brief($competency), [ 'timemodified' => transform::datetime($tplcomp->get('timemodified')), 'created_or_modified_by_you' => transform::yesno($tplcomp->get('usermodified') == $userid) ] ); } $recordset->close(); // Find the template cohorts. $sql = " SELECT $tfields, $tchfields, c.name AS cohortname FROM {" . template_cohort::TABLE . "} tch JOIN {" . template::TABLE . "} t ON t.id = tch.templateid JOIN {cohort} c ON c.id = tch.cohortid WHERE t.contextid = :contextid AND tch.usermodified = :userid ORDER BY t.id, tch.id"; $params = ['userid' => $userid, 'contextid' => $context->id]; $recordset = $DB->get_recordset_sql($sql, $params); foreach ($recordset as $record) { $templateid = $record->t_id; if (!isset($templates[$templateid])) { $inittemplate($record); } $tplcohort = new template_cohort(null, template_cohort::extract_record($record, 'tch_')); $templates[$templateid]['cohorts'][] = [ 'name' => $record->cohortname, 'timemodified' => transform::datetime($tplcohort->get('timemodified')), 'created_or_modified_by_you' => transform::yesno($tplcohort->get('usermodified') == $userid) ]; } $recordset->close(); // Find the modified templates which we haven't been found yet. $templateids = array_keys($templates); $insql = "IS NOT NULL"; $inparams = []; if (!empty($templateids)) { list($insql, $inparams) = $DB->get_in_or_equal($templateids, SQL_PARAMS_NAMED, 'param', false); } $sql = " SELECT $tfields FROM {" . template::TABLE . "} t WHERE t.contextid = :contextid AND t.usermodified = :userid AND t.id $insql ORDER BY t.id"; $params = array_merge($inparams, ['userid' => $userid, 'contextid' => $context->id]); $recordset = $DB->get_recordset_sql($sql, $params); foreach ($recordset as $record) { $inittemplate($record); } $recordset->close(); // Export all the things! writer::with_context($context)->export_related_data([get_string('competencies', 'core_competency')], 'templates', (object) ['templates' => array_values($templates)]); } /** * Transform a competency into a brief description. * * @param competency $competency The competency. * @return array */ protected static function transform_competency_brief(competency $competency) { global $OUTPUT; $exporter = new \core_competency\external\competency_exporter($competency, ['context' => $competency->get_context()]); $data = $exporter->export($OUTPUT); return [ 'idnumber' => $data->idnumber, 'name' => $data->shortname, 'description' => $data->description ]; } /** * Transform a competency rating. * * @param competency $competency The competency. * @param int $grade The grade. * @param performance_helper $helper The performance helper. * @return string */ protected static function transform_competency_grade(competency $competency, $grade, performance_helper $helper) { if ($grade === null) { return '-'; } $scale = $helper->get_scale_from_competency($competency); return $scale->scale_items[$grade - 1]; } /** * Transform an evidence. * * @param int $userid The user ID we are exporting for. * @param evidence $evidence The evidence. * @param competency $competency The associated competency. * @param performance_helper $helper The performance helper. * @return array */ protected static function transform_evidence($userid, evidence $evidence, competency $competency, performance_helper $helper) { $action = $evidence->get('action'); $actiontxt = '?'; if ($action == evidence::ACTION_LOG) { $actiontxt = get_string('privacy:evidence:action:log', 'core_competency'); } else if ($action == evidence::ACTION_COMPLETE) { $actiontxt = get_string('privacy:evidence:action:complete', 'core_competency'); } else if ($action == evidence::ACTION_OVERRIDE) { $actiontxt = get_string('privacy:evidence:action:override', 'core_competency'); } $actionuserid = $evidence->get('actionuserid'); return [ 'action' => $actiontxt, 'actionuserid' => $actionuserid ? transform::user($actionuserid) : '-', 'acting_user_is_you' => transform::yesno($userid == $actionuserid), 'description' => (string) $evidence->get_description(), 'url' => $evidence->get('url'), 'grade' => static::transform_competency_grade($competency, $evidence->get('grade'), $helper), 'note' => $evidence->get('note'), 'timecreated' => transform::datetime($evidence->get('timecreated')), 'timemodified' => transform::datetime($evidence->get('timemodified')), 'created_or_modified_by_you' => transform::yesno($userid == $evidence->get('usermodified')) ]; } /** * Transform a framework into a brief description. * * @param competency_framework $framework The framework. * @return array */ protected static function transform_framework_brief(competency_framework $framework) { global $OUTPUT; $exporter = new \core_competency\external\competency_framework_exporter($framework); $data = $exporter->export($OUTPUT); return [ 'name' => $data->shortname, 'idnumber' => $data->idnumber, 'description' => $data->description ]; } /** * Transform a template into a brief description. * * @param template $template The Template. * @return array */ protected static function transform_template_brief(template $template) { global $OUTPUT; $exporter = new \core_competency\external\template_exporter($template); $data = $exporter->export($OUTPUT); return [ 'name' => $data->shortname, 'description' => $data->description ]; } /** * Transform proficiency. * * @param null|bool $proficiency The proficiency. * @return string */ protected static function transform_proficiency($proficiency) { return $proficiency !== null ? transform::yesno($proficiency) : '-'; } /** * Transform user competency. * * @param int $userid The user ID we are exporting for. * @param user_competency|user_competency_plan|user_competency_course $uc The user competency. * @param competency $competency The associated competency. * @param performance_helper $helper The performance helper. * @return array */ protected static function transform_user_competency($userid, $uc, competency $competency, performance_helper $helper) { $data = [ 'proficient' => static::transform_proficiency($uc->get('proficiency')), 'rating' => static::transform_competency_grade($competency, $uc->get('grade'), $helper), 'timemodified' => $uc->get('timemodified') ? transform::datetime($uc->get('timemodified')) : '-', 'timecreated' => $uc->get('timecreated') ? transform::datetime($uc->get('timecreated')) : '-', 'created_or_modified_by_you' => transform::yesno($uc->get('usermodified') == $userid), ]; if ($uc instanceof user_competency) { $reviewer = $uc->get('reviewerid'); $data['status'] = (string) user_competency::get_status_name($uc->get('status')); $data['reviewerid'] = $reviewer ? transform::user($reviewer) : '-'; $data['reviewer_is_you'] = transform::yesno($reviewer == $userid); } return $data; } /** * Transform a user evidence. * * @param int $userid The user we are exporting for. * @param user_evidence $ue The evidence of prior learning. * @return array */ protected static function transform_user_evidence($userid, user_evidence $ue) { $options = ['context' => $ue->get_context()]; return [ 'name' => format_string($ue->get('name'), true, $options), 'description' => format_text($ue->get('description'), $ue->get('descriptionformat'), $options), 'url' => $ue->get('url'), 'timecreated' => $ue->get('timecreated') ? transform::datetime($ue->get('timecreated')) : '-', 'timemodified' => $ue->get('timemodified') ? transform::datetime($ue->get('timemodified')) : '-', 'created_or_modified_by_you' => transform::yesno($ue->get('usermodified') == $userid), 'competencies' => [] ]; } /** * Loop and export from a recordset. * * @param moodle_recordset $recordset The recordset. * @param string $splitkey The record key to determine when to export. * @param mixed $initial The initial data to reduce from. * @param callable $reducer The function to return the dataset, receives current dataset, and the current record. * @param callable $export The function to export the dataset, receives the last value from $splitkey and the dataset. * @return void */ protected static function recordset_loop_and_export(moodle_recordset $recordset, $splitkey, $initial, callable $reducer, callable $export) { $data = $initial; $lastid = null; foreach ($recordset as $record) { if ($lastid && $record->{$splitkey} != $lastid) { $export($lastid, $data); $data = $initial; } $data = $reducer($data, $record); $lastid = $record->{$splitkey}; } $recordset->close(); if (!empty($lastid)) { $export($lastid, $data); } } } classes/competency_framework.php000064400000037457152265256010013161 0ustar00. /** * Class for loading/storing competency frameworks from the DB. * * @package core_competency * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_competency; defined('MOODLE_INTERNAL') || die(); use coding_exception; use context; use lang_string; use stdClass; require_once($CFG->libdir . '/grade/grade_scale.php'); /** * Class for loading/storing competency frameworks from the DB. * * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class competency_framework extends persistent { const TABLE = 'competency_framework'; /** Taxonomy constant. */ const TAXONOMY_BEHAVIOUR = 'behaviour'; /** Taxonomy constant. */ const TAXONOMY_COMPETENCY = 'competency'; /** Taxonomy constant. */ const TAXONOMY_CONCEPT = 'concept'; /** Taxonomy constant. */ const TAXONOMY_DOMAIN = 'domain'; /** Taxonomy constant. */ const TAXONOMY_INDICATOR = 'indicator'; /** Taxonomy constant. */ const TAXONOMY_LEVEL = 'level'; /** Taxonomy constant. */ const TAXONOMY_OUTCOME = 'outcome'; /** Taxonomy constant. */ const TAXONOMY_PRACTICE = 'practice'; /** Taxonomy constant. */ const TAXONOMY_PROFICIENCY = 'proficiency'; /** Taxonomy constant. */ const TAXONOMY_SKILL = 'skill'; /** Taxonomy constant. */ const TAXONOMY_VALUE = 'value'; /** @var static The object before it was updated. */ protected $beforeupdate; /** * Get the context. * * @return \context The context */ public function get_context() { return context::instance_by_id($this->get('contextid')); } /** * Return the definition of the properties of this model. * * @return array */ protected static function define_properties() { return array( 'shortname' => array( 'type' => PARAM_TEXT ), 'idnumber' => array( 'type' => PARAM_RAW ), 'description' => array( 'type' => PARAM_CLEANHTML, 'default' => '' ), 'descriptionformat' => array( 'choices' => array(FORMAT_HTML, FORMAT_MOODLE, FORMAT_PLAIN, FORMAT_MARKDOWN), 'type' => PARAM_INT, 'default' => FORMAT_HTML ), 'visible' => array( 'type' => PARAM_BOOL, 'default' => 1 ), 'scaleid' => array( 'type' => PARAM_INT ), 'scaleconfiguration' => array( 'type' => PARAM_RAW ), 'contextid' => array( 'type' => PARAM_INT ), 'taxonomies' => array( 'type' => PARAM_RAW, 'default' => '' ) ); } /** * Hook to execute before validate. * * @return void */ protected function before_validate() { $this->beforeupdate = null; // During update. if ($this->get('id')) { $this->beforeupdate = new competency_framework($this->get('id')); } } /** * Return the current depth of a competency framework. * * @see competency::get_framework_depth() * @return int */ public function get_depth() { return competency::get_framework_depth($this->get('id')); } /** * Return the scale. * * @return \grade_scale */ public function get_scale() { $scale = \grade_scale::fetch(array('id' => $this->get('scaleid'))); $scale->load_items(); return $scale; } /** * Get the constant name for a level. * * @param int $level The level of the term. * @return string */ public function get_taxonomy($level) { $taxonomies = $this->get_taxonomies(); if (empty($taxonomies[$level])) { // If for some reason we cannot find the level, we fallback onto competency. $constant = self::TAXONOMY_COMPETENCY; } else { $constant = $taxonomies[$level]; } return $constant; } /** * Return the taxonomy constants indexed by level. * * @return array Contains the list of taxonomy constants indexed by level. */ protected function get_taxonomies() { $taxonomies = explode(',', $this->raw_get('taxonomies')); // Indexing first level at 1. array_unshift($taxonomies, null); unset($taxonomies[0]); // Ensure that we do not return empty levels. foreach ($taxonomies as $i => $taxonomy) { if (empty($taxonomy)) { $taxonomies[$i] = self::TAXONOMY_COMPETENCY; } } return $taxonomies; } /** * Returns true when some competencies of the framework have user competencies. * * This is useful to determine if the framework, or part of it, should be locked down. * * @return boolean */ public function has_user_competencies() { return user_competency::has_records_for_framework($this->get('id')) || user_competency_plan::has_records_for_framework($this->get('id')); } /** * Convenience method to set taxonomies from an array or string. * * @param string|array $taxonomies A string, or an array where the values are the term constants. */ protected function set_taxonomies($taxonomies) { if (is_array($taxonomies)) { $taxonomies = implode(',', $taxonomies); } $this->raw_set('taxonomies', $taxonomies); } /** * Validate the context ID. * * @param int $value The context ID. * @return bool|lang_string */ protected function validate_contextid($value) { global $DB; $context = context::instance_by_id($value, IGNORE_MISSING); if (!$context) { return new lang_string('invalidcontext', 'error'); } else if ($context->contextlevel != CONTEXT_SYSTEM && $context->contextlevel != CONTEXT_COURSECAT) { return new lang_string('invalidcontext', 'error'); } // During update. if ($this->get('id')) { // The context must never change. $oldcontextid = $DB->get_field(self::TABLE, 'contextid', array('id' => $this->get('id')), MUST_EXIST); if ($this->get('contextid') != $oldcontextid) { return new lang_string('invalidcontext', 'error'); } } return true; } /** * Validate the id number. * * @param string $value The id number. * @return bool|lang_string */ protected function validate_idnumber($value) { global $DB; $params = array( 'id' => $this->get('id'), 'idnumber' => $value, ); if ($DB->record_exists_select(self::TABLE, 'idnumber = :idnumber AND id <> :id', $params)) { return new lang_string('idnumbertaken', 'error'); } return true; } /** * Validate the scale ID. * * @param string $value The scale ID. * @return bool|lang_string */ protected function validate_scaleid($value) { global $DB; // Always validate that the scale exists. if (!$DB->record_exists_select('scale', 'id = :id', array('id' => $value))) { return new lang_string('invalidscaleid', 'error'); } // During update. if ($this->get('id')) { // Validate that we can only change the scale when it is not used yet. if ($this->beforeupdate->get('scaleid') != $value) { if ($this->beforeupdate->has_user_competencies()) { return new lang_string('errorscalealreadyused', 'core_competency'); } } } return true; } /** * Validate the scale configuration. * * @param string $value The scale configuration. * @return bool|lang_string */ protected function validate_scaleconfiguration($value) { $scaledefaultselected = false; $proficientselected = false; $scaleconfigurations = json_decode($value); if (is_array($scaleconfigurations)) { // The first element of the array contains the scale ID. $scaleinfo = array_shift($scaleconfigurations); if (empty($scaleinfo) || !isset($scaleinfo->scaleid) || $scaleinfo->scaleid != $this->get('scaleid')) { // This should never happen. return new lang_string('errorscaleconfiguration', 'core_competency'); } // Walk through the array to find proficient and default values. foreach ($scaleconfigurations as $scaleconfiguration) { if (isset($scaleconfiguration->scaledefault) && $scaleconfiguration->scaledefault) { $scaledefaultselected = true; } if (isset($scaleconfiguration->proficient) && $scaleconfiguration->proficient) { $proficientselected = true; } } } if (!$scaledefaultselected || !$proficientselected) { return new lang_string('errorscaleconfiguration', 'core_competency'); } return true; } /** * Validate taxonomies. * * @param mixed $value The taxonomies. * @return true|lang_string */ protected function validate_taxonomies($value) { $terms = explode(',', $value); foreach ($terms as $term) { if (!empty($term) && !array_key_exists($term, self::get_taxonomies_list())) { return new lang_string('invalidtaxonomy', 'core_competency', $term); } } return true; } /** * Extract the default grade from a scale configuration. * * Returns an array where the first element is the grade, and the second * is a boolean representing whether or not this grade is considered 'proficient'. * * @param string $config JSON encoded config. * @return array(int grade, int proficient) */ public static function get_default_grade_from_scale_configuration($config) { $config = json_decode($config); if (!is_array($config)) { throw new coding_exception('Unexpected scale configuration.'); } // Remove the scale ID from the config. array_shift($config); foreach ($config as $part) { if ($part->scaledefault) { return array((int) $part->id, (int) $part->proficient); } } throw new coding_exception('Invalid scale configuration, default not found.'); } /** * Extract the proficiency of a grade from a scale configuration. * * @param string $config JSON encoded config. * @param int $grade The grade. * @return int Representing a boolean */ public static function get_proficiency_of_grade_from_scale_configuration($config, $grade) { $config = json_decode($config); if (!is_array($config)) { throw new coding_exception('Unexpected scale configuration.'); } // Remove the scale ID from the config. array_shift($config); foreach ($config as $part) { if ($part->id == $grade) { return (int) $part->proficient; } } return 0; } /** * Get the string of a taxonomy from a constant * * @param string $constant The taxonomy constant. * @return lang_string */ public static function get_taxonomy_from_constant($constant) { return self::get_taxonomies_list()[$constant]; } /** * Get the list of all taxonomies. * * @return array Where the key is the taxonomy constant, and the value its translation. */ public static function get_taxonomies_list() { static $list = null; // At some point we'll have to switch to not using static cache, mainly for Unit Tests in case we // decide to allow more taxonomies to be added dynamically from a CFG variable for instance. if ($list === null) { $list = array( self::TAXONOMY_BEHAVIOUR => new lang_string('taxonomy_' . self::TAXONOMY_BEHAVIOUR, 'core_competency'), self::TAXONOMY_COMPETENCY => new lang_string('taxonomy_' . self::TAXONOMY_COMPETENCY, 'core_competency'), self::TAXONOMY_CONCEPT => new lang_string('taxonomy_' . self::TAXONOMY_CONCEPT, 'core_competency'), self::TAXONOMY_DOMAIN => new lang_string('taxonomy_' . self::TAXONOMY_DOMAIN, 'core_competency'), self::TAXONOMY_INDICATOR => new lang_string('taxonomy_' . self::TAXONOMY_INDICATOR, 'core_competency'), self::TAXONOMY_LEVEL => new lang_string('taxonomy_' . self::TAXONOMY_LEVEL, 'core_competency'), self::TAXONOMY_OUTCOME => new lang_string('taxonomy_' . self::TAXONOMY_OUTCOME, 'core_competency'), self::TAXONOMY_PRACTICE => new lang_string('taxonomy_' . self::TAXONOMY_PRACTICE, 'core_competency'), self::TAXONOMY_PROFICIENCY => new lang_string('taxonomy_' . self::TAXONOMY_PROFICIENCY, 'core_competency'), self::TAXONOMY_SKILL => new lang_string('taxonomy_' . self::TAXONOMY_SKILL, 'core_competency'), self::TAXONOMY_VALUE => new lang_string('taxonomy_' . self::TAXONOMY_VALUE, 'core_competency'), ); } return $list; } /** * Get a uniq idnumber. * * @param string $idnumber the framework idnumber * @return string */ public static function get_unused_idnumber($idnumber) { global $DB; $currentidnumber = $idnumber; $counter = 0; // Iteratere while the idnumber exists. while ($DB->record_exists_select(static::TABLE, 'idnumber = ?', array($currentidnumber))) { $suffixidnumber = '_' . ++$counter; $currentidnumber = substr($idnumber, 0, 100 - strlen($suffixidnumber)).$suffixidnumber; } // Return the uniq idnumber. return $currentidnumber; } /** * Whether or not the current user can manage the framework. * * @return bool */ public function can_manage() { return self::can_manage_context($this->get_context()); } /** * Whether or not the current user can manage the framework. * * @param context $context * @return bool */ public static function can_manage_context($context) { return has_capability('moodle/competency:competencymanage', $context); } /** * Whether or not the current user can read the framework. * * @return bool */ public function can_read() { return self::can_read_context($this->get_context()); } /** * Whether or not the current user can read the framework. * * @param context $context * @return bool */ public static function can_read_context($context) { return has_capability('moodle/competency:competencyview', $context) || self::can_manage_context($context); } } classes/related_competency.php000064400000016347152265256010012577 0ustar00. /** * Class for loading/storing related competencies from the DB. * * @package core_competency * @copyright 2015 David Monllao * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_competency; defined('MOODLE_INTERNAL') || die(); use lang_string; use stdClass; /** * Class for loading/storing related_competencies from the DB. * * @package core_competency * @copyright 2015 David Monllao * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class related_competency extends persistent { const TABLE = 'competency_relatedcomp'; /** * Return the definition of the properties of this model. * * @return array */ protected static function define_properties() { return array( 'competencyid' => array( 'type' => PARAM_INT ), 'relatedcompetencyid' => array( 'type' => PARAM_INT ), ); } /** * Validate competency ID. * * @param int $data The competency ID. * @return true|lang_string */ protected function validate_competencyid($data) { if (!competency::record_exists($data)) { return new lang_string('invaliddata', 'error'); } return true; } /** * Validate related competency ID. * * @param int $data The related competency ID. * @return true|lang_string */ protected function validate_relatedcompetencyid($data) { if ($this->get('competencyid') == $data) { // A competency cannot be related to itself. return new lang_string('invaliddata', 'error'); } if ($this->get('competencyid') > $data) { // The competency ID must be lower than the related competency ID. return new lang_string('invaliddata', 'error'); } else if (!competency::record_exists($data)) { return new lang_string('invaliddata', 'error'); } else if (!competency::share_same_framework(array($data, $this->get('competencyid')))) { // The competencies must belong to the same framework. return new lang_string('invaliddata', 'error'); } return true; } /** * Get relation specifying both competencies. * * This does not perform any validation on the data passed. If the relation exists in the database * then it is loaded in a the model, if not then it is up to the developer to save the model. * * @param int $competencyid * @param int $relatedcompetencyid * @return related_competency */ public static function get_relation($competencyid, $relatedcompetencyid) { global $DB; // Lower id always as competencyid so we know which one is competencyid and which one relatedcompetencyid. $relation = new static(); if ($competencyid > $relatedcompetencyid) { $relation->set('competencyid', $relatedcompetencyid); $relation->set('relatedcompetencyid', $competencyid); } else { $relation->set('competencyid', $competencyid); $relation->set('relatedcompetencyid', $relatedcompetencyid); } // We can do it because we have bidirectional relations in the DB. $params = array( 'competencyid' => $relation->get('competencyid'), 'relatedcompetencyid' => $relation->get('relatedcompetencyid') ); if ($record = $DB->get_record(self::TABLE, $params)) { $relation->from_record($record); } return $relation; } /** * Get the competencies related to a competency. * * @param int $competencyid The competency ID. * @return competency[] */ public static function get_related_competencies($competencyid) { global $DB; $fields = competency::get_sql_fields('c', 'c_'); $sql = "(SELECT $fields, " . $DB->sql_concat('rc.relatedcompetencyid', "'_'", 'rc.competencyid') . " AS rid FROM {" . self::TABLE . "} rc JOIN {" . competency::TABLE . "} c ON c.id = rc.relatedcompetencyid WHERE rc.competencyid = :cid) UNION ALL (SELECT $fields, " . $DB->sql_concat('rc.competencyid', "'_'", 'rc.relatedcompetencyid') . " AS rid FROM {" . self::TABLE . "} rc JOIN {" . competency::TABLE . "} c ON c.id = rc.competencyid WHERE rc.relatedcompetencyid = :cid2) ORDER BY c_path ASC, c_sortorder ASC"; $competencies = array(); $records = $DB->get_recordset_sql($sql, array('cid' => $competencyid, 'cid2' => $competencyid)); foreach ($records as $record) { unset($record->rid); $competencies[$record->c_id] = new competency(null, competency::extract_record($record, 'c_')); } $records->close(); return $competencies; } /** * Get the related competencies from competency ids. * * @param int[] $competencyids Array of competency ids. * @return related_competency[] */ public static function get_multiple_relations($competencyids) { global $DB; if (empty($competencyids)) { return array(); } list($insql, $params) = $DB->get_in_or_equal($competencyids); $records = $DB->get_records_select(self::TABLE, "competencyid $insql OR relatedcompetencyid $insql", array_merge($params, $params) ); $relatedcompetencies = array(); foreach ($records as $record) { unset($record->id); $relatedcompetencies[] = new related_competency(null, $record); } return $relatedcompetencies; } /** * Delete relations using competencies. * * @param array $competencyids Array of competencies ids. * @return bool True if relations were deleted successfully. */ public static function delete_multiple_relations($competencyids) { global $DB; if (empty($competencyids)) { return true; } list($insql, $params) = $DB->get_in_or_equal($competencyids); return $DB->delete_records_select(self::TABLE, "competencyid $insql OR relatedcompetencyid $insql", array_merge($params, $params) ); } } classes/persistent.php000064400000003553152265256010011124 0ustar00. namespace core_competency; /** * Abstract class for core_competency objects saved to the DB. * * This is a legacy class which all core_competency persistent classes created prior * to 3.3 must extend. * * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ abstract class persistent extends \core\persistent { /** * Magic method to capture getters and setters. * This is only available for competency persistents for backwards compatibility. * It is recommended to use get('propertyname') and set('propertyname', 'value') directly. * * @param string $method Callee. * @param array $arguments List of arguments. * @return mixed */ final public function __call($method, $arguments) { debugging('Use of magic setters and getters is deprecated. Use get() and set().', DEBUG_DEVELOPER); if (strpos($method, 'get_') === 0) { return $this->get(substr($method, 4)); } else if (strpos($method, 'set_') === 0) { return $this->set(substr($method, 4), $arguments[0]); } throw new \coding_exception('Unexpected method call: ' . $method); } } classes/template_cohort.php000064400000020056152265256010012112 0ustar00. /** * Template cohort persistent. * * @package core_competency * @copyright 2015 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_competency; defined('MOODLE_INTERNAL') || die(); use lang_string; use core_competency\template; /** * Template cohort persistent. * * @package core_competency * @copyright 2015 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class template_cohort extends persistent { const TABLE = 'competency_templatecohort'; /** 2 hours of threshold to prevent expired plans **/ const DUEDATE_THRESHOLD = 7200; /** * Return the custom definition of the properties of this model. * * @return array Where keys are the property names. */ protected static function define_properties() { return array( 'templateid' => array( 'type' => PARAM_INT ), 'cohortid' => array( 'type' => PARAM_INT ) ); } /** * Validate the cohort ID. * * @param int $value The cohort ID. * @return true|lang_string */ protected function validate_cohortid($value) { global $DB; if (!$DB->record_exists('cohort', array('id' => $value))) { return new lang_string('invaliddata', 'error'); } return true; } /** * Validate the template ID. * * @param int $value The template ID. * @return true|lang_string */ protected function validate_templateid($value) { if (!template::record_exists($value)) { return new lang_string('invaliddata', 'error'); } return true; } /** * Return an array of user IDs for which the plans are missing. * * Plans are considered as missing when a member of a cohort does not have a plan created. * When the parameter $unlinkedaremissing is set to false, plans that were unlinked from * their template will be ignored so that we do not recreate unlinked plans endlessly. * * This method ignores the due date of the template. * * @param int $templateid The template ID. * @param int $cohortid The cohort ID. * @param boolean $unlinkedaremissing When true, unlinked plans are considered as missing. * @return int[] User IDs. */ public static function get_missing_plans($templateid, $cohortid, $unlinkedaremissing = false) { global $DB; $skipsql = ''; $skipparams = array(); if (!$unlinkedaremissing) { $skipsql = 'OR p.origtemplateid = :origtemplateid'; $skipparams = array('origtemplateid' => $templateid); } $sql = "SELECT cm.userid FROM {cohort_members} cm LEFT JOIN {" . plan::TABLE . "} p ON p.userid = cm.userid AND (p.templateid = :templateid $skipsql) WHERE cm.cohortid = :cohortid AND p.id IS NULL"; $params = array('templateid' => $templateid, 'cohortid' => $cohortid) + $skipparams; return $DB->get_fieldset_sql($sql, $params); } /** * Get a relation. * * This does not perform any validation on the data passed. If the relation exists in the database * then it is loaded in a the model, if not then it is up to the developer to save the model. * * @param int $templateid * @param int $cohortid * @return template_cohort */ public static function get_relation($templateid, $cohortid) { global $DB; $params = array( 'templateid' => $templateid, 'cohortid' => $cohortid ); $relation = new static(null, (object) $params); if ($record = $DB->get_record(self::TABLE, $params)) { $relation->from_record($record); } return $relation; } /** * Get a relations by templateid. * * This does not perform any validation on the data passed. If the relation exists in the database * then it is loaded in a the model, if not then it is up to the developer to save the model. * * @param int $templateid * @return template_cohort[] array of template cohort */ public static function get_relations_by_templateid($templateid) { global $DB; $params = array( 'templateid' => $templateid ); $relations = array(); $records = $DB->get_records(self::TABLE, $params); foreach ($records as $record) { $relations[] = new template_cohort(0, $record); } return $relations; } /** * Return an array of templates persistent with their missing userids. * * Note that only cohorts associated with visible templates are considered, * as well as only templates with a due date in the future, or no due date. * * @param int $lastruntime The last time the Cohort ssync task ran. * @param bool $unlinkedaremissing When true, unlinked plans are considered as missing. * @return array( array( * 'template' => \core_competency\template, * 'userids' => array * )) */ public static function get_all_missing_plans($lastruntime = 0, $unlinkedaremissing = false) { global $DB; $planwhereclause = " WHERE (p.id is NULL AND (cm.timeadded >= :lastruntime1 OR tc.timecreated >= :lastruntime3 OR t.timemodified >= :lastruntime4))"; if ($unlinkedaremissing) { $planwhereclause .= " OR (p.origtemplateid IS NOT NULL AND cm.timeadded < :lastruntime2)"; } $sql = "SELECT " . $DB->sql_concat('cm.userid', 'tc.templateid') . " as uniqueid, cm.userid, t.* FROM {cohort_members} cm JOIN {" . self::TABLE . "} tc ON cm.cohortid = tc.cohortid JOIN {" . template::TABLE . "} t ON (tc.templateid = t.id AND t.visible = 1) AND (t.duedate = 0 OR t.duedate > :time1) LEFT JOIN {" . plan::TABLE . "} p ON (cm.userid = p.userid AND (t.id = p.templateid OR t.id = p.origtemplateid)) $planwhereclause ORDER BY t.id"; $params = array('time1' => time(), 'time2' => time(), 'lastruntime1' => $lastruntime, 'lastruntime2' => $lastruntime, 'lastruntime3' => $lastruntime, 'lastruntime4' => $lastruntime); $results = $DB->get_records_sql($sql, $params); $missingplans = array(); foreach ($results as $usertemplate) { $userid = $usertemplate->userid; // Check if template already exist in the array. if (isset($missingplans[$usertemplate->id])) { $missingplans[$usertemplate->id]['userids'][] = $userid; } else { unset($usertemplate->userid); unset($usertemplate->uniqueid); $template = new template(0, $usertemplate); $missingplans[$template->get('id')]['template'] = $template; $missingplans[$template->get('id')]['userids'][] = $userid; } } return array_values($missingplans); } } classes/user_evidence.php000064400000013237152265256010011544 0ustar00. /** * User evidence persistent. * * @package core_competency * @copyright 2015 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_competency; defined('MOODLE_INTERNAL') || die(); use context_user; use lang_string; /** * User evidence persistent class. * * @package core_competency * @copyright 2015 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class user_evidence extends persistent { const TABLE = 'competency_userevidence'; /** * Return the definition of the properties of this model. * * @return array */ protected static function define_properties() { return array( 'userid' => array( 'type' => PARAM_INT ), 'name' => array( 'type' => PARAM_TEXT ), 'description' => array( 'type' => PARAM_CLEANHTML, 'default' => '', ), 'descriptionformat' => array( 'choices' => array(FORMAT_HTML, FORMAT_MOODLE, FORMAT_PLAIN, FORMAT_MARKDOWN), 'type' => PARAM_INT, 'default' => FORMAT_HTML, ), 'url' => array( 'type' => PARAM_URL, 'default' => '', 'message' => new lang_string('invalidurl', 'core_competency') ) ); } /** * Can the current user manage this user evidence? * * @return bool */ public function can_manage() { return self::can_manage_user($this->get('userid')); } /** * Can the current user view this user evidence? * * @return bool */ public function can_read() { return self::can_read_user($this->get('userid')); } /** * Get the context of this user evidence. * * @return context */ public function get_context() { return context_user::instance($this->get('userid')); } /** * Get link competencies. */ public function get_competencies() { return user_evidence_competency::get_competencies_by_userevidenceid($this->get('id')); } /** * Get link user competencies. */ public function get_user_competencies() { return user_evidence_competency::get_user_competencies_by_userevidenceid($this->get('id')); } /** * Return true if the user of the evidence has plan. * * @return bool */ public function user_has_plan() { return plan::record_exists_select('userid = ?', array($this->get('userid'))); } /** * Return the files associated with this evidence. * * @return object[] */ public function get_files() { $fs = get_file_storage(); $files = $fs->get_area_files($this->get_context()->id, 'core_competency', 'userevidence', $this->get('id'), 'filename', false); return $files; } /** * Validate the URL. * * @param int $value * @return true|lang_string */ protected function validate_url($value) { if (empty($value) && !is_numeric($value)) { return true; } if (!preg_match('@^https?://.+@', $value)) { return new lang_string('invalidurl', 'core_competency'); } return true; } /** * Validate the user ID. * * @param int $value * @return true|lang_string */ protected function validate_userid($value) { global $DB; // During create. if (!$this->get('id')) { // Check that the user exists. We do not need to do that on update because // the userid of an evidence should never change. if (!$DB->record_exists('user', array('id' => $value))) { return new lang_string('invaliddata', 'error'); } } return true; } /** * Can the current user manage a user's evidence? * * @param int $evidenceuserid The user to whom the evidence would belong. * @return bool */ public static function can_manage_user($evidenceuserid) { global $USER; $context = context_user::instance($evidenceuserid); $capabilities = array('moodle/competency:userevidencemanage'); if ($context->instanceid == $USER->id) { $capabilities[] = 'moodle/competency:userevidencemanageown'; } return has_any_capability($capabilities, $context); } /** * Can the current user view a user's evidence? * * @param int $evidenceuserid The user to whom the evidence would belong. * @return bool */ public static function can_read_user($evidenceuserid) { $context = context_user::instance($evidenceuserid); $capabilities = array('moodle/competency:userevidenceview'); return has_any_capability($capabilities, $context) || self::can_manage_user($evidenceuserid); } } classes/plan.php000064400000053027152265256010007657 0ustar00. /** * Class for plans persistence. * * @package core_competency * @copyright 2015 David Monllao * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_competency; defined('MOODLE_INTERNAL') || die(); use comment; use context_user; use dml_missing_record_exception; use lang_string; /** * Class for loading/storing plans from the DB. * * @copyright 2015 David Monllao * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class plan extends persistent { const TABLE = 'competency_plan'; /** Draft status. */ const STATUS_DRAFT = 0; /** Active status. */ const STATUS_ACTIVE = 1; /** Complete status. */ const STATUS_COMPLETE = 2; /** Waiting for review. */ const STATUS_WAITING_FOR_REVIEW = 3; /** In review. */ const STATUS_IN_REVIEW = 4; /** 10 minutes threshold **/ const DUEDATE_THRESHOLD = 600; /** @var plan object before update. */ protected $beforeupdate = null; /** * Return the definition of the properties of this model. * * @return array */ protected static function define_properties() { return array( 'name' => array( 'type' => PARAM_TEXT, ), 'description' => array( 'type' => PARAM_CLEANHTML, 'default' => '' ), 'descriptionformat' => array( 'choices' => array(FORMAT_HTML, FORMAT_MOODLE, FORMAT_PLAIN, FORMAT_MARKDOWN), 'type' => PARAM_INT, 'default' => FORMAT_HTML, ), 'userid' => array( 'type' => PARAM_INT, ), 'templateid' => array( 'type' => PARAM_INT, 'default' => null, 'null' => NULL_ALLOWED, ), 'origtemplateid' => array( 'type' => PARAM_INT, 'default' => null, 'null' => NULL_ALLOWED, ), 'status' => array( 'choices' => array(self::STATUS_DRAFT, self::STATUS_COMPLETE, self::STATUS_ACTIVE, self::STATUS_WAITING_FOR_REVIEW, self::STATUS_IN_REVIEW), 'type' => PARAM_INT, 'default' => self::STATUS_DRAFT, ), 'duedate' => array( 'type' => PARAM_INT, 'default' => 0, ), 'reviewerid' => array( 'type' => PARAM_INT, 'default' => null, 'null' => NULL_ALLOWED, ) ); } /** * Hook to execute before validate. * * @return void */ protected function before_validate() { $this->beforeupdate = null; // During update. if ($this->get('id')) { $this->beforeupdate = new self($this->get('id')); } } /** * Whether the current user can comment on this plan. * * @return bool */ public function can_comment() { return static::can_comment_user($this->get('userid')); } /** * Whether the current user can manage the plan. * * @return bool */ public function can_manage() { if ($this->is_draft()) { return self::can_manage_user_draft($this->get('userid')); } return self::can_manage_user($this->get('userid')); } /** * Whether the current user can read the plan. * * @return bool */ public function can_read() { if ($this->is_draft()) { return self::can_read_user_draft($this->get('userid')); } return self::can_read_user($this->get('userid')); } /** * Whether the current user can read comments on this plan. * * @return bool */ public function can_read_comments() { return $this->can_read(); } /** * Whether the current user can request a review of the plan. * * @return bool */ public function can_request_review() { return self::can_request_review_user($this->get('userid')); } /** * Whether the current user can review the plan. * * @return bool */ public function can_review() { return self::can_review_user($this->get('userid')); } /** * Get the comment object. * * @return comment */ public function get_comment_object() { global $CFG; require_once($CFG->dirroot . '/comment/lib.php'); if (!$this->get('id')) { throw new \coding_exception('The plan must exist.'); } $comment = new comment((object) array( 'client_id' => 'plancommentarea' . $this->get('id'), 'context' => $this->get_context(), 'component' => 'competency', // This cannot be named 'core_competency'. 'itemid' => $this->get('id'), 'area' => 'plan', 'showcount' => true, )); $comment->set_fullwidth(true); return $comment; } /** * Get the competencies in this plan. * * @return competency[] */ public function get_competencies() { $competencies = array(); if ($this->get('status') == self::STATUS_COMPLETE) { // Get the competencies from the archive of the plan. $competencies = user_competency_plan::list_competencies($this->get('id'), $this->get('userid')); } else if ($this->is_based_on_template()) { // Get the competencies from the template. $competencies = template_competency::list_competencies($this->get('templateid')); } else { // Get the competencies from the plan. $competencies = plan_competency::list_competencies($this->get('id')); } return $competencies; } /** * Get a single competency from this plan. * * This will throw an exception if the competency does not belong to the plan. * * @param int $competencyid The competency ID. * @return competency */ public function get_competency($competencyid) { $competency = null; if ($this->get('status') == self::STATUS_COMPLETE) { // Get the competency from the archive of the plan. $competency = user_competency_plan::get_competency_by_planid($this->get('id'), $competencyid); } else if ($this->is_based_on_template()) { // Get the competency from the template. $competency = template_competency::get_competency($this->get('templateid'), $competencyid); } else { // Get the competency from the plan. $competency = plan_competency::get_competency($this->get('id'), $competencyid); } return $competency; } /** * Get the context in which the plan is attached. * * @return context_user */ public function get_context() { return context_user::instance($this->get('userid')); } /** * Human readable status name. * * @return string */ public function get_statusname() { $status = $this->get('status'); switch ($status) { case self::STATUS_DRAFT: $strname = 'draft'; break; case self::STATUS_IN_REVIEW: $strname = 'inreview'; break; case self::STATUS_WAITING_FOR_REVIEW: $strname = 'waitingforreview'; break; case self::STATUS_ACTIVE: $strname = 'active'; break; case self::STATUS_COMPLETE: $strname = 'complete'; break; default: throw new \moodle_exception('errorplanstatus', 'core_competency', '', $status); break; } return get_string('planstatus' . $strname, 'core_competency'); } /** * Get the plan template. * * @return template|null */ public function get_template() { $templateid = $this->get('templateid'); if ($templateid === null) { return null; } return new template($templateid); } /** * Is the plan in draft mode? * * This method is convenient to know if the plan is a draft because whilst a draft * is being reviewed its status is not "draft" any more, but it still is a draft nonetheless. * * @return boolean */ public function is_draft() { return in_array($this->get('status'), static::get_draft_statuses()); } /** * Validate the template ID. * * @param mixed $value The value. * @return true|lang_string */ protected function validate_templateid($value) { // Checks that the template exists. if (!empty($value) && !template::record_exists($value)) { return new lang_string('invaliddata', 'error'); } return true; } /** * Validate the user ID. * * @param int $value * @return true|lang_string */ protected function validate_userid($value) { global $DB; // During create. if (!$this->get('id')) { // Check that the user exists. We do not need to do that on update because // the userid of a plan should never change. if (!$DB->record_exists('user', array('id' => $value))) { return new lang_string('invaliddata', 'error'); } } return true; } /** * Can the current user comment on a user's plan? * * @param int $planuserid The user ID the plan belongs to. * @return bool */ public static function can_comment_user($planuserid) { global $USER; $capabilities = array('moodle/competency:plancomment'); if ($USER->id == $planuserid) { $capabilities[] = 'moodle/competency:plancommentown'; } return has_any_capability($capabilities, context_user::instance($planuserid)); } /** * Can the current user manage a user's plan? * * @param int $planuserid The user to whom the plan would belong. * @return bool */ public static function can_manage_user($planuserid) { global $USER; $context = context_user::instance($planuserid); $capabilities = array('moodle/competency:planmanage'); if ($context->instanceid == $USER->id) { $capabilities[] = 'moodle/competency:planmanageown'; } return has_any_capability($capabilities, $context); } /** * Can the current user manage a user's draft plan? * * @param int $planuserid The user to whom the plan would belong. * @return bool */ public static function can_manage_user_draft($planuserid) { global $USER; $context = context_user::instance($planuserid); $capabilities = array('moodle/competency:planmanagedraft'); if ($context->instanceid == $USER->id) { $capabilities[] = 'moodle/competency:planmanageowndraft'; } return has_any_capability($capabilities, $context); } /** * Can the current user read the comments on a user's plan? * * @param int $planuserid The user ID the plan belongs to. * @return bool */ public static function can_read_comments_user($planuserid) { // Everyone who can read the plan can read the comments. return static::can_read_user($planuserid); } /** * Can the current user view a user's plan? * * @param int $planuserid The user to whom the plan would belong. * @return bool */ public static function can_read_user($planuserid) { global $USER; $context = context_user::instance($planuserid); $capabilities = array('moodle/competency:planview'); if ($context->instanceid == $USER->id) { $capabilities[] = 'moodle/competency:planviewown'; } return has_any_capability($capabilities, $context) || self::can_manage_user($planuserid); } /** * Can the current user view a user's draft plan? * * @param int $planuserid The user to whom the plan would belong. * @return bool */ public static function can_read_user_draft($planuserid) { global $USER; $context = context_user::instance($planuserid); $capabilities = array('moodle/competency:planviewdraft'); if ($context->instanceid == $USER->id) { $capabilities[] = 'moodle/competency:planviewowndraft'; } return has_any_capability($capabilities, $context) || self::can_manage_user_draft($planuserid); } /** * Can the current user request the draft to be reviewed. * * @param int $planuserid The user to whom the plan would belong. * @return bool */ public static function can_request_review_user($planuserid) { global $USER; $capabilities = array('moodle/competency:planrequestreview'); if ($USER->id == $planuserid) { $capabilities[] = 'moodle/competency:planrequestreviewown'; } return has_any_capability($capabilities, context_user::instance($planuserid)); } /** * Can the current user review the plan. * * This means being able to send the plan from draft to active, and vice versa. * * @param int $planuserid The user to whom the plan would belong. * @return bool */ public static function can_review_user($planuserid) { return has_capability('moodle/competency:planreview', context_user::instance($planuserid)) || self::can_manage_user($planuserid); } /** * Get the plans of a user containing a specific competency. * * @param int $userid The user ID. * @param int $competencyid The competency ID. * @return plans[] */ public static function get_by_user_and_competency($userid, $competencyid) { global $DB; $sql = 'SELECT p.* FROM {' . self::TABLE . '} p LEFT JOIN {' . plan_competency::TABLE . '} pc ON pc.planid = p.id AND pc.competencyid = :competencyid1 LEFT JOIN {' . user_competency_plan::TABLE . '} ucp ON ucp.planid = p.id AND ucp.competencyid = :competencyid2 LEFT JOIN {' . template_competency::TABLE . '} tc ON tc.templateid = p.templateid AND tc.competencyid = :competencyid3 WHERE p.userid = :userid AND (pc.id IS NOT NULL OR ucp.id IS NOT NULL OR tc.id IS NOT NULL) ORDER BY p.id ASC'; $params = array( 'competencyid1' => $competencyid, 'competencyid2' => $competencyid, 'competencyid3' => $competencyid, 'userid' => $userid ); $plans = array(); $records = $DB->get_records_sql($sql, $params); foreach ($records as $record) { $plans[$record->id] = new plan(0, $record); } return $plans; } /** * Get the list of draft statuses. * * @return array Contains the status constants. */ public static function get_draft_statuses() { return array(self::STATUS_DRAFT, self::STATUS_WAITING_FOR_REVIEW, self::STATUS_IN_REVIEW); } /** * Get the recordset of the plans that are due, incomplete and not draft. * * @return \moodle_recordset */ public static function get_recordset_for_due_and_incomplete() { global $DB; $sql = "duedate > 0 AND duedate < :now AND status = :status"; $params = array('now' => time(), 'status' => self::STATUS_ACTIVE); return $DB->get_recordset_select(self::TABLE, $sql, $params); } /** * Return a list of status depending on capabilities. * * @param int $userid The user to whom the plan would belong. * @return array */ public static function get_status_list($userid) { $status = array(); if (self::can_manage_user_draft($userid)) { $status[self::STATUS_DRAFT] = get_string('planstatusdraft', 'core_competency'); } if (self::can_manage_user($userid)) { $status[self::STATUS_ACTIVE] = get_string('planstatusactive', 'core_competency'); } return $status; } /** * Update from template. * * Bulk update a lot of plans from a template * * @param template $template * @return bool */ public static function update_multiple_from_template(template $template) { global $DB; if (!$template->is_valid()) { // As we will bypass this model's validation we rely on the template being validated. throw new \coding_exception('The template must be validated before updating plans.'); } $params = array( 'templateid' => $template->get('id'), 'status' => self::STATUS_COMPLETE, 'name' => $template->get('shortname'), 'description' => $template->get('description'), 'descriptionformat' => $template->get('descriptionformat'), 'duedate' => $template->get('duedate'), ); $sql = "UPDATE {" . self::TABLE . "} SET name = :name, description = :description, descriptionformat = :descriptionformat, duedate = :duedate WHERE templateid = :templateid AND status != :status"; return $DB->execute($sql, $params); } /** * Check if a template is associated to the plan. * * @return bool */ public function is_based_on_template() { return $this->get('templateid') !== null; } /** * Check if plan can be edited. * * @return bool */ public function can_be_edited() { return !$this->is_based_on_template() && $this->get('status') != self::STATUS_COMPLETE && $this->can_manage(); } /** * Validate the due date. * When setting a due date it must not exceed the DUEDATE_THRESHOLD. * * @param int $value The due date. * @return bool|lang_string */ protected function validate_duedate($value) { // We do not check duedate when plan is draft, complete, unset, or based on a template. if ($this->is_based_on_template() || $this->is_draft() || $this->get('status') == self::STATUS_COMPLETE || empty($value)) { return true; } // During update. if ($this->get('id')) { $before = $this->beforeupdate->get('duedate'); $beforestatus = $this->beforeupdate->get('status'); // The value has not changed, then it's always OK. Though if we're going // from draft to active it has to has to be validated. if ($before == $value && !in_array($beforestatus, self::get_draft_statuses())) { return true; } } if ($value <= time()) { // We cannot set the date in the past. return new lang_string('errorcannotsetduedateinthepast', 'core_competency'); } if ($value <= time() + self::DUEDATE_THRESHOLD) { // We cannot set the date too soon, but we can leave it empty. return new lang_string('errorcannotsetduedatetoosoon', 'core_competency'); } return true; } /** * Checks if a template has user plan records. * * @param int $templateid The template ID * @return boolean */ public static function has_records_for_template($templateid) { return self::record_exists_select('templateid = ?', array($templateid)); } /** * Count the number of plans for a template, optionally filtering by status. * * @param int $templateid The template ID * @param int $status The plan status. 0 means all statuses. * @return int */ public static function count_records_for_template($templateid, $status) { $filters = array('templateid' => $templateid); if ($status > 0) { $filters['status'] = $status; } return self::count_records($filters); } /** * Get the plans for a template, optionally filtering by status. * * @param int $templateid The template ID * @param int $status The plan status. 0 means all statuses. * @param int $skip The number of plans to skip * @param int $limit The max number of plans to return * @return int */ public static function get_records_for_template($templateid, $status = 0, $skip = 0, $limit = 100) { $filters = array('templateid' => $templateid); if ($status > 0) { $filters['status'] = $status; } return self::get_records($filters, $skip, $limit); } } classes/course_competency_settings.php000064400000007657152265256010014403 0ustar00. /** * Class for course_competency_settings persistence. * * @package core_competency * @copyright 2016 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_competency; use lang_string; use context_course; defined('MOODLE_INTERNAL') || die(); /** * Class for course_competency_settings persistence. * * @copyright 2016 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class course_competency_settings extends persistent { /** Table name for plan_competency persistency */ const TABLE = 'competency_coursecompsetting'; /** * Return the definition of the properties of this model. * * @return array */ protected static function define_properties() { return array( 'courseid' => array( 'type' => PARAM_INT, ), 'pushratingstouserplans' => array( 'type' => PARAM_BOOL, 'default' => function() { return get_config('core_competency', 'pushcourseratingstouserplans'); } ), ); } /** * Get a the course settings for a single course. * * @param int $courseid The course id * @return course_competency_settings */ public static function get_by_courseid($courseid) { global $DB; $params = array( 'courseid' => $courseid ); $settings = new static(null, (object) $params); if ($record = $DB->get_record(self::TABLE, $params)) { $settings->from_record($record); } return $settings; } /** * Can the current user view competency settings for this course. * * @param int $courseid The course ID. * @return bool */ public static function can_read($courseid) { $context = context_course::instance($courseid); $capabilities = array('moodle/competency:coursecompetencyview', 'moodle/competency:coursecompetencymanage'); return has_any_capability($capabilities, $context); } /** * Can the current user change competency settings for this course. * * @param int $courseid The course ID. * @return bool */ public static function can_manage_course($courseid) { $context = context_course::instance($courseid); $capabilities = array('moodle/competency:coursecompetencyconfigure'); return has_any_capability($capabilities, $context); } /** * Can the current user change competency settings for this course. * * @return bool */ public function can_manage() { return static::can_manage_course($this->get('courseid')); } /** * Validate course ID. * * @param int $data The course ID. * @return true|lang_string */ protected function validate_courseid($data) { global $DB; if (!$DB->record_exists('course', array('id' => $data))) { return new lang_string('invalidcourseid', 'error'); } return true; } /** * Get the context. * * @return context The context */ public function get_context() { return context_course::instance($this->get('courseid')); } } classes/user_competency_course.php000064400000022203152265256010013501 0ustar00. /** * Class for user_competency_course persistence. * * @package core_competency * @copyright 2016 Jun Pataleta * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_competency; defined('MOODLE_INTERNAL') || die(); use context_course; use context_user; use lang_string; /** * Class for loading/storing user_competency_course from the DB. * * @copyright 2016 Jun Pataleta * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class user_competency_course extends persistent { /** Table name for user_competency persistency */ const TABLE = 'competency_usercompcourse'; /** * Return the definition of the properties of this model. * * @return array */ protected static function define_properties() { return array( 'userid' => array( 'type' => PARAM_INT, ), 'courseid' => array( 'type' => PARAM_INT ), 'competencyid' => array( 'type' => PARAM_INT, ), 'proficiency' => array( 'type' => PARAM_BOOL, 'default' => null, 'null' => NULL_ALLOWED, ), 'grade' => array( 'type' => PARAM_INT, 'default' => null, 'null' => NULL_ALLOWED, ), ); } /** * Return the competency Object. * * @return competency Competency Object */ public function get_competency() { return new competency($this->get('competencyid')); } /** * Get the context. * * @return context The context. */ public function get_context() { return context_user::instance($this->get('userid')); } /** * Create a new user_competency_course object. * * Note, this is intended to be used to create a blank relation, for instance when * the record was not found in the database. This does not save the model. * * @param int $userid The user ID. * @param int $competencyid The competency ID. * @param int $courseid The course ID. * @return \core_competency\user_competency_course */ public static function create_relation($userid, $competencyid, $courseid) { $data = new \stdClass(); $data->userid = $userid; $data->competencyid = $competencyid; $data->courseid = $courseid; $relation = new user_competency_course(0, $data); return $relation; } /** * Validate the user ID. * * @param int $value The value. * @return true|lang_string */ protected function validate_userid($value) { global $DB; if (!$DB->record_exists('user', array('id' => $value))) { return new lang_string('invaliduserid', 'error'); } return true; } /** * Validate the competency ID. * * @param int $value The value. * @return true|lang_string */ protected function validate_competencyid($value) { if (!competency::record_exists($value)) { return new lang_string('errornocompetency', 'core_competency', $value); } return true; } /** * Validate course ID. * * @param int $value The course ID. * @return true|lang_string */ protected function validate_courseid($value) { if (!context_course::instance($value, IGNORE_MISSING)) { return new lang_string('errorinvalidcourse', 'core_competency', $value); } return true; } /** * Validate the proficiency. * * @param int $value The value. * @return true|lang_string */ protected function validate_proficiency($value) { $grade = $this->get('grade'); if ($grade !== null && $value === null) { // We must set a proficiency when we set a grade. return new lang_string('invaliddata', 'error'); } else if ($grade === null && $value !== null) { // We must not set a proficiency when we don't set a grade. return new lang_string('invaliddata', 'error'); } return true; } /** * Validate the grade. * * @param int $value The value. * @return true|lang_string */ protected function validate_grade($value) { if ($value !== null) { if ($value <= 0) { return new lang_string('invalidgrade', 'core_competency'); } // TODO MDL-52243 Use a core method to validate the grade_scale item. // Check if grade exist in the scale item values. $competency = $this->get_competency(); if (!array_key_exists($value - 1 , $competency->get_scale()->scale_items)) { return new lang_string('invalidgrade', 'core_competency'); } } return true; } /** * Get multiple user_competency_course for a user. * * @param int $userid * @param int $courseid * @param array $competenciesorids Limit search to those competencies, or competency IDs. * @return \core_competency\user_competency_course[] */ public static function get_multiple($userid, $courseid, ?array $competenciesorids = null) { global $DB; $params = array(); $params['userid'] = $userid; $params['courseid'] = $courseid; $sql = '1 = 1'; if (!empty($competenciesorids)) { $test = reset($competenciesorids); if (is_number($test)) { $ids = $competenciesorids; } else { $ids = array(); foreach ($competenciesorids as $comp) { $ids[] = $comp->get('id'); } } list($insql, $inparams) = $DB->get_in_or_equal($ids, SQL_PARAMS_NAMED); $params += $inparams; $sql = "competencyid $insql"; } // Order by ID to prevent random ordering. return self::get_records_select("userid = :userid AND courseid = :courseid AND $sql", $params, 'id ASC'); } /** * Count the proficient competencies in this course for one user. * * @param int $courseid The course id * @param int $userid The user id * @return int */ public static function count_proficient_competencies($courseid, $userid) { global $DB; $sql = 'SELECT COUNT(comp.id) FROM {' . self::TABLE . '} usercoursecomp JOIN {' . course_competency::TABLE . '} cc ON usercoursecomp.competencyid = cc.competencyid AND cc.courseid = usercoursecomp.courseid JOIN {' . competency::TABLE . '} comp ON usercoursecomp.competencyid = comp.id WHERE usercoursecomp.courseid = ? AND usercoursecomp.userid = ? AND usercoursecomp.proficiency = ?'; $params = array($courseid, $userid, true); $results = $DB->count_records_sql($sql, $params); return $results; } /** * Get the list of competencies that were completed the least times in a course. * * @param int $courseid * @param int $skip The number of competencies to skip * @param int $limit The max number of competencies to return * @return competency[] */ public static function get_least_proficient_competencies_for_course($courseid, $skip = 0, $limit = 0) { global $DB; $fields = competency::get_sql_fields('c', 'c_'); $params = array('courseid' => $courseid); $sql = 'SELECT ' . $fields . ' FROM (SELECT cc.competencyid, SUM(COALESCE(ucc.proficiency, 0)) AS timesproficient FROM {' . course_competency::TABLE . '} cc LEFT JOIN {' . self::TABLE . '} ucc ON ucc.competencyid = cc.competencyid AND ucc.courseid = cc.courseid WHERE cc.courseid = :courseid GROUP BY cc.competencyid ) p JOIN {' . competency::TABLE . '} c ON c.id = p.competencyid ORDER BY p.timesproficient ASC, c.id DESC'; $results = $DB->get_records_sql($sql, $params, $skip, $limit); $comps = array(); foreach ($results as $r) { $c = competency::extract_record($r, 'c_'); $comps[] = new competency(0, $c); } return $comps; } } classes/competency.php000064400000067460152265256010011101 0ustar00. /** * Class for loading/storing competencies from the DB. * * @package core_competency * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_competency; defined('MOODLE_INTERNAL') || die(); use coding_exception; use context_system; use lang_string; use stdClass; require_once($CFG->libdir . '/grade/grade_scale.php'); /** * Class for loading/storing competencies from the DB. * * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class competency extends persistent { const TABLE = 'competency'; /** Outcome none. */ const OUTCOME_NONE = 0; /** Outcome evidence. */ const OUTCOME_EVIDENCE = 1; /** Outcome complete. */ const OUTCOME_COMPLETE = 2; /** Outcome recommend. */ const OUTCOME_RECOMMEND = 3; /** @var competency Object before update. */ protected $beforeupdate = null; /** @var competency|null To store new parent. */ protected $newparent; /** * Return the definition of the properties of this model. * * @return array */ protected static function define_properties() { return array( 'shortname' => array( 'type' => PARAM_TEXT ), 'idnumber' => array( 'type' => PARAM_RAW ), 'description' => array( 'default' => '', 'type' => PARAM_CLEANHTML ), 'descriptionformat' => array( 'choices' => array(FORMAT_HTML, FORMAT_MOODLE, FORMAT_PLAIN, FORMAT_MARKDOWN), 'type' => PARAM_INT, 'default' => FORMAT_HTML ), 'sortorder' => array( 'default' => 0, 'type' => PARAM_INT ), 'parentid' => array( 'default' => 0, 'type' => PARAM_INT ), 'path' => array( 'default' => '/0/', 'type' => PARAM_RAW ), 'ruleoutcome' => array( 'choices' => array(self::OUTCOME_NONE, self::OUTCOME_EVIDENCE, self::OUTCOME_COMPLETE, self::OUTCOME_RECOMMEND), 'default' => self::OUTCOME_NONE, 'type' => PARAM_INT ), 'ruletype' => array( 'type' => PARAM_RAW, 'default' => null, 'null' => NULL_ALLOWED ), 'ruleconfig' => array( 'default' => null, 'type' => PARAM_RAW, 'null' => NULL_ALLOWED ), 'scaleid' => array( 'default' => null, 'type' => PARAM_INT, 'null' => NULL_ALLOWED ), 'scaleconfiguration' => array( 'default' => null, 'type' => PARAM_RAW, 'null' => NULL_ALLOWED ), 'competencyframeworkid' => array( 'default' => 0, 'type' => PARAM_INT ), ); } /** * Hook to execute before validate. * * @return void */ protected function before_validate() { $this->beforeupdate = null; $this->newparent = null; // During update. if ($this->get('id')) { $this->beforeupdate = new competency($this->get('id')); // The parent ID has changed. if ($this->beforeupdate->get('parentid') != $this->get('parentid')) { $this->newparent = $this->get_parent(); // Update path and sortorder. $this->set_new_path($this->newparent); $this->set_new_sortorder(); } } else { // During create. $this->set_new_path(); // Always generate new sortorder when we create new competency. $this->set_new_sortorder(); } } /** * Hook to execute after an update. * * @param bool $result Whether or not the update was successful. * @return void */ protected function after_update($result) { global $DB; if (!$result) { $this->beforeupdate = null; return; } // The parent ID has changed, we need to fix all the paths of the children. if ($this->beforeupdate->get('parentid') != $this->get('parentid')) { $beforepath = $this->beforeupdate->get('path') . $this->get('id') . '/'; $like = $DB->sql_like('path', '?'); $likesearch = $DB->sql_like_escape($beforepath) . '%'; $table = '{' . self::TABLE . '}'; $sql = "UPDATE $table SET path = REPLACE(path, ?, ?) WHERE " . $like; $DB->execute($sql, array( $beforepath, $this->get('path') . $this->get('id') . '/', $likesearch )); // Resolving sortorder holes left after changing parent. $table = '{' . self::TABLE . '}'; $sql = "UPDATE $table SET sortorder = sortorder -1 " . " WHERE competencyframeworkid = ? AND parentid = ? AND sortorder > ?"; $DB->execute($sql, array($this->get('competencyframeworkid'), $this->beforeupdate->get('parentid'), $this->beforeupdate->get('sortorder') )); } $this->beforeupdate = null; } /** * Hook to execute after a delete. * * @param bool $result Whether or not the delete was successful. * @return void */ protected function after_delete($result) { global $DB; if (!$result) { return; } // Resolving sortorder holes left after delete. $table = '{' . self::TABLE . '}'; $sql = "UPDATE $table SET sortorder = sortorder -1 WHERE competencyframeworkid = ? AND parentid = ? AND sortorder > ?"; $DB->execute($sql, array($this->get('competencyframeworkid'), $this->get('parentid'), $this->get('sortorder'))); } /** * Extracts the default grade from the scale configuration. * * Returns an array where the first element is the grade, and the second * is a boolean representing whether or not this grade is considered 'proficient'. * * @return array(int grade, bool proficient) */ public function get_default_grade() { $scaleid = $this->get('scaleid'); $scaleconfig = $this->get('scaleconfiguration'); if ($scaleid === null) { $scaleconfig = $this->get_framework()->get('scaleconfiguration'); } return competency_framework::get_default_grade_from_scale_configuration($scaleconfig); } /** * Get the competency framework. * * @return competency_framework */ public function get_framework() { return new competency_framework($this->get('competencyframeworkid')); } /** * Get the competency level. * * @return int */ public function get_level() { $path = $this->get('path'); $path = trim($path, '/'); return substr_count($path, '/') + 1; } /** * Return the parent competency. * * @return null|competency */ public function get_parent() { $parentid = $this->get('parentid'); if (!$parentid) { return null; } return new competency($parentid); } /** * Extracts the proficiency of a grade from the scale configuration. * * @param int $grade The grade (scale item ID). * @return array(int grade, bool proficient) */ public function get_proficiency_of_grade($grade) { $scaleid = $this->get('scaleid'); $scaleconfig = $this->get('scaleconfiguration'); if ($scaleid === null) { $scaleconfig = $this->get_framework()->get('scaleconfiguration'); } return competency_framework::get_proficiency_of_grade_from_scale_configuration($scaleconfig, $grade); } /** * Return the related competencies. * * @return competency[] */ public function get_related_competencies() { return related_competency::get_related_competencies($this->get('id')); } /** * Get the rule object. * * @return null|competency_rule */ public function get_rule_object() { $rule = $this->get('ruletype'); if (!$rule || !is_subclass_of($rule, 'core_competency\\competency_rule')) { // Double check that the rule is extending the right class to avoid bad surprises. return null; } return new $rule($this); } /** * Return the scale. * * @return \grade_scale */ public function get_scale() { $scaleid = $this->get('scaleid'); if ($scaleid === null) { return $this->get_framework()->get_scale(); } $scale = \grade_scale::fetch(array('id' => $scaleid)); $scale->load_items(); return $scale; } /** * Returns true when the competency has user competencies. * * This is useful to determine if the competency, or part of it, should be locked down. * * @return boolean */ public function has_user_competencies() { return user_competency::has_records_for_competency($this->get('id')) || user_competency_plan::has_records_for_competency($this->get('id')); } /** * Check if the competency is the parent of passed competencies. * * @param array $ids IDs of supposedly direct children. * @return boolean */ public function is_parent_of(array $ids) { global $DB; list($insql, $params) = $DB->get_in_or_equal($ids, SQL_PARAMS_NAMED); $params['parentid'] = $this->get('id'); return $DB->count_records_select(self::TABLE, "id $insql AND parentid = :parentid", $params) == count($ids); } /** * Reset the rule. * * @return void */ public function reset_rule() { $this->raw_set('ruleoutcome', static::OUTCOME_NONE); $this->raw_set('ruletype', null); $this->raw_set('ruleconfig', null); } /** * Helper method to set the path. * * @param competency $parent The parent competency object. * @return void */ protected function set_new_path(?competency $parent = null) { $path = '/0/'; if ($this->get('parentid')) { $parent = $parent !== null ? $parent : $this->get_parent(); $path = $parent->get('path') . $this->get('parentid') . '/'; } $this->raw_set('path', $path); } /** * Helper method to set the sortorder. * * @return void */ protected function set_new_sortorder() { $search = array('parentid' => $this->get('parentid'), 'competencyframeworkid' => $this->get('competencyframeworkid')); $this->raw_set('sortorder', $this->count_records($search)); } /** * This does a specialised search that finds all nodes in the tree with matching text on any text like field, * and returns this node and all its parents in a displayable sort order. * * @param string $searchtext The text to search for. * @param int $competencyframeworkid The competency framework to limit the search. * @return persistent[] */ public static function search($searchtext, $competencyframeworkid) { global $DB; $like1 = $DB->sql_like('shortname', ':like1', false); $like2 = $DB->sql_like('idnumber', ':like2', false); $like3 = $DB->sql_like('description', ':like3', false); $params = array( 'like1' => '%' . $DB->sql_like_escape($searchtext) . '%', 'like2' => '%' . $DB->sql_like_escape($searchtext) . '%', 'like3' => '%' . $DB->sql_like_escape($searchtext) . '%', 'frameworkid' => $competencyframeworkid ); $sql = 'competencyframeworkid = :frameworkid AND ((' . $like1 . ') OR (' . $like2 . ') OR (' . $like3 . '))'; $records = $DB->get_records_select(self::TABLE, $sql, $params, 'path, sortorder ASC', '*'); // Now get all the parents. $parents = array(); foreach ($records as $record) { $split = explode('/', trim($record->path, '/')); foreach ($split as $parent) { $parents[intval($parent)] = true; } } $parents = array_keys($parents); // Skip ones we already fetched. foreach ($parents as $idx => $parent) { if ($parent == 0 || isset($records[$parent])) { unset($parents[$idx]); } } if (count($parents)) { list($parentsql, $parentparams) = $DB->get_in_or_equal($parents, SQL_PARAMS_NAMED); $parentrecords = $DB->get_records_select(self::TABLE, 'id ' . $parentsql, $parentparams, 'path, sortorder ASC', '*'); foreach ($parentrecords as $id => $record) { $records[$id] = $record; } } $instances = array(); // Convert to instances of this class. foreach ($records as $record) { $newrecord = new static(0, $record); $instances[$newrecord->get('id')] = $newrecord; } return $instances; } /** * Validate the competency framework ID. * * @param int $value The framework ID. * @return true|lang_string */ protected function validate_competencyframeworkid($value) { // During update. if ($this->get('id')) { // Ensure that we are not trying to move the competency across frameworks. if ($this->beforeupdate->get('competencyframeworkid') != $value) { return new lang_string('invaliddata', 'error'); } } else { // During create. // Check that the framework exists. if (!competency_framework::record_exists($value)) { return new lang_string('invaliddata', 'error'); } } return true; } /** * Validate the ID number. * * @param string $value The ID number. * @return true|lang_string */ protected function validate_idnumber($value) { global $DB; $sql = 'idnumber = :idnumber AND competencyframeworkid = :competencyframeworkid AND id <> :id'; $params = array( 'id' => $this->get('id'), 'idnumber' => $value, 'competencyframeworkid' => $this->get('competencyframeworkid') ); if ($DB->record_exists_select(self::TABLE, $sql, $params)) { return new lang_string('idnumbertaken', 'error'); } return true; } /** * Validate the path. * * @param string $value The path. * @return true|lang_string */ protected function validate_path($value) { // The last item should be the parent ID. $id = $this->get('parentid'); if (substr($value, -(strlen($id) + 2)) != '/' . $id . '/') { return new lang_string('invaliddata', 'error'); } else if (!preg_match('@/([0-9]+/)+@', $value)) { // The format of the path is not correct. return new lang_string('invaliddata', 'error'); } return true; } /** * Validate the parent ID. * * @param string $value The ID. * @return true|lang_string */ protected function validate_parentid($value) { // Check that the parent exists. But only if we don't have it already, and we actually have a parent. if (!empty($value) && !$this->newparent && !self::record_exists($value)) { return new lang_string('invaliddata', 'error'); } // During update. if ($this->get('id')) { // If there is a new parent. if ($this->beforeupdate->get('parentid') != $value && $this->newparent) { // Check that the new parent belongs to the same framework. if ($this->newparent->get('competencyframeworkid') != $this->get('competencyframeworkid')) { return new lang_string('invaliddata', 'error'); } } } return true; } /** * Validate the rule. * * @param string $value The ID. * @return true|lang_string */ protected function validate_ruletype($value) { if ($value === null) { return true; } if (!class_exists($value) || !is_subclass_of($value, 'core_competency\\competency_rule')) { return new lang_string('invaliddata', 'error'); } return true; } /** * Validate the rule config. * * @param string $value The ID. * @return true|lang_string */ protected function validate_ruleconfig($value) { $rule = $this->get_rule_object(); // We don't have a rule. if (empty($rule)) { if ($value === null) { // No config, perfect. return true; } // Config but no rules, whoops! return new lang_string('invaliddata', 'error'); } $valid = $rule->validate_config($value); if ($valid !== true) { // Whoops! return new lang_string('invaliddata', 'error'); } return true; } /** * Validate the scale ID. * * Note that the value for a scale can never be 0, null has to be used when * the framework's scale has to be used. * * @param int $value * @return true|lang_string */ protected function validate_scaleid($value) { global $DB; if ($value === null) { return true; } // Always validate that the scale exists. if (!$DB->record_exists_select('scale', 'id = :id', array('id' => $value))) { return new lang_string('invalidscaleid', 'error'); } // During update. if ($this->get('id')) { // Validate that we can only change the scale when it is not used yet. if ($this->beforeupdate->get('scaleid') != $value) { if ($this->has_user_competencies()) { return new lang_string('errorscalealreadyused', 'core_competency'); } } } return true; } /** * Validate the scale configuration. * * This logic is adapted from {@link \core_competency\competency_framework::validate_scaleconfiguration()}. * * @param string $value The scale configuration. * @return bool|lang_string */ protected function validate_scaleconfiguration($value) { $scaleid = $this->get('scaleid'); if ($scaleid === null && $value === null) { return true; } $scaledefaultselected = false; $proficientselected = false; $scaleconfigurations = json_decode($value); if (is_array($scaleconfigurations)) { // The first element of the array contains the scale ID. $scaleinfo = array_shift($scaleconfigurations); if (empty($scaleinfo) || !isset($scaleinfo->scaleid) || $scaleinfo->scaleid != $scaleid) { // This should never happen. return new lang_string('errorscaleconfiguration', 'core_competency'); } // Walk through the array to find proficient and default values. foreach ($scaleconfigurations as $scaleconfiguration) { if (isset($scaleconfiguration->scaledefault) && $scaleconfiguration->scaledefault) { $scaledefaultselected = true; } if (isset($scaleconfiguration->proficient) && $scaleconfiguration->proficient) { $proficientselected = true; } } } if (!$scaledefaultselected || !$proficientselected) { return new lang_string('errorscaleconfiguration', 'core_competency'); } return true; } /** * Return whether or not the competency IDs share the same framework. * * @param array $ids Competency IDs * @return bool */ public static function share_same_framework(array $ids) { global $DB; list($insql, $params) = $DB->get_in_or_equal($ids); $sql = "SELECT COUNT('x') FROM (SELECT DISTINCT(competencyframeworkid) FROM {" . self::TABLE . "} WHERE id {$insql}) f"; return $DB->count_records_sql($sql, $params) == 1; } /** * Get the available rules. * * @return array Keys are the class names, values are the name of the rule. */ public static function get_available_rules() { // Fully qualified class names without leading slashes because get_class() does not add them either. $rules = array( 'core_competency\\competency_rule_all' => competency_rule_all::get_name(), 'core_competency\\competency_rule_points' => competency_rule_points::get_name(), ); return $rules; } /** * Return the current depth of a competency framework. * * @param int $frameworkid The framework ID. * @return int */ public static function get_framework_depth($frameworkid) { global $DB; $totallength = $DB->sql_length('path'); $trimmedlength = $DB->sql_length("REPLACE(path, '/', '')"); $sql = "SELECT ($totallength - $trimmedlength - 1) AS depth FROM {" . self::TABLE . "} WHERE competencyframeworkid = :id ORDER BY depth DESC"; $record = $DB->get_record_sql($sql, array('id' => $frameworkid), IGNORE_MULTIPLE); if (!$record) { $depth = 0; } else { $depth = $record->depth; } return $depth; } /** * Build a framework tree with competency nodes. * * @param int $frameworkid the framework id * @return stdClass[] tree of framework competency nodes */ public static function get_framework_tree($frameworkid) { $competencies = self::search('', $frameworkid); return self::build_tree($competencies, 0); } /** * Get the context from the framework. * * @return \context */ public function get_context() { return $this->get_framework()->get_context(); } /** * Recursively build up the tree of nodes. * * @param array $all - List of all competency classes. * @param int $parentid - The current parent ID. Pass 0 to build the tree from the top. * @return stdClass[] $tree tree of nodes */ protected static function build_tree($all, $parentid) { $tree = array(); foreach ($all as $one) { if ($one->get('parentid') == $parentid) { $node = new stdClass(); $node->competency = $one; $node->children = self::build_tree($all, $one->get('id')); $tree[] = $node; } } return $tree; } /** * Check if we can delete competencies safely. * * This moethod does not check any capablities. * Check if competency is used in a plan and user competency. * Check if competency is used in a template. * Check if competency is linked to a course. * * @param array $ids Array of competencies ids. * @return bool True if we can delete the competencies. */ public static function can_all_be_deleted($ids) { global $CFG; if (empty($ids)) { return true; } // Check if competency is used in template. if (template_competency::has_records_for_competencies($ids)) { return false; } // Check if competency is used in plan. if (plan_competency::has_records_for_competencies($ids)) { return false; } // Check if competency is used in course. if (course_competency::has_records_for_competencies($ids)) { return false; } // Check if competency is used in user_competency. if (user_competency::has_records_for_competencies($ids)) { return false; } // Check if competency is used in user_competency_plan. if (user_competency_plan::has_records_for_competencies($ids)) { return false; } require_once($CFG->libdir . '/badgeslib.php'); // Check if competency is used in a badge. if (badge_award_criteria_competency_has_records_for_competencies($ids)) { return false; } return true; } /** * Delete the competencies. * * This method is reserved to core usage. * This method does not trigger the after_delete event. * This method does not delete related objects such as related competencies and evidences. * * @param array $ids The competencies ids. * @return bool True if the competencies were deleted successfully. */ public static function delete_multiple($ids) { global $DB; list($insql, $params) = $DB->get_in_or_equal($ids, SQL_PARAMS_NAMED); return $DB->delete_records_select(self::TABLE, "id $insql", $params); } /** * Get descendant ids. * * @param competency $competency The competency. * @return array Array of competencies ids. */ public static function get_descendants_ids($competency) { global $DB; $path = $DB->sql_like_escape($competency->get('path') . $competency->get('id') . '/') . '%'; $like = $DB->sql_like('path', ':likepath'); return $DB->get_fieldset_select(self::TABLE, 'id', $like, array('likepath' => $path)); } /** * Get competencyids by frameworkid. * * @param int $frameworkid The competency framework ID. * @return array Array of competency ids. */ public static function get_ids_by_frameworkid($frameworkid) { global $DB; return $DB->get_fieldset_select(self::TABLE, 'id', 'competencyframeworkid = :frmid', array('frmid' => $frameworkid)); } /** * Delete competencies by framework ID. * * This method is reserved to core usage. * This method does not trigger the after_delete event. * This method does not delete related objects such as related competencies and evidences. * * @param int $id the framework ID * @return bool Return true if delete was successful. */ public static function delete_by_frameworkid($id) { global $DB; return $DB->delete_records(self::TABLE, array('competencyframeworkid' => $id)); } /** * Get competency ancestors. * * @return competency[] Return array of ancestors. */ public function get_ancestors() { global $DB; $ancestors = array(); $ancestorsids = explode('/', trim($this->get('path'), '/')); // Drop the root item from the array /0/. array_shift($ancestorsids); if (!empty($ancestorsids)) { list($insql, $params) = $DB->get_in_or_equal($ancestorsids, SQL_PARAMS_NAMED); $ancestors = self::get_records_select("id $insql", $params); } return $ancestors; } } classes/course_module_competency.php000064400000025003152265256010014011 0ustar00. /** * Class for loading/storing competencies from the DB. * * @package core_competency * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_competency; defined('MOODLE_INTERNAL') || die(); use stdClass; use lang_string; /** * Class for loading/storing course_module_competencies from the DB. * * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class course_module_competency extends persistent { const TABLE = 'competency_modulecomp'; /** Course competency ruleoutcome constant. */ const OUTCOME_NONE = 0; /** Course competency ruleoutcome constant. */ const OUTCOME_EVIDENCE = 1; /** Course competency ruleoutcome constant. */ const OUTCOME_RECOMMEND = 2; /** Course competency ruleoutcome constant. */ const OUTCOME_COMPLETE = 3; /** * Return the definition of the properties of this model. * * @return array */ protected static function define_properties() { return array( 'cmid' => array( 'type' => PARAM_INT ), 'competencyid' => array( 'type' => PARAM_INT ), 'sortorder' => array( 'type' => PARAM_INT ), 'ruleoutcome' => array( 'choices' => array(self::OUTCOME_NONE, self::OUTCOME_EVIDENCE, self::OUTCOME_RECOMMEND, self::OUTCOME_COMPLETE ), 'default' => self::OUTCOME_EVIDENCE, 'type' => PARAM_INT, ), 'overridegrade' => array( 'default' => false, 'type' => PARAM_BOOL ), ); } /** * Hook to execute before validate. * * @return void */ protected function before_validate() { if (($this->get('id') && $this->get('sortorder') === null) || !$this->get('id')) { $this->set('sortorder', $this->count_records(array('cmid' => $this->get('cmid')))); } } /** * Return a list of rules. * * @return array Indexed by outcome value. */ public static function get_ruleoutcome_list() { static $list = null; if ($list === null) { $list = array( self::OUTCOME_NONE => self::get_ruleoutcome_name(self::OUTCOME_NONE), self::OUTCOME_EVIDENCE => self::get_ruleoutcome_name(self::OUTCOME_EVIDENCE), self::OUTCOME_RECOMMEND => self::get_ruleoutcome_name(self::OUTCOME_RECOMMEND), self::OUTCOME_COMPLETE => self::get_ruleoutcome_name(self::OUTCOME_COMPLETE)); } return $list; } /** * Human readable rule name. * * @param int $ruleoutcome The value of ruleoutcome. * @return lang_string */ public static function get_ruleoutcome_name($ruleoutcome) { switch ($ruleoutcome) { case self::OUTCOME_NONE: $strname = 'none'; break; case self::OUTCOME_EVIDENCE: $strname = 'evidence'; break; case self::OUTCOME_RECOMMEND: $strname = 'recommend'; break; case self::OUTCOME_COMPLETE: $strname = 'complete'; break; default: throw new \moodle_exception('errorcompetencyrule', 'core_competency', '', $ruleoutcome); break; } return new lang_string('coursemodulecompetencyoutcome_' . $strname, 'core_competency'); } /** * Validate cmid ID. * * @param int $data The CM ID. * @return true|lang_string */ protected function validate_cmid($data) { global $DB; if (!$DB->record_exists('course_modules', array('id' => $data))) { return new lang_string('invalidmodule', 'error'); } return true; } /** * Validate competency ID. * * @param int $data The competency ID. * @return true|lang_string */ protected function validate_competencyid($data) { if (!competency::record_exists($data)) { return new lang_string('invaliddata', 'error'); } return true; } /** * Return the module IDs and visible flags that include this competency in a single course. * * @param int $competencyid The competency id * @param int $courseid The course ID. * @return array of ints (cmids) */ public static function list_course_modules($competencyid, $courseid) { global $DB; $results = $DB->get_records_sql('SELECT coursemodules.id as id FROM {' . self::TABLE . '} modcomp JOIN {course_modules} coursemodules ON modcomp.cmid = coursemodules.id WHERE modcomp.competencyid = ? AND coursemodules.course = ?', array($competencyid, $courseid)); return array_keys($results); } /** * Count the competencies in this course module. * * @param int $cmid The course module id. * @return int */ public static function count_competencies($cmid) { global $DB; $sql = 'SELECT COUNT(comp.id) FROM {' . self::TABLE . '} coursemodulecomp JOIN {' . competency::TABLE . '} comp ON coursemodulecomp.competencyid = comp.id WHERE coursemodulecomp.cmid = ? '; $params = array($cmid); $results = $DB->count_records_sql($sql, $params); return $results; } /** * List the competencies in this course module. * * @param int $cmid The course module id * @return competency[] Indexed by competency ID. */ public static function list_competencies($cmid) { global $DB; $sql = 'SELECT comp.* FROM {' . competency::TABLE . '} comp JOIN {' . self::TABLE . '} coursemodulecomp ON coursemodulecomp.competencyid = comp.id WHERE coursemodulecomp.cmid = ? ORDER BY coursemodulecomp.sortorder ASC'; $params = array($cmid); $results = $DB->get_recordset_sql($sql, $params); $instances = array(); foreach ($results as $result) { $comp = new competency(0, $result); $instances[$comp->get('id')] = $comp; } $results->close(); return $instances; } /** * Get a single competency from the course module (only if it is really in the course module). * * @param int $cmid The course module id * @param int $competencyid The competency id * @return competency */ public static function get_competency($cmid, $competencyid) { global $DB; $sql = 'SELECT comp.* FROM {' . competency::TABLE . '} comp JOIN {' . self::TABLE . '} crsmodcomp ON crsmodcomp.competencyid = comp.id WHERE crsmodcomp.cmid = ? AND crsmodcomp.competencyid = ?'; $params = array($cmid, $competencyid); $result = $DB->get_record_sql($sql, $params); if (!$result) { throw new \coding_exception('The competency does not belong to this course module: ' . $competencyid . ', ' . $cmid); } return new competency(0, $result); } /** * Hook to execute after delete. * * @param bool $result Whether or not the delete was successful. * @return void */ protected function after_delete($result) { global $DB; if (!$result) { return; } $table = '{' . self::TABLE . '}'; $sql = "UPDATE $table SET sortorder = sortorder -1 WHERE cmid = ? AND sortorder > ?"; $DB->execute($sql, array($this->get('cmid'), $this->get('sortorder'))); } /** * List the course_module_competencies in this course module. * * @param int $cmid The course module id * @return course_module_competency[] */ public static function list_course_module_competencies($cmid) { global $DB; $sql = 'SELECT coursemodcomp.* FROM {' . self::TABLE . '} coursemodcomp JOIN {' . competency::TABLE . '} comp ON coursemodcomp.competencyid = comp.id WHERE coursemodcomp.cmid = ? ORDER BY coursemodcomp.sortorder ASC'; $params = array($cmid); $results = $DB->get_recordset_sql($sql, $params); $instances = array(); foreach ($results as $result) { array_push($instances, new course_module_competency(0, $result)); } $results->close(); return $instances; } /** * List the relationship objects for a competency in a course. * * @param int $competencyid The competency ID. * @param int $courseid The course ID. * @return course_module_competency[] */ public static function get_records_by_competencyid_in_course($competencyid, $courseid) { global $DB; $sql = 'SELECT cmc.* FROM {' . self::TABLE . '} cmc JOIN {course_modules} cm ON cm.course = ? AND cmc.cmid = cm.id WHERE cmc.competencyid = ? ORDER BY cmc.sortorder ASC'; $params = array($courseid, $competencyid); $results = $DB->get_recordset_sql($sql, $params); $instances = array(); foreach ($results as $result) { $instances[$result->id] = new course_module_competency(0, $result); } $results->close(); return $instances; } } classes/user_evidence_competency.php000064400000012612152265256010013766 0ustar00. /** * User evidence competency persistent. * * This represent the many to many relationship between evidence of prior * learning and competencies. * * @package core_competency * @copyright 2015 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_competency; defined('MOODLE_INTERNAL') || die(); use stdClass; use lang_string; /** * User evidence competency persistent class. * * @package core_competency * @copyright 2015 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class user_evidence_competency extends persistent { const TABLE = 'competency_userevidencecomp'; /** * Return the definition of the properties of this model. * * @return array */ protected static function define_properties() { return array( 'userevidenceid' => array( 'type' => PARAM_INT ), 'competencyid' => array( 'type' => PARAM_INT, ), ); } /** * Validate competency ID. * * @param int $value ID. * @return true|lang_string */ protected function validate_competencyid($value) { if (!competency::record_exists($value)) { return new lang_string('invaliddata', 'error'); } return true; } /** * Validate user evidence ID. * * @param int $value ID. * @return true|lang_string */ protected function validate_userevidenceid($value) { if (!user_evidence::record_exists($value)) { return new lang_string('invaliddata', 'error'); } return true; } /** * Get competencies by user evidence ID. * * @param int $userevidenceid The user evidence ID. * @return competency[] */ public static function get_competencies_by_userevidenceid($userevidenceid) { global $DB; $sql = "SELECT c.* FROM {" . self::TABLE . "} uec JOIN {" . competency::TABLE . "} c ON uec.userevidenceid = ? AND uec.competencyid = c.id ORDER BY c.shortname"; $competencies = array(); $records = $DB->get_recordset_sql($sql, array($userevidenceid)); foreach ($records as $record) { $competencies[] = new competency(0, $record); } $records->close(); return $competencies; } /** * Get user competencies by user evidence ID. * * @param int $userevidenceid The user evidence ID. * @return user_competency[] */ public static function get_user_competencies_by_userevidenceid($userevidenceid) { global $DB; $sql = "SELECT uc.* FROM {" . user_competency::TABLE . "} uc JOIN {" . self::TABLE . "} uec ON uc.competencyid = uec.competencyid JOIN {" . user_evidence::TABLE . "} ue ON uec.userevidenceid = ue.id AND uc.userid = ue.userid AND ue.id = ? ORDER BY uc.id ASC"; $usercompetencies = array(); $records = $DB->get_recordset_sql($sql, array($userevidenceid)); foreach ($records as $record) { $usercompetencies[] = new user_competency(0, $record); } $records->close(); return $usercompetencies; } /** * Get a relation. * * This does not perform any validation on the data passed. If the relation exists in the database * then it is loaded in a the model, if not then it is up to the developer to save the model. * * @param int $userevidenceid * @param int $competencyid * @return template_cohort */ public static function get_relation($userevidenceid, $competencyid) { global $DB; $params = array( 'userevidenceid' => $userevidenceid, 'competencyid' => $competencyid ); $relation = new static(null, (object) $params); if ($record = $DB->get_record(static::TABLE, $params)) { $relation->from_record($record); } return $relation; } /** * Delete evidences using competencies. * * @param array $competencyids Array of competencies ids. * @return bool Return true if the delete was successful. */ public static function delete_by_competencyids($competencyids) { global $DB; if (empty($competencyids)) { return true; } list($insql, $params) = $DB->get_in_or_equal($competencyids); return $DB->delete_records_select(self::TABLE, "competencyid $insql", $params); } } classes/plan_competency.php000064400000012560152265256010012102 0ustar00. /** * Class for plan_competency persistence. * * @package core_competency * @copyright 2015 Issam Taboubi * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_competency; defined('MOODLE_INTERNAL') || die(); use lang_string; /** * Class for managing competencies in the plan (add/remove competencies for given plan). * * @copyright 2015 Issam Taboubi * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class plan_competency extends persistent { /** Table name for plan_competency persistency */ const TABLE = 'competency_plancomp'; /** * Return the definition of the properties of this model. * * @return array */ protected static function define_properties() { return array( 'planid' => array( 'type' => PARAM_INT, ), 'competencyid' => array( 'type' => PARAM_INT, ), 'sortorder' => array( 'type' => PARAM_INT, 'default' => null, ), ); } /** * List the competencies in this plan. * * @param int $planid The plan id * @return array[competency] */ public static function list_competencies($planid) { global $DB; $sql = 'SELECT comp.* FROM {' . competency::TABLE . '} comp JOIN {' . self::TABLE . '} plancomp ON plancomp.competencyid = comp.id WHERE plancomp.planid = ? ORDER BY plancomp.sortorder ASC, plancomp.id ASC'; $params = array($planid); // TODO MDL-52229 Handle hidden competencies. $results = $DB->get_records_sql($sql, $params); $instances = array(); foreach ($results as $result) { array_push($instances, new competency(0, $result)); } return $instances; } /** * Get a single competency from the plan (only if it is really in the plan). * * @param int $planid The plan id * @param int $competencyid The competency id * @return competency */ public static function get_competency($planid, $competencyid) { global $DB; $sql = 'SELECT comp.* FROM {' . competency::TABLE . '} comp JOIN {' . self::TABLE . '} plncomp ON plncomp.competencyid = comp.id WHERE plncomp.planid = ? AND plncomp.competencyid = ?'; $params = array($planid, $competencyid); $result = $DB->get_record_sql($sql, $params); if (!$result) { throw new \coding_exception('The competency does not belong to this plan: ' . $competencyid . ', ' . $planid); } return new competency(0, $result); } /** * Hook to execute before validate. * * @return void */ protected function before_validate() { if (($this->get('id') && $this->get('sortorder') === null) || !$this->get('id')) { $this->set('sortorder', $this->count_records(array('planid' => $this->get('planid')))); } } /** * Validate competencyid. * * @param int $value ID. * @return true|lang_string */ protected function validate_competencyid($value) { if (!competency::record_exists($value)) { return new lang_string('invaliddata', 'error'); } return true; } /** * Validate planid. * * @param int $value ID. * @return true|lang_string */ protected function validate_planid($value) { if (!plan::record_exists($value)) { return new lang_string('invaliddata', 'error'); } return true; } /** * Hook to execute after delete. * * @param bool $result Whether or not the delete was successful. * @return void */ protected function after_delete($result) { global $DB; if (!$result) { return; } $table = '{' . self::TABLE . '}'; $sql = "UPDATE $table SET sortorder = sortorder -1 WHERE planid = ? AND sortorder > ?"; $DB->execute($sql, array($this->get('planid'), $this->get('sortorder'))); } /** * Check if plan competency has records for competencies. * * @param array $competencyids The competences IDs * @return boolean */ public static function has_records_for_competencies($competencyids) { global $DB; list($insql, $params) = $DB->get_in_or_equal($competencyids, SQL_PARAMS_NAMED); return self::record_exists_select("competencyid $insql", $params); } } classes/competency_rule_points.php000064400000015231152265256010013511 0ustar00. /** * Competency rule points based. * * @package core_competency * @copyright 2015 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_competency; defined('MOODLE_INTERNAL') || die(); use coding_exception; use lang_string; /** * Competency rule points based class. * * This rule matches when related competencies contribute for a required number of points. * * @package core_competency * @copyright 2015 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class competency_rule_points extends competency_rule { /** * Get the rule config. * * @return mixed */ protected function get_config() { $config = parent::get_config(); return json_decode($config); } /** * Whether or not the rule is matched. * * @param user_competency $usercompetency The user competency. * @return bool */ public function matches(user_competency $usercompetency) { global $DB; $config = $this->get_config(); $pointsrequired = $config->base->points; // Index by competency ID and extract required. $compsrules = array(); $requiredids = array(); foreach ($config->competencies as $comp) { $compsrules[$comp->id] = $comp; if ($comp->required) { $requiredids[$comp->id] = $comp->id; } } // Find all the user competency records. list($insql, $params) = $DB->get_in_or_equal(array_keys($compsrules), SQL_PARAMS_NAMED); $sql = "userid = :userid AND proficiency = :proficiency AND competencyid $insql"; $params['userid'] = $usercompetency->get('userid'); $params['proficiency'] = 1; $ucs = user_competency::get_records_select($sql, $params, '', 'competencyid'); // Check that all the required are found. if (!empty($requiredids)) { $unmetrequired = array_diff_key($requiredids, $ucs); if (!empty($unmetrequired)) { return false; } } // Check that we have enough points. $points = 0; foreach ($compsrules as $compid => $comp) { if (array_key_exists($compid, $ucs)) { $points += $comp->points; } } return $points >= $pointsrequired; } /** * Validate the rule config. * * @param string $value The value to validate. * @return bool */ public function validate_config($value) { $compids = array(); $config = json_decode($value); if ($config === null || !isset($config->base) || !isset($config->competencies)) { return false; } if (!isset($config->base->points)) { return false; } try { $requiredpoints = validate_param($config->base->points, PARAM_INT); } catch (\invalid_parameter_exception $e) { return false; } if ($requiredpoints < 1) { return false; } $totalpoints = 0; // Validate the competency info. foreach ($config->competencies as $competency) { // Cannot include self. if ($competency->id == $this->competency->get('id')) { return false; } // Check for duplicates. if (in_array($competency->id, $compids)) { return false; } // Check for required fields. if (!isset($competency->id) || !isset($competency->points) || !isset($competency->required)) { return false; } // Validate the parameters. try { validate_param($competency->id, PARAM_INT); $points = validate_param($competency->points, PARAM_INT); validate_param($competency->required, PARAM_BOOL); } catch (\invalid_parameter_exception $e) { return false; } $totalpoints += $points; if ($points < 0) { return false; } $compids[] = $competency->id; } // No competencies, that's strange. if (empty($compids)) { return false; } // Impossible to reach the points required. if ($requiredpoints > $totalpoints) { return false; } // Check that all the competencies are children of the competency. // We may want to relax this check at a later stage if we want to allow competencies // to be linked throughout the whole framework. return $this->competency->is_parent_of($compids); } /** * The name of the rule. * * @return lang_string */ public static function get_name() { return new lang_string('pointsrequiredaremet', 'core_competency'); } /** * Migrate rule config when duplicate competency based on mapping competencies ids. * * @param string $config the config rule of a competency * @param array $mappings array that match the old competency ids with the new competencies * @return string */ public static function migrate_config($config, $mappings) { $ruleconfig = json_decode($config, true); if (is_array($ruleconfig)) { foreach ($ruleconfig['competencies'] as $key => $rulecomp) { $rulecmpid = $rulecomp['id']; if (array_key_exists($rulecmpid, $mappings)) { $ruleconfig['competencies'][$key]['id'] = $mappings[$rulecmpid]->get('id'); } else { throw new coding_exception("The competency id is not found in the matchids."); } } } else { throw new coding_exception("Invalid JSON config rule."); } return json_encode($ruleconfig); } } classes/competency_rule_all.php000064400000005420152265256010012744 0ustar00. /** * Competency rule all. * * @package core_competency * @copyright 2015 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_competency; defined('MOODLE_INTERNAL') || die(); use lang_string; /** * Competency rule all class. * * This rule is considered matched when all the children of a competency are completed. * * @package core_competency * @copyright 2015 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class competency_rule_all extends competency_rule { /** * Whether or not the rule is matched. * * @param user_competency $usercompetency The user competency. * @return bool */ public function matches(user_competency $usercompetency) { global $DB; // TODO Improve performance here, perhaps the caller could already provide records. $children = competency::get_records(array('parentid' => $this->competency->get('id'))); if (empty($children)) { // Leaves are not compatible with this rule. return false; } $ids = array(); foreach ($children as $child) { $ids[] = $child->get('id'); } list($insql, $params) = $DB->get_in_or_equal($ids, SQL_PARAMS_NAMED); $sql = "userid = :userid AND proficiency = :proficiency AND competencyid $insql"; $params['userid'] = $usercompetency->get('userid'); $params['proficiency'] = 1; // Is the user is marked as proficient in all children? return user_competency::count_records_select($sql, $params) === count($ids); } /** * Validate the rule config. * * @param string $value The value to validate. * @return bool */ public function validate_config($value) { return $value === null; } /** * The name of the rule. * * @return lang_string */ public static function get_name() { return new lang_string('allchildrenarecomplete', 'core_competency'); } } classes/course_competency.php000064400000033413152265256010012450 0ustar00. /** * Class for loading/storing competencies from the DB. * * @package core_competency * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_competency; use coding_exception; use lang_string; use core_course\external\course_summary_exporter; /** * Class for loading/storing course_competencies from the DB. * * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class course_competency extends persistent { const TABLE = 'competency_coursecomp'; /** Course competency ruleoutcome constant. */ const OUTCOME_NONE = 0; /** Course competency ruleoutcome constant. */ const OUTCOME_EVIDENCE = 1; /** Course competency ruleoutcome constant. */ const OUTCOME_RECOMMEND = 2; /** Course competency ruleoutcome constant. */ const OUTCOME_COMPLETE = 3; /** * Return the definition of the properties of this model. * * @return array */ protected static function define_properties() { return array( 'courseid' => array( 'type' => PARAM_INT ), 'competencyid' => array( 'type' => PARAM_INT ), 'sortorder' => array( 'type' => PARAM_INT ), 'ruleoutcome' => array( 'choices' => array(self::OUTCOME_NONE, self::OUTCOME_EVIDENCE, self::OUTCOME_RECOMMEND, self::OUTCOME_COMPLETE ), 'default' => self::OUTCOME_EVIDENCE, 'type' => PARAM_INT, ), ); } /** * Hook to execute before validate. * * @return void */ protected function before_validate() { if (($this->get('id') && $this->get('sortorder') === null) || !$this->get('id')) { $this->set('sortorder', $this->count_records(array('courseid' => $this->get('courseid')))); } } /** * Return the courses where both competency and user are. * * A user is considered being in a course when they are enrolled, the enrolment is valid, * the enrolment instance is enabled, and the enrolment plugin is enabled.. * * @param int $competencyid The competency ID. * @param int $userid The user ID. * @return array Indexed by course ID. */ public static function get_courses_with_competency_and_user($competencyid, $userid) { global $CFG, $DB; if (!$plugins = explode(',', $CFG->enrol_plugins_enabled)) { return array(); } $ctxfields = \context_helper::get_preload_record_columns_sql('ctx'); list($plugins, $params) = $DB->get_in_or_equal($plugins, SQL_PARAMS_NAMED, 'ee'); $params['competencyid'] = $competencyid; $params['userid'] = $userid; $params['enabled'] = ENROL_INSTANCE_ENABLED; $params['active'] = ENROL_USER_ACTIVE; $params['contextlevel'] = CONTEXT_COURSE; // Heavily based on enrol_get_shared_courses(). $sql = "SELECT c.*, $ctxfields FROM {course} c JOIN {" . static::TABLE . "} cc ON cc.courseid = c.id AND cc.competencyid = :competencyid JOIN ( SELECT DISTINCT c.id FROM {enrol} e JOIN {user_enrolments} ue ON ue.enrolid = e.id AND ue.status = :active AND ue.userid = :userid JOIN {course} c ON c.id = e.courseid WHERE e.status = :enabled AND e.enrol $plugins ) ec ON ec.id = c.id LEFT JOIN {context} ctx ON ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel ORDER BY c.id"; $courses = $DB->get_records_sql($sql, $params); array_map('context_helper::preload_from_record', $courses); return $courses; } /** * Return a list of rules. * * @return array Indexed by outcome value. */ public static function get_ruleoutcome_list() { static $list = null; if ($list === null) { $list = array( self::OUTCOME_NONE => self::get_ruleoutcome_name(self::OUTCOME_NONE), self::OUTCOME_EVIDENCE => self::get_ruleoutcome_name(self::OUTCOME_EVIDENCE), self::OUTCOME_RECOMMEND => self::get_ruleoutcome_name(self::OUTCOME_RECOMMEND), self::OUTCOME_COMPLETE => self::get_ruleoutcome_name(self::OUTCOME_COMPLETE)); } return $list; } /** * Human readable rule name. * * @param int $ruleoutcome The value of ruleoutcome. * @return lang_string */ public static function get_ruleoutcome_name($ruleoutcome) { switch ($ruleoutcome) { case self::OUTCOME_NONE: $strname = 'none'; break; case self::OUTCOME_EVIDENCE: $strname = 'evidence'; break; case self::OUTCOME_RECOMMEND: $strname = 'recommend'; break; case self::OUTCOME_COMPLETE: $strname = 'complete'; break; default: throw new \moodle_exception('errorcoursecompetencyrule', 'core_competency', '', $ruleoutcome); break; } return new lang_string('coursecompetencyoutcome_' . $strname, 'core_competency'); } /** * Validate course ID. * * @param int $data The course ID. * @return true|lang_string */ protected function validate_courseid($data) { global $DB; if (!$DB->record_exists('course', array('id' => $data))) { return new lang_string('invalidcourseid', 'error'); } return true; } /** * Validate competency ID. * * @param int $data The competency ID. * @return true|lang_string */ protected function validate_competencyid($data) { if (!competency::record_exists($data)) { return new lang_string('invaliddata', 'error'); } return true; } /** * Return the course IDs and visible flags that include this competency. * * Only the ids and visible flag are returned, for the full records use list_courses. * * @param int $competencyid The competency id * @return array containing courseid and visible. */ public static function list_courses_min($competencyid) { global $DB; $results = $DB->get_records_sql('SELECT course.id as id, course.visible as visible FROM {' . self::TABLE . '} coursecomp JOIN {course} course ON coursecomp.courseid = course.id WHERE coursecomp.competencyid = ? ', array($competencyid)); return $results; } /** * Return partial course records foreach course that contains this competency. * * @param int $competencyid The competency id * @return array[stdClass] Array of course records containg id, visible, shortname, idnumber, fullname */ public static function list_courses($competencyid) { global $DB; // We need all the course summary exporter properties, plus category. $coursefields = course_summary_exporter::properties_definition(); $coursefields = array_map(function(string $field): string { return "course.{$field}"; }, array_keys($coursefields)); $results = $DB->get_records_sql('SELECT ' . implode(',', $coursefields) . ', course.category FROM {course} course JOIN {' . self::TABLE . '} coursecomp ON coursecomp.courseid = course.id WHERE coursecomp.competencyid = ? ', array($competencyid)); return $results; } /** * Count the competencies in this course. * * @param int $courseid The course id * @return int */ public static function count_competencies($courseid) { global $DB; $sql = 'SELECT COUNT(comp.id) FROM {' . self::TABLE . '} coursecomp JOIN {' . competency::TABLE . '} comp ON coursecomp.competencyid = comp.id WHERE coursecomp.courseid = ? '; $params = array($courseid); $results = $DB->count_records_sql($sql, $params); return $results; } /** * List the competencies in this course. * * @param int $courseid The course id * @return competency[] Indexed by competency ID. */ public static function list_competencies($courseid) { global $DB; $sql = 'SELECT comp.* FROM {' . competency::TABLE . '} comp JOIN {' . self::TABLE . '} coursecomp ON coursecomp.competencyid = comp.id WHERE coursecomp.courseid = ?'; $params = array($courseid); $sql .= ' ORDER BY coursecomp.sortorder ASC'; $results = $DB->get_recordset_sql($sql, $params); $instances = array(); foreach ($results as $result) { $comp = new competency(0, $result); $instances[$comp->get('id')] = $comp; } $results->close(); return $instances; } /** * Get a single competency from the course (only if it is really in the course). * * @param int $courseid The course id * @param int $competencyid The competency id * @return competency */ public static function get_competency($courseid, $competencyid) { global $DB; $sql = 'SELECT comp.* FROM {' . competency::TABLE . '} comp JOIN {' . self::TABLE . '} crscomp ON crscomp.competencyid = comp.id WHERE crscomp.courseid = ? AND crscomp.competencyid = ?'; $params = array($courseid, $competencyid); $result = $DB->get_record_sql($sql, $params); if (!$result) { throw new coding_exception('The competency does not belong to this course: ' . $competencyid . ', ' . $courseid); } return new competency(0, $result); } /** * Hook to execute after delete. * * @param bool $result Whether or not the delete was successful. * @return void */ protected function after_delete($result) { global $DB; if (!$result) { return; } $table = '{' . self::TABLE . '}'; $sql = "UPDATE $table SET sortorder = sortorder -1 WHERE courseid = ? AND sortorder > ?"; $DB->execute($sql, array($this->get('courseid'), $this->get('sortorder'))); } /** * Get the specified course_competency in this course. * * @param int $courseid The course id * @param int $competencyid The competency id * @return course_competency */ public static function get_course_competency($courseid, $competencyid) { global $DB; $sql = 'SELECT crscomp.* FROM {' . self::TABLE . '} crscomp WHERE crscomp.courseid = ? AND crscomp.competencyid = ?'; $params = array($courseid, $competencyid); $result = $DB->get_record_sql($sql, $params); if (!$result) { throw new coding_exception('The competency does not belong to this course: ' . $competencyid . ', ' . $courseid); } return new course_competency(0, $result); } /** * List the course_competencies in this course. * * @param int $courseid The course id * @return course_competency[] */ public static function list_course_competencies($courseid) { global $DB; $sql = 'SELECT coursecomp.* FROM {' . self::TABLE . '} coursecomp JOIN {' . competency::TABLE . '} comp ON coursecomp.competencyid = comp.id WHERE coursecomp.courseid = ?'; $params = array($courseid); $sql .= ' ORDER BY coursecomp.sortorder ASC'; $results = $DB->get_recordset_sql($sql, $params); $instances = array(); foreach ($results as $result) { array_push($instances, new course_competency(0, $result)); } $results->close(); return $instances; } /** * Check if course competency has records for competencies. * * @param array $competencyids Array of competencies ids. * @return boolean Return true if one or more than a competency was found in a course. */ public static function has_records_for_competencies($competencyids) { global $DB; list($insql, $params) = $DB->get_in_or_equal($competencyids, SQL_PARAMS_NAMED); return self::record_exists_select("competencyid $insql", $params); } } classes/user_competency.php000064400000040754152265256010012134 0ustar00. /** * Class for user_competency persistence. * * @package core_competency * @copyright 2015 Serge Gauthier * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_competency; defined('MOODLE_INTERNAL') || die(); use coding_exception; use context_course; use context_user; use comment; use lang_string; /** * Class for loading/storing user_competency from the DB. * * @copyright 2015 Serge Gauthier * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class user_competency extends persistent { /** Table name for user_competency persistency */ const TABLE = 'competency_usercomp'; /** Idle status */ const STATUS_IDLE = 0; /** Waiting for review status */ const STATUS_WAITING_FOR_REVIEW = 1; /** In review status */ const STATUS_IN_REVIEW = 2; /** * Return the definition of the properties of this model. * * @return array */ protected static function define_properties() { return array( 'userid' => array( 'type' => PARAM_INT, ), 'competencyid' => array( 'type' => PARAM_INT, ), 'status' => array( 'choices' => array( self::STATUS_IDLE, self::STATUS_WAITING_FOR_REVIEW, self::STATUS_IN_REVIEW, ), 'type' => PARAM_INT, 'default' => self::STATUS_IDLE, ), 'reviewerid' => array( 'type' => PARAM_INT, 'default' => null, 'null' => NULL_ALLOWED, ), 'proficiency' => array( 'type' => PARAM_BOOL, 'default' => null, 'null' => NULL_ALLOWED, ), 'grade' => array( 'type' => PARAM_INT, 'default' => null, 'null' => NULL_ALLOWED, ), ); } /** * Whether the current user can comment on this user competency. * * @return bool */ public function can_comment() { return static::can_comment_user($this->get('userid')); } /** * Whether the current user can read this user competency. * * @return bool */ public function can_read() { return static::can_read_user($this->get('userid')); } /** * Whether the current user can read comments on this user competency. * * @return bool */ public function can_read_comments() { return static::can_read_comments_user($this->get('userid')); } /** * Can the current user send the user competency for review? * * @return bool */ public function can_request_review() { return static::can_request_review_user($this->get('userid')); } /** * Can the current user review the user competency? * * @return bool */ public function can_review() { return static::can_review_user($this->get('userid')); } /** * Human readable status name. * * @param int $status The status code. * @return lang_string */ public static function get_status_name($status) { switch ($status) { case self::STATUS_IDLE: $strname = 'idle'; break; case self::STATUS_WAITING_FOR_REVIEW: $strname = 'waitingforreview'; break; case self::STATUS_IN_REVIEW: $strname = 'inreview'; break; default: throw new \moodle_exception('errorusercomptencystatus', 'core_competency', '', $status); break; } return new lang_string('usercompetencystatus_' . $strname, 'core_competency'); } /** * Get list of competency status. * * @return array */ public static function get_status_list() { static $list = null; if ($list === null) { $list = array( self::STATUS_IDLE => self::get_status_name(self::STATUS_IDLE), self::STATUS_WAITING_FOR_REVIEW => self::get_status_name(self::STATUS_WAITING_FOR_REVIEW), self::STATUS_IN_REVIEW => self::get_status_name(self::STATUS_IN_REVIEW)); } return $list; } /** * Get the comment object. * * @return comment */ public function get_comment_object() { global $CFG; require_once($CFG->dirroot . '/comment/lib.php'); if (!$this->get('id')) { throw new coding_exception('The user competency record must exist.'); } $comment = new comment((object) array( 'context' => $this->get_context(), 'component' => 'competency', // This cannot be named 'core_competency'. 'itemid' => $this->get('id'), 'area' => 'user_competency', 'showcount' => true, )); $comment->set_fullwidth(true); return $comment; } /** * Return the competency Object. * * @return competency Competency Object */ public function get_competency() { return new competency($this->get('competencyid')); } /** * Get the context. * * @return \context The context. */ public function get_context() { return context_user::instance($this->get('userid')); } /** * Find the plans for the user and this competency. * * Note that this: * - does not perform any capability check. * - may return completed plans. * - may return an empty array. * * @return plans[] */ public function get_plans() { return plan::get_by_user_and_competency($this->get('userid'), $this->get('competencyid')); } /** * Validate the user ID. * * @param int $value The value. * @return true|lang_string */ protected function validate_userid($value) { global $DB; if (!$DB->record_exists('user', array('id' => $value))) { return new lang_string('invaliduserid', 'error'); } return true; } /** * Validate the competency ID. * * @param int $value The value. * @return true|lang_string */ protected function validate_competencyid($value) { if (!competency::record_exists($value)) { return new lang_string('errornocompetency', 'core_competency', $value); } return true; } /** * Validate the proficiency. * * @param int $value The value. * @return true|lang_string */ protected function validate_proficiency($value) { $grade = $this->get('grade'); if ($grade !== null && $value === null) { // We must set a proficiency when we set a grade. return new lang_string('invaliddata', 'error'); } else if ($grade === null && $value !== null) { // We must not set a proficiency when we don't set a grade. return new lang_string('invaliddata', 'error'); } return true; } /** * Validate the reviewer ID. * * @param int $value The value. * @return true|lang_string */ protected function validate_reviewerid($value) { global $DB; if ($value !== null && !$DB->record_exists('user', array('id' => $value))) { return new lang_string('invaliduserid', 'error'); } return true; } /** * Validate the grade. * * @param int $value The value. * @return true|lang_string */ protected function validate_grade($value) { if ($value !== null) { if ($value <= 0) { return new lang_string('invalidgrade', 'core_competency'); } // TODO MDL-52243 Use a core method to validate the grade_scale item. // Check if grade exist in the scale item values. $competency = $this->get_competency(); if (!array_key_exists($value - 1 , $competency->get_scale()->scale_items)) { return new lang_string('invalidgrade', 'core_competency'); } } return true; } /** * Can the current user comment on a user's competency? * * @param int $userid The user ID the competency belongs to. * @return bool */ public static function can_comment_user($userid) { global $USER; $capabilities = array('moodle/competency:usercompetencycomment'); if ($USER->id == $userid) { $capabilities[] = 'moodle/competency:usercompetencycommentown'; } if (has_any_capability($capabilities, context_user::instance($userid))) { return true; } return false; } /** * Can the current user grade a user's user competency? * * @param int $userid The user ID the competency belongs to. * @return bool */ public static function can_grade_user($userid) { $ratecap = 'moodle/competency:competencygrade'; return has_capability($ratecap, context_user::instance($userid)); } /** * Can the current user grade a user's user competency in a course? * * @param int $userid The user ID the competency belongs to. * @param int $courseid The course ID. * @return bool */ public static function can_grade_user_in_course($userid, $courseid) { $ratecap = 'moodle/competency:competencygrade'; return has_capability($ratecap, context_course::instance($courseid)) || static::can_grade_user($userid); } /** * Can the current user read the comments on a user's competency? * * @param int $userid The user ID the competency belongs to. * @return bool */ public static function can_read_comments_user($userid) { // Everyone who can read the user competency can read the comments. return static::can_read_user($userid); } /** * Can the current user read the user competencies of a user in a course? * * @param int $userid The user ID the competency belongs to. * @param int $courseid The course ID. * @return bool */ public static function can_read_user_in_course($userid, $courseid) { $capability = 'moodle/competency:usercompetencyview'; return has_capability($capability, context_course::instance($courseid)) || static::can_read_user($userid); } /** * Can the current user read a user's competency? * * @param int $userid The user ID the competency belongs to. * @return bool */ public static function can_read_user($userid) { $capability = 'moodle/competency:usercompetencyview'; return has_capability($capability, context_user::instance($userid)) || plan::can_read_user($userid); } /** * Can the current user send a user's competency for review? * * Note that the status 'review' is not meant to be used for student to self-assess * themselves, then to ask the teacher to review their assessment. It is more intended * for a student to provide evidence of prior learning and request their review. * * @param int $userid The user ID the competency belongs to. * @return bool */ public static function can_request_review_user($userid) { global $USER; $capabilities = array('moodle/competency:usercompetencyrequestreview'); if ($USER->id == $userid) { $capabilities[] = 'moodle/competency:usercompetencyrequestreviewown'; } if (has_any_capability($capabilities, context_user::instance($userid))) { return true; } return false; } /** * Can the current user review the user competency? * * @param int $userid The user ID the competency belongs to. * @return bool */ public static function can_review_user($userid) { $capability = 'moodle/competency:usercompetencyreview'; return has_capability($capability, context_user::instance($userid)); } /** * Create a new user_competency object. * * Note, this is intended to be used to create a blank relation, for instance when * the record was not found in the database. This does not save the model. * * @param int $userid The user ID. * @param int $competencyid The competency ID. * @return \core_competency\user_competency */ public static function create_relation($userid, $competencyid) { $relation = new user_competency(0, (object) array('userid' => $userid, 'competencyid' => $competencyid)); return $relation; } /** * Fetch a competency by user competency ID. * * This is a convenience method to attempt to efficiently fetch a competency when * the only information we have is the user_competency ID, in evidence for instance. * * @param int $id The user competency ID. * @return competency */ public static function get_competency_by_usercompetencyid($id) { global $DB; $sql = "SELECT c.* FROM {" . self::TABLE . "} uc JOIN {" . competency::TABLE . "} c ON c.id = uc.competencyid WHERE uc.id = ?"; $record = $DB->get_record_sql($sql, array($id), MUST_EXIST); return new competency(0, $record); } /** * Get multiple user_competency for a user. * * @param int $userid * @param array $competenciesorids Limit search to those competencies, or competency IDs. * @return \core_competency\user_competency[] */ public static function get_multiple($userid, ?array $competenciesorids = null) { global $DB; $params = array(); $params['userid'] = $userid; $sql = '1 = 1'; if (!empty($competenciesorids)) { $test = reset($competenciesorids); if (is_number($test)) { $ids = $competenciesorids; } else { $ids = array(); foreach ($competenciesorids as $comp) { $ids[] = $comp->get('id'); } } list($insql, $inparams) = $DB->get_in_or_equal($ids, SQL_PARAMS_NAMED); $params += $inparams; $sql = "competencyid $insql"; } // Order by ID to prevent random ordering. return self::get_records_select("userid = :userid AND $sql", $params, 'id ASC'); } /** * Checks if a competency has user competency records. * * @param int $competencyid The competency ID * @return boolean */ public static function has_records_for_competency($competencyid) { return self::record_exists_select('competencyid = ?', array($competencyid)); } /** * Checks if any of the competencies of a framework has a user competency record. * * @param int $frameworkid The competency framework ID. * @return boolean */ public static function has_records_for_framework($frameworkid) { global $DB; $sql = "SELECT 'x' FROM {" . self::TABLE . "} uc JOIN {" . competency::TABLE . "} c ON uc.competencyid = c.id WHERE c.competencyframeworkid = ?"; $params = array($frameworkid); return $DB->record_exists_sql($sql, $params); } /** * Check if user competency has records for competencies. * * @param array $competencyids The competencies ids. * @return boolean Return true if the delete was successfull. */ public static function has_records_for_competencies($competencyids) { global $DB; list($insql, $params) = $DB->get_in_or_equal($competencyids, SQL_PARAMS_NAMED); return self::record_exists_select("competencyid $insql", $params); } } classes/url.php000064400000014112152265256010007517 0ustar00. /** * URL manager. * * @package core_competency * @copyright 2016 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_competency; defined('MOODLE_INTERNAL') || die(); use moodle_url; /** * URL manager class. * * This class has to be used to get the URL to a resource, this allows for different * alternate frontends to be used without resorting to core hacks. Note that you * do not have to use this when you are navigating between pages of your own plugin. * * To set another resolver, set the following config value in config.php: * $CFG->core_competency_url_resolver = 'your_plugin\\your_url_resolver_class'; * * Your URL resolver should implement the same methods as the ones listed in * this class (except for {{@link self::get()}}) but not statically. * * /!\ Note, resolvers MUST NEVER assume that the resource, or the resources * represented by the arguments, still exist. * * @package core_competency * @copyright 2016 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class url { /** @var url_resolver The URL resolver instance.*/ protected static $resolver; /** * Defer to the resolver. * * @param string $resource The resource type. * @param array $args The arguments. * @return mixed */ protected static function get($resource, $args) { global $CFG; if (!isset(static::$resolver)) { $klass = !empty($CFG->core_competency_url_resolver) ? $CFG->core_competency_url_resolver : 'tool_lp\\url_resolver'; static::$resolver = new $klass(); } if (!method_exists(static::$resolver, $resource)) { debugging("URL for '$resource' not implemented.", DEBUG_DEVELOPER); return new moodle_url('/'); } return call_user_func_array([static::$resolver, $resource], $args); } /** * The URL where the competency can be found. * * @param int $competencyid The competency ID. * @param int $pagecontextid The ID of the context we are in. * @return moodle_url */ public static function competency($competencyid, $pagecontextid) { return static::get(__FUNCTION__, func_get_args()); } /** * The URL where the framework can be found. * * @param int $frameworkid The framework ID. * @param int $pagecontextid The ID of the context we are in. * @return moodle_url */ public static function framework($frameworkid, $pagecontextid) { return static::get(__FUNCTION__, func_get_args()); } /** * The URL where the frameworks can be found. * * @param int $pagecontextid The ID of the context that we are browsing. * @return moodle_url */ public static function frameworks($pagecontextid) { return static::get(__FUNCTION__, func_get_args()); } /** * The URL where the plan can be found. * * @param int $planid The plan ID. * @return moodle_url */ public static function plan($planid) { return static::get(__FUNCTION__, func_get_args()); } /** * The URL where the plans of a user can be found. * * @param int $userid The user ID. * @return moodle_url */ public static function plans($userid) { return static::get(__FUNCTION__, func_get_args()); } /** * The URL where the template can be found. * * @param int $templateid The template ID. * @param int $pagecontextid The ID of the context we are in. * @return moodle_url */ public static function template($templateid, $pagecontextid) { return static::get(__FUNCTION__, func_get_args()); } /** * The URL where the templates can be found. * * @param int $pagecontextid The ID of the context that we are browsing. * @return moodle_url */ public function templates($pagecontextid) { return static::get(__FUNCTION__, func_get_args()); } /** * The URL where the user competency can be found. * * @param int $usercompetencyid The user competency ID * @return moodle_url */ public static function user_competency($usercompetencyid) { return static::get(__FUNCTION__, func_get_args()); } /** * The URL where the user competency can be found in the context of a course. * * @param int $userid The user ID * @param int $competencyid The competency ID. * @param int $courseid The course ID. * @return moodle_url */ public static function user_competency_in_course($userid, $competencyid, $courseid) { return static::get(__FUNCTION__, func_get_args()); } /** * The URL where the user competency can be found in the context of a plan. * * @param int $userid The user ID * @param int $competencyid The competency ID. * @param int $planid The plan ID. * @return moodle_url */ public static function user_competency_in_plan($userid, $competencyid, $planid) { return static::get(__FUNCTION__, func_get_args()); } /** * The URL where the user evidence (of prior learning) can be found. * * @param int $userevidenceid The user evidence ID * @return moodle_url */ public static function user_evidence($userevidenceid) { return static::get(__FUNCTION__, func_get_args()); } } classes/competency_rule.php000064400000006036152265256010012120 0ustar00. /** * Competency rule base. * * @package core_competency * @copyright 2015 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_competency; defined('MOODLE_INTERNAL') || die(); use coding_exception; /** * Competency rule base abstract class. * * Rules are attached to a competency and then tested against a user competency * to determine whether or not it matches. * * @package core_competency * @copyright 2015 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ abstract class competency_rule { /** @var competency The competency. */ protected $competency; /** * Constructor. * * @param competency $competency The competency. */ public function __construct(competency $competency) { $class = $competency->get('ruletype'); if (!$class || !($this instanceof $class)) { throw new coding_exception('This competency does not use this rule.'); } $this->competency = $competency; } /** * Get the rule config. * * @return mixed */ protected function get_config() { return $this->competency->get('ruleconfig'); } /** * Whether or not the rule is matched. * * @param user_competency $usercompetency The user competency to test against. * @return bool */ abstract public function matches(user_competency $usercompetency); /** * Validate the rule config. * * @param string $value The value to validate. * @return bool */ abstract public function validate_config($value); /** * The name of the rule. * * @return lang_string */ public static function get_name() { throw new coding_exception('Method not implemented.'); } /** * Migrate rule config from one set of competencies to another. * * Exceptions should be thrown when the migration can not be performed. * * @param string $config Original config rule of a competency. * @param array $mappings Array that matches the original competency IDs with the new competencies objects. * @return string New configuration. * @throws Exception */ public static function migrate_config($config, $mappings) { return $config; } } classes/template_competency.php000064400000021527152265256010012766 0ustar00. /** * Class for loading/storing competencies from the DB. * * @package core_competency * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_competency; defined('MOODLE_INTERNAL') || die(); use stdClass; /** * Class for loading/storing template_competencies from the DB. * * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class template_competency extends persistent { const TABLE = 'competency_templatecomp'; /** * Return the definition of the properties of this model. * * @return array */ protected static function define_properties() { return array( 'templateid' => array( 'type' => PARAM_INT, 'default' => 0, ), 'competencyid' => array( 'type' => PARAM_INT, 'default' => 0, ), 'sortorder' => array( 'type' => PARAM_INT, 'default' => 0, ), ); } /** * Count the templates using a competency. * * @param int $competencyid The competency id * @param bool $onlyvisible If true, only count visible templates using this competency. * @return int */ public static function count_templates($competencyid, $onlyvisible) { global $DB; $sql = 'SELECT COUNT(tpl.id) FROM {' . self::TABLE . '} tplcomp JOIN {' . template::TABLE . '} tpl ON tplcomp.templateid = tpl.id WHERE tplcomp.competencyid = ? '; $params = array($competencyid); if ($onlyvisible) { $sql .= ' AND tpl.visible = ?'; $params[] = 1; } $results = $DB->count_records_sql($sql, $params); return $results; } /** * List the templates using a competency. * * @param int $competencyid The competency id * @param bool $onlyvisible If true, only count visible templates using this competency. * @return array[competency] */ public static function list_templates($competencyid, $onlyvisible) { global $DB; $sql = 'SELECT tpl.* FROM {' . template::TABLE . '} tpl JOIN {' . self::TABLE . '} tplcomp ON tplcomp.templateid = tpl.id WHERE tplcomp.competencyid = ? '; $params = array($competencyid); if ($onlyvisible) { $sql .= ' AND tpl.visible = ?'; $params[] = 1; } $sql .= ' ORDER BY tpl.id ASC'; $results = $DB->get_records_sql($sql, $params); $instances = array(); foreach ($results as $result) { array_push($instances, new template(0, $result)); } return $instances; } /** * Count the competencies in a template. * * @param int $templateid The template id * @return int */ public static function count_competencies($templateid) { global $DB; $sql = 'SELECT COUNT(comp.id) FROM {' . self::TABLE . '} tplcomp JOIN {' . competency::TABLE . '} comp ON tplcomp.competencyid = comp.id WHERE tplcomp.templateid = ? '; $params = array($templateid); $results = $DB->count_records_sql($sql, $params); return $results; } /** * Count the competencies in a template with no links to courses. * * @param int $templateid The template id * @return int */ public static function count_competencies_with_no_courses($templateid) { global $DB; $sql = 'SELECT COUNT(comp.id) FROM {' . self::TABLE . '} tplcomp JOIN {' . competency::TABLE . '} comp ON tplcomp.competencyid = comp.id LEFT JOIN {' . course_competency::TABLE . '} crscomp ON crscomp.competencyid = comp.id WHERE tplcomp.templateid = ? AND crscomp.id IS NULL'; $params = array($templateid); $results = $DB->count_records_sql($sql, $params); return $results; } /** * Get a single competency from the template (only if it is really in the template). * * @param int $templateid The template id * @param int $competencyid The competency id * @return competency */ public static function get_competency($templateid, $competencyid) { global $DB; $sql = 'SELECT comp.* FROM {' . competency::TABLE . '} comp JOIN {' . self::TABLE . '} tplcomp ON tplcomp.competencyid = comp.id WHERE tplcomp.templateid = ? AND tplcomp.competencyid = ?'; $params = array($templateid, $competencyid); $result = $DB->get_record_sql($sql, $params); if (!$result) { throw new \coding_exception('The competency does not belong to this template: ' . $competencyid . ', ' . $templateid); } return new competency(0, $result); } /** * List the competencies in this template. * * @param int $templateid The template id * @return array[competency] */ public static function list_competencies($templateid) { global $DB; $sql = 'SELECT comp.* FROM {' . competency::TABLE . '} comp JOIN {' . self::TABLE . '} tplcomp ON tplcomp.competencyid = comp.id WHERE tplcomp.templateid = ? ORDER BY tplcomp.sortorder ASC, tplcomp.id ASC'; $params = array($templateid); $results = $DB->get_records_sql($sql, $params); $instances = array(); foreach ($results as $result) { array_push($instances, new competency(0, $result)); } return $instances; } /** * Remove the competencies in this template. * * @param int $templateid The template id * @return boolen */ public static function delete_by_templateid($templateid) { global $DB; return $DB->delete_records(self::TABLE, array('templateid' => $templateid)); } /** * Hook to execute before validate. * * @return void */ protected function before_validate() { if (($this->get('id') && $this->get('sortorder') === null) || !$this->get('id')) { $this->set('sortorder', $this->count_records(array('templateid' => $this->get('templateid')))); } } /** * Validate competencyid. * * @param int $value ID. * @return true|lang_string */ protected function validate_competencyid($value) { if (!competency::record_exists($value)) { return new \lang_string('invaliddata', 'error'); } return true; } /** * Validate templateid. * * @param int $value ID. * @return true|lang_string */ protected function validate_templateid($value) { if (!template::record_exists($value)) { return new \lang_string('invaliddata', 'error'); } return true; } /** * Hook to execute after delete. * * @param bool $result Whether or not the delete was successful. * @return void */ protected function after_delete($result) { global $DB; if (!$result) { return; } $table = '{' . self::TABLE . '}'; $sql = "UPDATE $table SET sortorder = sortorder -1 WHERE templateid = ? AND sortorder > ?"; $DB->execute($sql, array($this->get('templateid'), $this->get('sortorder'))); } /** * Check if template competency has records for competencies. * * @param array $competencyids Array of competencies ids. * @return boolean Return true if competencies were found in template_competency. */ public static function has_records_for_competencies($competencyids) { global $DB; list($insql, $params) = $DB->get_in_or_equal($competencyids, SQL_PARAMS_NAMED); return self::record_exists_select("competencyid $insql", $params); } } classes/template.php000064400000013340152265256010010532 0ustar00. /** * Class for loading/storing learning plan templates from the DB. * * @package core_competency * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_competency; defined('MOODLE_INTERNAL') || die(); use context; use lang_string; use stdClass; /** * Class for loading/storing learning plan templates from the DB. * * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class template extends persistent { const TABLE = 'competency_template'; /** @var template object before update. */ protected $beforeupdate = null; /** * Return the definition of the properties of this model. * * @return array */ protected static function define_properties() { return array( 'shortname' => array( 'type' => PARAM_TEXT, ), 'description' => array( 'default' => '', 'type' => PARAM_CLEANHTML, ), 'descriptionformat' => array( 'choices' => array(FORMAT_HTML, FORMAT_MOODLE, FORMAT_PLAIN, FORMAT_MARKDOWN), 'type' => PARAM_INT, 'default' => FORMAT_HTML ), 'duedate' => array( 'default' => 0, 'type' => PARAM_INT, ), 'visible' => array( 'default' => 1, 'type' => PARAM_BOOL, ), 'contextid' => array( 'type' => PARAM_INT ), ); } /** * Hook to execute after an update. * * @param bool $result Whether or not the update was successful. * @return void */ protected function after_update($result) { $this->beforeupdate = null; } /** * Hook to execute before validate. * * @return void */ protected function before_validate() { $this->beforeupdate = null; // During update. if ($this->get('id')) { $this->beforeupdate = new self($this->get('id')); } } /** * Whether or not the current user can read the template. * * @return bool */ public function can_manage() { return self::can_manage_context($this->get_context()); } /** * Whether or not the current user can manage the template. * * @param context $context * @return bool */ public static function can_manage_context($context) { return has_capability('moodle/competency:templatemanage', $context); } /** * Whether or not the current user can read the template. * * @return bool */ public function can_read() { return self::can_read_context($this->get_context()); } /** * Whether or not the current user can read the template. * * @param context $context * @return bool */ public static function can_read_context($context) { return has_capability('moodle/competency:templateview', $context) || self::can_manage_context($context); } /** * Get the context. * * @return context The context */ public function get_context() { return context::instance_by_id($this->get('contextid')); } /** * Validate the context ID. * * @param int $value The context ID. * @return bool|lang_string */ protected function validate_contextid($value) { $context = context::instance_by_id($value, IGNORE_MISSING); if (!$context) { return new lang_string('invalidcontext', 'error'); } else if ($context->contextlevel != CONTEXT_SYSTEM && $context->contextlevel != CONTEXT_COURSECAT) { return new lang_string('invalidcontext', 'error'); } return true; } /** * Validate the due date. * * The due date can always be changed, but when it is it must be: * - unset * - set in the future. * * @param int $value The due date. * @return bool|lang_string */ protected function validate_duedate($value) { // During update. if ($this->get('id')) { $before = $this->beforeupdate->get('duedate'); // The value has not changed, then it's always OK. if ($before == $value) { return true; } } // During create and update, the date must be set in the future, or not set. if (!empty($value) && $value <= time() - 600) { // We cannot set the date in the past. But we allow for 10 minutes of margin so that // a user can set the due date to "now" without risking to hit a validation error. return new lang_string('errorcannotsetduedateinthepast', 'core_competency'); } return true; } /** * Returns true when the template has user learning plans. * * @return boolean */ public function has_plans() { return plan::has_records_for_template($this->get('id')); } } classes/evidence.php000064400000024443152265256010010507 0ustar00. /** * Evidence persistent file. * * @package core_competency * @copyright 2015 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_competency; defined('MOODLE_INTERNAL') || die(); use coding_exception; use context; use context_user; use lang_string; use moodle_exception; use stdClass; /** * Evidence persistent class. * * @package core_competency * @copyright 2015 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class evidence extends persistent { const TABLE = 'competency_evidence'; /** Action logging. */ const ACTION_LOG = 0; /** Action rating a competency when no rating is set. */ const ACTION_COMPLETE = 2; /** Action rating a competency. */ const ACTION_OVERRIDE = 3; /** * Return the definition of the properties of this model. * * @return array */ protected static function define_properties() { return array( 'usercompetencyid' => array( 'type' => PARAM_INT ), 'contextid' => array( 'type' => PARAM_INT ), 'action' => array( 'type' => PARAM_INT, 'choices' => array(self::ACTION_LOG, self::ACTION_COMPLETE, self::ACTION_OVERRIDE) ), 'actionuserid' => array( 'type' => PARAM_INT, 'default' => null, 'null' => NULL_ALLOWED ), 'descidentifier' => array( 'type' => PARAM_STRINGID ), 'desccomponent' => array( 'type' => PARAM_COMPONENT ), 'desca' => array( 'type' => PARAM_RAW, 'default' => null, 'null' => NULL_ALLOWED ), 'url' => array( 'type' => PARAM_URL, 'default' => null, 'null' => NULL_ALLOWED ), 'grade' => array( 'type' => PARAM_INT, 'default' => null, 'null' => NULL_ALLOWED ), 'note' => array( 'type' => PARAM_NOTAGS, 'default' => null, 'null' => NULL_ALLOWED ) ); } /** * Return the competency linked to this. * * @return competency */ public function get_competency() { return user_competency::get_competency_by_usercompetencyid($this->get('usercompetencyid')); } /** * Return the evidence's context. * * @return context */ public function get_context() { return context::instance_by_id($this->get('contextid')); } /** * Convenience method to get the description $a. * * @return mixed */ protected function get_desca() { $value = $this->raw_get('desca'); if ($value !== null) { $value = json_decode($value); } return $value; } /** * Convenience method to get the description. * * @return lang_string */ public function get_description() { return new lang_string($this->get('descidentifier'), $this->get('desccomponent'), $this->get_desca()); } /** * Convenience method to set the description $a. * * @param mixed $value * @return mixed */ protected function set_desca($value) { if ($value !== null) { if (!is_scalar($value) && !is_array($value) && !($value instanceof stdClass)) { throw new coding_exception('$a format not supported.'); } $value = json_encode($value); } $this->raw_set('desca', $value); } /** * Convenience method handling moodle_urls. * * @param null|string|moodle_url $url The URL. */ protected function set_url($url) { if ($url instanceof \moodle_url) { $url = $url->out(false); } $this->raw_set('url', $url); } /** * Validate the action user ID. * * @param int $value A user ID. * @return true|lang_string */ protected function validate_actionuserid($value) { if ($value !== null && !\core_user::is_real_user($value)) { return new lang_string('invaliddata', 'error'); } return true; } /** * Validate the context ID. * * @param int $value * @return true|lang_string */ protected function validate_contextid($value) { try { context::instance_by_id($value); } catch (moodle_exception $e) { // That does not look good... return new lang_string('invaliddata', 'error'); } return true; } /** * Validate the description $a. * * @param string $value * @return true|lang_string */ protected function validate_desca($value) { if ($value === null) { return true; } $desc = json_decode($value); if ($desc === null && json_last_error() !== JSON_ERROR_NONE) { return new lang_string('invaliddata', 'error'); } return true; } /** * Validate the description identifier. * * Only validate string existence during create. If the string is removed later on we should * not prevent this model from being updated. Alternatively we could check if the string has * changed before performing the check but this overhead is not required for now. * An evidence should usually never be updated anyway. * * @param string $value * @return true|lang_string */ protected function validate_descidentifier($value) { if (!$this->get('id') && !get_string_manager()->string_exists($value, $this->get('desccomponent'))) { return new lang_string('invalidevidencedesc', 'core_competency'); } return true; } /** * Validate the grade. * * For performance reason we do not validate that the grade is a valid item of the * scale associated with the competency or framework. * * @param int $value The value. * @return true|lang_string */ protected function validate_grade($value) { if ($value !== null && $value <= 0) { return new lang_string('invalidgrade', 'core_competency'); } $action = $this->get('action'); if ($value === null && $action == self::ACTION_COMPLETE) { return new lang_string('invalidgrade', 'core_competency'); } else if ($value !== null && $action == self::ACTION_LOG) { return new lang_string('invalidgrade', 'core_competency'); } if ($value !== null) { // TODO MDL-52243 Use a core method to validate the grade_scale item. // Check if grade exist in the scale item values. $competency = $this->get_competency(); if (!array_key_exists($value - 1, $competency->get_scale()->scale_items)) { return new lang_string('invalidgrade', 'core_competency'); } } return true; } /** * Validate the user competency. * * @param int $value * @return true|lang_string */ protected function validate_usercompetencyid($value) { if (!user_competency::record_exists($value)) { return new lang_string('invaliddata', 'error'); } return true; } /** * Whether the current user can delete an evidence in the context of a user. * * @param int $userid The user ID the evidence belongs to. * @return bool */ public static function can_delete_user($userid) { return has_capability('moodle/competency:evidencedelete', context_user::instance($userid)); } /** * Load a list of records in a context for a user competency. * * @param int $usercompetencyid The id of the user competency. * @param context $context Context to filter the evidence list. * @param string $sort The field from the evidence table to sort on. * @param string $order The sort direction * @param int $skip Limitstart. * @param int $limit Number of rows to return. * * @return \core_competency\persistent[] */ public static function get_records_for_usercompetency($usercompetencyid, \context $context, $sort = '', $order = 'ASC', $skip = 0, $limit = 0) { global $DB; $params = array( 'usercompid' => $usercompetencyid, 'path' => $context->path . '/%', 'contextid' => $context->id ); if (!empty($sort)) { $sort = ' ORDER BY e.' . $sort . ' ' . $order . ', e.id ASC'; } else { $sort = ' ORDER BY e.id ASC'; } $sql = 'SELECT e.* FROM {' . static::TABLE . '} e JOIN {context} c ON c.id = e.contextid WHERE (c.path LIKE :path OR c.id = :contextid) AND e.usercompetencyid = :usercompid ' . $sort; $records = $DB->get_records_sql($sql, $params, $skip, $limit); $instances = array(); foreach ($records as $record) { $newrecord = new static(0, $record); array_push($instances, $newrecord); } return $instances; } } upgrade.txt000064400000002144152265256010006741 0ustar00=== 4.5 Onwards === This file has been replaced by UPGRADING.md. See MDL-81125 for further information. === This files describes API changes in /competency/*. The information provided here is intended especially for developers. === 3.7 === * tool_lp can render the ui for a competency summary or a competency picker. Use get_plugins_with_function('competency_picker') or get_plugins_with_function('render_competency_summary') to call it. === 3.3 === * Deprecated classes and their new equivalent: - core_competency\persistent -> core\persistent - core_competency\invalid_persistent_exception -> core\invalid_persistent_exception - core_competency\external\exporter -> core\external\exporter - core_competency\external\persistent_exporter -> core\external\persistent_exporter - core_competency\external\comment_area_exporter -> core_cohort\external\comment_area_exporter - core_competency\external\stored_file_exporter -> core_files\external\stored_file_exporter - core_competency\external\user_summary_exporter -> core_user\external\user_summary_exporter tests/reportbuilder/datasource/competencies_test.php000064400000041330152265256010017155 0ustar00. declare(strict_types=1); namespace core_competency\reportbuilder\datasource; use core_competency_generator; use core_competency\user_competency; use core_reportbuilder_generator; use core_reportbuilder\local\filters\{boolean_select, date, select, text}; use core_reportbuilder\tests\core_reportbuilder_testcase; /** * Unit tests for comptencies datasource * * @package core_competency * @covers \core_competency\reportbuilder\datasource\competencies * @copyright 2024 Paul Holden * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class competencies_test extends core_reportbuilder_testcase { /** * Test default datasource */ public function test_datasource_default(): void { $this->resetAfterTest(); $user = $this->getDataGenerator()->create_user(); /** @var core_competency_generator $generator */ $generator = $this->getDataGenerator()->get_plugin_generator('core_competency'); // First framework has two competencies. $framework = $generator->create_framework(['shortname' => 'Zoology']); $generator->create_competency(['competencyframeworkid' => $framework->get('id'), 'shortname' => 'Zebras']); $competency = $generator->create_competency([ 'competencyframeworkid' => $framework->get('id'), 'shortname' => 'Aardvarks', ]); // Framework two has no competencies. $generator->create_framework(['shortname' => 'Algebra']); // Assign user to second competency. $generator->create_user_competency([ 'competencyid' => $competency->get('id'), 'userid' => $user->id, 'proficiency' => true, 'grade' => 1, ]); /** @var core_reportbuilder_generator $generator */ $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder'); $report = $generator->create_report(['name' => 'Competencies', 'source' => competencies::class, 'default' => 1]); $content = $this->get_custom_report_content($report->get('id')); // Default columns are framework name, competency name, user fullname, proficient. Sorted by first three. $this->assertEquals([ ['Algebra', '', '', ''], ['Zoology', 'Aardvarks', fullname($user), 'Yes'], ['Zoology', 'Zebras', '', ''], ], array_map('array_values', $content)); } /** * Test datasource columns that aren't added by default */ public function test_datasource_non_default_columns(): void { $this->resetAfterTest(); $user = $this->getDataGenerator()->create_user(); $cohort = $this->getDataGenerator()->create_cohort(['name' => 'My cohort']); cohort_add_member($cohort->id, $user->id); $scale = $this->getDataGenerator()->create_scale(['name' => 'My scale', 'scale' => 'A,B,C,D']); /** @var core_competency_generator $generator */ $generator = $this->getDataGenerator()->get_plugin_generator('core_competency'); $framework = $generator->create_framework(['description' => 'So cool', 'idnumber' => 'FRM101', 'scaleid' => $scale->id]); $competency = $generator->create_competency(['competencyframeworkid' => $framework->get('id'), 'idnumber' => 'COM101']); $generator->create_user_competency([ 'competencyid' => $competency->get('id'), 'userid' => $user->id, 'proficiency' => true, 'grade' => 3, ]); /** @var core_reportbuilder_generator $generator */ $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder'); $report = $generator->create_report(['name' => 'Competencies', 'source' => competencies::class, 'default' => 0]); // Framework. $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'framework:description']); $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'framework:idnumber']); $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'framework:scale']); $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'framework:visible']); $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'framework:timecreated']); $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'framework:timemodified']); // Competency. $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'competency:idnumber']); $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'competency:timecreated']); $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'competency:timemodified']); // User competency. $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'usercompetency:status']); $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'usercompetency:rating']); // Cohort. $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'cohort:name']); $content = $this->get_custom_report_content($report->get('id')); $this->assertCount(1, $content); [ $frameworkdescription, $frameworkidnumber, $frameworkscale, $frameworkvisible, $frameworktimecreated, $frameworktimemodified, $competencyidnumber, $competencytimecreated, $competencytimemodified, $usercompetencystatus, $usercompetencyrating, $cohortname, ] = array_values($content[0]); $this->assertEquals('So cool', $frameworkdescription); $this->assertEquals('FRM101', $frameworkidnumber); $this->assertEquals('My scale', $frameworkscale); $this->assertEquals('Yes', $frameworkvisible); $this->assertNotEmpty($frameworktimecreated); $this->assertNotEmpty($frameworktimemodified); $this->assertEquals('COM101', $competencyidnumber); $this->assertNotEmpty($competencytimecreated); $this->assertNotEmpty($competencytimemodified); $this->assertEquals('Idle', $usercompetencystatus); $this->assertEquals('C', $usercompetencyrating); $this->assertEquals('My cohort', $cohortname); } /** * Data provider for {@see test_datasource_filters} * * @return array[] */ public static function datasource_filters_provider(): array { return [ // Framework. 'Framework name' => ['framework:name', [ 'framework:name_operator' => text::IS_EQUAL_TO, 'framework:name_value' => 'How much I care', ], true], 'Framework name (no match)' => ['framework:name', [ 'framework:name_operator' => text::IS_EQUAL_TO, 'framework:name_value' => 'Something else', ], false], 'Framework idnumber' => ['framework:idnumber', [ 'framework:idnumber_operator' => text::IS_EQUAL_TO, 'framework:idnumber_value' => 'FRM101', ], true], 'Framework idnumber (no match)' => ['framework:idnumber', [ 'framework:idnumber_operator' => text::IS_EQUAL_TO, 'framework:idnumber_value' => 'FRM102', ], false], 'Framework scale' => ['framework:scale', [ 'framework:scale_operator' => select::EQUAL_TO, 'framework:scale_value' => '', ], true], 'Framework scale (no match)' => ['framework:scale', [ 'framework:scale_operator' => select::NOT_EQUAL_TO, 'framework:scale_value' => '', ], false], 'Framework visible' => ['framework:visible', [ 'framework:visible_operator' => boolean_select::CHECKED, ], true], 'Framework visible (no match)' => ['framework:visible', [ 'framework:visible_operator' => boolean_select::NOT_CHECKED, ], false], 'Framework time created' => ['framework:timecreated', [ 'framework:timecreated_operator' => date::DATE_RANGE, 'framework:timecreated_from' => 1622502000, ], true], 'Framework time created (no match)' => ['framework:timecreated', [ 'framework:timecreated_operator' => date::DATE_RANGE, 'framework:timecreated_to' => 1622502000, ], false], 'Framework time modified' => ['framework:timemodified', [ 'framework:timemodified_operator' => date::DATE_RANGE, 'framework:timemodified_from' => 1622502000, ], true], 'Framework time modified (no match)' => ['framework:timemodified', [ 'framework:timemodified_operator' => date::DATE_RANGE, 'framework:timemodified_to' => 1622502000, ], false], // Competency. 'Competency name' => ['competency:name', [ 'competency:name_operator' => text::IS_EQUAL_TO, 'competency:name_value' => 'My framework', ], true], 'Competency name (no match)' => ['competency:name', [ 'competency:name_operator' => text::IS_EQUAL_TO, 'competency:name_value' => 'Something else', ], false], 'Competency idnumber' => ['competency:idnumber', [ 'competency:idnumber_operator' => text::IS_EQUAL_TO, 'competency:idnumber_value' => 'COM101', ], true], 'Competency idnumber (no match)' => ['competency:idnumber', [ 'competency:idnumber_operator' => text::IS_EQUAL_TO, 'competency:idnumber_value' => 'COM102', ], false], 'Competency time created' => ['competency:timecreated', [ 'competency:timecreated_operator' => date::DATE_RANGE, 'competency:timecreated_from' => 1622502000, ], true], 'Competency time created (no match)' => ['competency:timecreated', [ 'competency:timecreated_operator' => date::DATE_RANGE, 'competency:timecreated_to' => 1622502000, ], false], 'Competency time modified' => ['competency:timemodified', [ 'competency:timemodified_operator' => date::DATE_RANGE, 'competency:timemodified_from' => 1622502000, ], true], 'Competency time modified (no match)' => ['competency:timemodified', [ 'competency:timemodified_operator' => date::DATE_RANGE, 'competency:timemodified_to' => 1622502000, ], false], // User competency. 'User competency status' => ['usercompetency:status', [ 'usercompetency:status_operator' => SELECT::EQUAL_TO, 'usercompetency:status_value' => user_competency::STATUS_IDLE, ], true], 'User competency status (no match)' => ['usercompetency:status', [ 'usercompetency:status_operator' => SELECT::EQUAL_TO, 'usercompetency:status_value' => user_competency::STATUS_WAITING_FOR_REVIEW, ], false], 'User competency proficient' => ['usercompetency:proficient', [ 'usercompetency:proficient_operator' => boolean_select::CHECKED, ], true], 'User competency proficient (no match)' => ['usercompetency:proficient', [ 'usercompetency:proficient_operator' => boolean_select::NOT_CHECKED, ], false], // User. 'User username' => ['user:username', [ 'user:username_operator' => text::IS_EQUAL_TO, 'user:username_value' => 'testuser', ], true], 'User username (no match)' => ['user:username', [ 'user:username_operator' => text::IS_NOT_EQUAL_TO, 'user:username_value' => 'testuser', ], false], // Cohort. 'Cohort name' => ['cohort:name', [ 'cohort:name_operator' => text::IS_EQUAL_TO, 'cohort:name_value' => 'My cohort', ], true], 'Cohort name (no match)' => ['cohort:name', [ 'cohort:name_operator' => text::IS_EQUAL_TO, 'cohort:name_value' => 'Another cohort', ], false], ]; } /** * Test datasource filters * * @param string $filtername * @param array $filtervalues * @param bool $expectmatch * * @dataProvider datasource_filters_provider */ public function test_datasource_filters( string $filtername, array $filtervalues, bool $expectmatch ): void { $this->resetAfterTest(); $user = $this->getDataGenerator()->create_user(['username' => 'testuser']); $cohort = $this->getDataGenerator()->create_cohort(['name' => 'My cohort']); cohort_add_member($cohort->id, $user->id); $scale = $this->getDataGenerator()->create_scale([]); /** @var core_competency_generator $generator */ $generator = $this->getDataGenerator()->get_plugin_generator('core_competency'); $framework = $generator->create_framework([ 'shortname' => 'How much I care', 'description' => 'Sometimes I feel my heart will overflow', 'idnumber' => 'FRM101', 'scaleid' => $scale->id, ]); $competency = $generator->create_competency([ 'competencyframeworkid' => $framework->get('id'), 'shortname' => 'My framework', 'idnumber' => 'COM101', ]); $generator->create_user_competency([ 'competencyid' => $competency->get('id'), 'userid' => $user->id, 'proficiency' => true, 'grade' => 3, ]); /** @var core_reportbuilder_generator $generator */ $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder'); // Create report containing single column, and given filter. $report = $generator->create_report(['name' => 'Competencies', 'source' => competencies::class, 'default' => 0]); $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'framework:idnumber']); // Add filter, set it's values. $generator->create_filter(['reportid' => $report->get('id'), 'uniqueidentifier' => $filtername]); $content = $this->get_custom_report_content( reportid: $report->get('id'), filtervalues: str_replace('', $scale->id, $filtervalues), ); if ($expectmatch) { $this->assertCount(1, $content); $this->assertEquals('FRM101', reset($content[0])); } else { $this->assertEmpty($content); } } /** * Stress test datasource * * In order to execute this test PHPUNIT_LONGTEST should be defined as true in phpunit.xml or directly in config.php */ public function test_stress_datasource(): void { if (!PHPUNIT_LONGTEST) { $this->markTestSkipped('PHPUNIT_LONGTEST is not defined'); } $this->resetAfterTest(); $user = $this->getDataGenerator()->create_user(); /** @var core_competency_generator $generator */ $generator = $this->getDataGenerator()->get_plugin_generator('core_competency'); $framework = $generator->create_framework(); $competency = $generator->create_competency(['competencyframeworkid' => $framework->get('id')]); $generator->create_user_competency(['competencyid' => $competency->get('id'), 'userid' => $user->id]); $this->datasource_stress_test_columns(competencies::class); $this->datasource_stress_test_columns_aggregation(competencies::class); $this->datasource_stress_test_conditions(competencies::class, 'framework:idnumber'); } } tests/generator/lib.php000064400000042503152265256010011163 0ustar00. /** * Competency data generator. * * @package core_competency * @category test * @copyright 2015 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ use core_competency\competency; use core_competency\competency_framework; use core_competency\course_competency; use core_competency\course_module_competency; use core_competency\evidence; use core_competency\external; use core_competency\plan; use core_competency\plan_competency; use core_competency\related_competency; use core_competency\template; use core_competency\template_cohort; use core_competency\template_competency; use core_competency\user_competency; use core_competency\user_competency_course; use core_competency\user_competency_plan; use core_competency\user_evidence; use core_competency\user_evidence_competency; defined('MOODLE_INTERNAL') || die(); global $CFG; require_once($CFG->libdir . '/grade/grade_scale.php'); /** * Competency data generator class. * * @package core_competency * @category test * @copyright 2015 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class core_competency_generator extends component_generator_base { /** @var int Number of created competencies. */ protected $competencycount = 0; /** @var int Number of created frameworks. */ protected $frameworkcount = 0; /** @var int Number of created plans. */ protected $plancount = 0; /** @var int Number of created templates. */ protected $templatecount = 0; /** @var int Number of created user_evidence. */ protected $userevidencecount = 0; /** @var stdClass Scale that we might need. */ protected $scale; /** * Reset process. * * Do not call directly. * * @return void */ public function reset() { $this->competencycount = 0; $this->frameworkcount = 0; $this->scale = null; } /** * Create a new competency. * * @param array|stdClass $record * @return competency */ public function create_competency($record = null) { $this->competencycount++; $i = $this->competencycount; $record = (object) $record; if (!isset($record->competencyframeworkid)) { throw new coding_exception('The competencyframeworkid value is required.'); } if (!isset($record->shortname)) { $record->shortname = "Competency shortname $i"; } if (!isset($record->idnumber)) { $record->idnumber = "cmp{$i}"; } if (!isset($record->description)) { $record->description = "Competency $i description "; } if (!isset($record->descriptionformat)) { $record->descriptionformat = FORMAT_HTML; } if (!isset($record->scaleconfiguration) && isset($record->scaleid)) { $record->scaleconfiguration = json_encode($this->make_default_scale_configuration($record->scaleid)); } if (isset($record->scaleconfiguration) && (is_array($record->scaleconfiguration) || is_object($record->scaleconfiguration))) { // Conveniently encode the config. $record->scaleconfiguration = json_encode($record->scaleconfiguration); } $competency = new competency(0, $record); $competency->create(); return $competency; } /** * Create a new framework. * * @param array|stdClass $record * @return competency_framework */ public function create_framework($record = null) { if (defined('BEHAT_TEST') && BEHAT_TEST) { $generator = behat_util::get_data_generator(); } else { $generator = phpunit_util::get_data_generator(); } $this->frameworkcount++; $i = $this->frameworkcount; $record = (object) $record; if (!isset($record->shortname)) { $record->shortname = "Framework shortname $i"; } if (!isset($record->idnumber)) { $record->idnumber = "frm{$i}"; } if (!isset($record->description)) { $record->description = "Framework $i description "; } if (!isset($record->descriptionformat)) { $record->descriptionformat = FORMAT_HTML; } if (!isset($record->visible)) { $record->visible = 1; } if (!isset($record->scaleid)) { if (isset($record->scaleconfiguration)) { throw new coding_exception('Scale configuration must be provided with a scale.'); } if (!$this->scale) { $this->scale = $generator->create_scale(array('scale' => 'A,B,C,D')); } $record->scaleid = $this->scale->id; } if (!isset($record->scaleconfiguration)) { $record->scaleconfiguration = json_encode($this->make_default_scale_configuration($record->scaleid)); } if (is_array($record->scaleconfiguration) || is_object($record->scaleconfiguration)) { // Conveniently encode the config. $record->scaleconfiguration = json_encode($record->scaleconfiguration); } if (!isset($record->contextid)) { $record->contextid = context_system::instance()->id; } $framework = new competency_framework(0, $record); $framework->create(); return $framework; } /** * Create a related competency. * * @param array|stdClass $record * @return related_competency */ public function create_related_competency($record = null) { $record = (object) $record; if (!isset($record->competencyid)) { throw new coding_exception('Property competencyid is required.'); } if (!isset($record->relatedcompetencyid)) { throw new coding_exception('Property relatedcompetencyid is required.'); } $relation = related_competency::get_relation($record->competencyid, $record->relatedcompetencyid); if ($relation->get('id')) { throw new coding_exception('Relation already exists'); } $relation->create(); return $relation; } /** * Create a template. * * @param array|stdClass $record * @return template */ public function create_template($record = null) { $this->templatecount++; $i = $this->templatecount; $record = (object) $record; if (!isset($record->shortname)) { $record->shortname = "Template shortname $i"; } if (!isset($record->description)) { $record->description = "Template $i description "; } if (!isset($record->contextid)) { $record->contextid = context_system::instance()->id; } $template = new template(0, $record); $template->create(); return $template; } /** * Create a template competency. * * @param array|stdClass $record * @return template_competency */ public function create_template_competency($record = null) { $record = (object) $record; if (!isset($record->competencyid)) { throw new coding_exception('Property competencyid is required.'); } if (!isset($record->templateid)) { throw new coding_exception('Property templateid is required.'); } $relation = new template_competency(0, $record); $relation->create(); return $relation; } /** * Create a new user competency. * * @param array|stdClass $record * @return user_competency */ public function create_user_competency($record = null) { $record = (object) $record; if (!isset($record->userid)) { throw new coding_exception('The userid value is required.'); } if (!isset($record->competencyid)) { throw new coding_exception('The competencyid value is required.'); } $usercompetency = new user_competency(0, $record); $usercompetency->create(); return $usercompetency; } /** * Create a new plan. * * @param array|stdClass $record * @return plan */ public function create_plan($record = null) { $this->plancount++; $i = $this->plancount; $record = (object) $record; if (!isset($record->name)) { $record->name = "Plan shortname $i"; } if (!isset($record->description)) { $record->description = "Plan $i description"; } if (!isset($record->descriptionformat)) { $record->descriptionformat = FORMAT_HTML; } if (!isset($record->userid)) { throw new coding_exception('The userid value is required.'); } $plan = new plan(0, $record); $plan->create(); return $plan; } /** * Create a new user competency course. * * @param array|stdClass $record * @return user_competency_course */ public function create_user_competency_course($record = null) { $record = (object) $record; if (!isset($record->userid)) { throw new coding_exception('The userid value is required.'); } if (!isset($record->competencyid)) { throw new coding_exception('The competencyid value is required.'); } if (!isset($record->courseid)) { throw new coding_exception('The courseid value is required.'); } $usercompetencycourse = new user_competency_course(0, $record); $usercompetencycourse->create(); return $usercompetencycourse; } /** * Create a new user competency plan. * * @param array|stdClass $record * @return user_competency_plan */ public function create_user_competency_plan($record = null) { $record = (object) $record; if (!isset($record->userid)) { throw new coding_exception('The userid value is required.'); } if (!isset($record->competencyid)) { throw new coding_exception('The competencyid value is required.'); } if (!isset($record->planid)) { throw new coding_exception('The planid value is required.'); } if (!isset($record->sortorder)) { $record->sortorder = 0; } $usercompetencyplan = new user_competency_plan(0, $record); $usercompetencyplan->create(); return $usercompetencyplan; } /** * Create a new plan competency. * * @param array|stdClass $record * @return plan_competency */ public function create_plan_competency($record = null) { $record = (object) $record; if (!isset($record->planid)) { throw new coding_exception('The planid value is required.'); } if (!isset($record->competencyid)) { throw new coding_exception('The competencyid value is required.'); } $plancompetency = new plan_competency(0, $record); $plancompetency->create(); return $plancompetency; } /** * Create a new template cohort. * * @param array|stdClass $record * @return template_cohort */ public function create_template_cohort($record = null) { $record = (object) $record; if (!isset($record->templateid)) { throw new coding_exception('The templateid value is required.'); } if (!isset($record->cohortid)) { throw new coding_exception('The cohortid value is required.'); } $tplcohort = new template_cohort(0, $record); $tplcohort->create(); return $tplcohort; } /** * Create a new evidence. * * @param array|stdClass $record * @return evidence */ public function create_evidence($record = null) { $record = (object) $record; if (!isset($record->usercompetencyid)) { throw new coding_exception('The usercompetencyid value is required.'); } if (!isset($record->action) && !isset($record->grade)) { $record->action = evidence::ACTION_LOG; } if (!isset($record->action)) { throw new coding_exception('The action value is required with a grade.'); } if (!isset($record->contextid)) { $record->contextid = context_system::instance()->id; } if (!isset($record->descidentifier)) { $record->descidentifier = 'invalidevidencedesc'; } if (!isset($record->desccomponent)) { $record->desccomponent = 'core_competency'; } $evidence = new evidence(0, $record); $evidence->create(); return $evidence; } /** * Create a new course competency. * * @param array|stdClass $record * @return user_competency */ public function create_course_competency($record = null) { $record = (object) $record; if (!isset($record->courseid)) { throw new coding_exception('The courseid value is required.'); } if (!isset($record->competencyid)) { throw new coding_exception('The competencyid value is required.'); } $cc = new course_competency(0, $record); $cc->create(); return $cc; } /** * Create a new course module competency. * * @param array|stdClass $record * @return course_module_competency */ public function create_course_module_competency($record = null) { $record = (object) $record; if (!isset($record->cmid)) { throw new coding_exception('The cmid value is required.'); } if (!isset($record->competencyid)) { throw new coding_exception('The competencyid value is required.'); } $cc = new course_module_competency(0, $record); $cc->create(); return $cc; } /** * Create a new user_evidence. * * @param array|stdClass $record * @return evidence */ public function create_user_evidence($record = null) { $this->userevidencecount++; $i = $this->userevidencecount; $record = (object) $record; if (!isset($record->userid)) { throw new coding_exception('The userid value is required.'); } if (!isset($record->name)) { $record->name = "Evidence $i name"; } if (!isset($record->description)) { $record->description = "Evidence $i description"; } if (!isset($record->descriptionformat)) { $record->descriptionformat = FORMAT_HTML; } $ue = new user_evidence(0, $record); $ue->create(); return $ue; } /** * Create a new user_evidence_comp. * * @param array|stdClass $record * @return evidence */ public function create_user_evidence_competency($record = null) { $record = (object) $record; if (!isset($record->userevidenceid)) { throw new coding_exception('The userevidenceid value is required.'); } if (!isset($record->competencyid)) { throw new coding_exception('The competencyid value is required.'); } $uec = new user_evidence_competency(0, $record); $uec->create(); return $uec; } /** * Make a default scale configuration. * * The last and second-last item will be flagged proficient. The * second-last item will be flagged as default. * * @param int $scaleid The scale ID. * @return array Configuration as array. */ protected function make_default_scale_configuration($scaleid) { $scale = grade_scale::fetch(array('id' => $scaleid)); $values = $scale->load_items(); foreach ($values as $key => $value) { // Add a key (make the first value 1). $values[$key] = array('id' => $key + 1, 'name' => $value); } if (count($values) < 2) { throw new coding_exception('Please provide the scale configuration for one-item scales.'); } $scaleconfig = array(); // Last item is proficient. $item = array_pop($values); array_unshift($scaleconfig, array( 'id' => $item['id'], 'proficient' => 1 )); // Second-last item is default and proficient. $item = array_pop($values); array_unshift($scaleconfig, array( 'id' => $item['id'], 'scaledefault' => 1, 'proficient' => 1 )); // Add the scale ID. array_unshift($scaleconfig, array('scaleid' => $scaleid)); return $scaleconfig; } } tests/generator/behat_core_competency_generator.php000064400000031630152265256010017003 0ustar00. use core_competency\competency; use core_competency\competency_framework; use core_competency\plan; /** * Behat data generator for core_competency. * * @package core_competency * @category test * @copyright 2022 Noel De Martin * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class behat_core_competency_generator extends behat_generator_base { /** * Get a list of the entities that Behat can create using the generator step. * * @return array */ protected function get_creatable_entities(): array { return [ 'competencies' => [ 'singular' => 'competency', 'datagenerator' => 'competency', 'required' => ['shortname', 'competencyframework'], 'switchids' => ['competencyframework' => 'competencyframeworkid'], ], 'course_competencies' => [ 'singular' => 'course_competency', 'datagenerator' => 'course_competency', 'required' => ['course', 'competency'], 'switchids' => ['course' => 'courseid', 'competency' => 'competencyid'], ], 'frameworks' => [ 'singular' => 'framework', 'datagenerator' => 'framework', 'required' => ['shortname'], 'switchids' => ['scale' => 'scaleid'], ], 'plans' => [ 'singular' => 'plan', 'datagenerator' => 'plan', 'required' => ['name'], 'switchids' => ['user' => 'userid', 'status' => 'status'], ], 'plan_competencies' => [ 'singular' => 'plan_competency', 'datagenerator' => 'plan_competency', 'required' => ['plan', 'competency'], 'switchids' => ['competency' => 'competencyid', 'plan' => 'planid'], ], 'related_competencies' => [ 'singular' => 'related_competency', 'datagenerator' => 'related_competency', 'required' => ['competency', 'relatedcompetency'], 'switchids' => ['competency' => 'competencyid', 'relatedcompetency' => 'relatedcompetencyid'], ], 'user_competency' => [ 'singular' => 'user_competency', 'datagenerator' => 'user_competency', 'required' => ['competency', 'user'], 'switchids' => ['competency' => 'competencyid', 'user' => 'userid'], ], 'user_competency_courses' => [ 'singular' => 'user_competency_course', 'datagenerator' => 'user_competency_course', 'required' => ['course', 'competency', 'user'], 'switchids' => ['course' => 'courseid', 'competency' => 'competencyid', 'user' => 'userid'], ], 'user_competency_plans' => [ 'singular' => 'user_competency_plan', 'datagenerator' => 'user_competency_plan', 'required' => ['plan', 'competency', 'user'], 'switchids' => ['plan' => 'planid', 'competency' => 'competencyid', 'user' => 'userid'], ], 'user_evidence' => [ 'singular' => 'user_evidence', 'datagenerator' => 'user_evidence', 'required' => ['user', 'name'], 'switchids' => ['user' => 'userid'], ], 'user_evidence_competency' => [ 'singular' => 'user_evidence_competency', 'datagenerator' => 'user_evidence_competency', 'required' => ['userevidence', 'competency'], 'switchids' => ['userevidence' => 'userevidenceid', 'competency' => 'competencyid'], ], 'templates' => [ 'singular' => 'template', 'datagenerator' => 'template', 'required' => ['shortname'], 'switchids' => ['context' => 'contextid'], ], 'template_competencies' => [ 'singular' => 'template_competency', 'datagenerator' => 'template_competency', 'required' => ['template', 'competency'], 'switchids' => ['template' => 'templateid', 'competency' => 'competencyid'], ], ]; } /** * Get the competency framework id using an idnumber. * * @param string $idnumber * @return int The competency framework id */ protected function get_competencyframework_id(string $idnumber): int { global $DB; if (!$id = $DB->get_field('competency_framework', 'id', ['idnumber' => $idnumber])) { throw new Exception('The specified competency framework with idnumber "' . $idnumber . '" could not be found.'); } return $id; } /** * Get the competency id using an idnumber. * * @param string $idnumber * @return int The competency id */ protected function get_competency_id(string $idnumber): int { global $DB; if (!$id = $DB->get_field('competency', 'id', ['idnumber' => $idnumber])) { throw new Exception('The specified competency with idnumber "' . $idnumber . '" could not be found.'); } return $id; } /** * Get the learning plan id using a name. * * @param string $name * @return int The learning plan id */ protected function get_plan_id(string $name): int { global $DB; if (!$id = $DB->get_field('competency_plan', 'id', ['name' => $name])) { throw new Exception('The specified learning plan with name "' . $name . '" could not be found.'); } return $id; } /** * Get the related competency id using an idnumber. * * @param string $idnumber * @return int The related competency id */ protected function get_relatedcompetency_id(string $idnumber): int { return $this->get_competency_id($idnumber); } /** * Get the template id by shortname. * * @param string $shortname The template name. * @return int */ protected function get_template_id(string $shortname): int { global $DB; if (!$id = $DB->get_field('competency_template', 'id', ['shortname' => $shortname])) { throw new Exception('The specified template with name "' . $shortname . '" could not be found.'); } return $id; } /** * Add a plan. * * @param array $data Plan data. */ public function process_plan(array $data): void { $generator = $this->get_data_generator(); $competencyids = $data['competencyids'] ?? []; unset($data['competencyids']); $plan = $generator->create_plan($data); foreach ($competencyids as $competencyid) { $generator->create_plan_competency([ 'planid' => $plan->get('id'), 'competencyid' => $competencyid, ]); } } /** * Preprocess user competency data. * * @param array $data Raw data. * @return array Processed data. */ protected function preprocess_user_competency(array $data): array { $this->prepare_grading($data); return $data; } /** * Preprocess user course competency data. * * @param array $data Raw data. * @return array Processed data. */ protected function preprocess_user_competency_course(array $data): array { $this->prepare_grading($data); return $data; } /** * Preprocess user learning plan competency data. * * @param array $data Raw data. * @return array Processed data. */ protected function preprocess_user_competency_plan(array $data): array { $this->prepare_grading($data); return $data; } /** * Preprocess plan data. * * @param array $data Raw data. * @return array Processed data. */ protected function preprocess_plan(array $data): array { global $DB, $USER; if (isset($data['competencies'])) { $competencies = array_map('trim', str_getcsv($data['competencies'], escape: '\\')); $data['competencyids'] = array_map([$this, 'get_competency_id'], $competencies); unset($data['competencies']); } if (isset($data['reviewer'])) { if (is_number($data['reviewer'])) { $data['reviewerid'] = $data['reviewer']; } else { if (!$userid = $DB->get_field('user', 'id', ['username' => $data['reviewer']])) { throw new Exception('The specified user "' . $data['reviewer'] . '" could not be found.'); } $data['reviewerid'] = $userid; } unset($data['reviewer']); } return $data + [ 'userid' => $USER->id, ]; } /** * Prepare grading attributes for record data. * * @param array $data Record data. */ protected function prepare_grading(array &$data): void { if (!isset($data['grade'])) { return; } global $DB; $competency = competency::get_record(['id' => $data['competencyid']]); $competencyframework = competency_framework::get_record(['id' => $competency->get('competencyframeworkid')]); $scale = $DB->get_field('scale', 'scale', ['id' => $competencyframework->get('scaleid')]); $grades = array_map('trim', explode(',', $scale)); $grade = array_search($data['grade'], $grades); if ($grade === false) { throw new Exception('The grade "'.$data['grade'].'" was not found in the "'. $competencyframework->get('shortname').'" competency framework.'); } $data['proficiency'] = true; $data['grade'] = $grade + 1; } /** * Get the module data generator. * * @return core_competency_generator Competency data generator. */ protected function get_data_generator(): core_competency_generator { return $this->componentdatagenerator; } /** * Get the user evidence id using a name. * * @param string $name User evidence name. * @return int The user evidence id */ protected function get_userevidence_id(string $name): int { global $DB; if (!$id = $DB->get_field('competency_userevidence', 'id', ['name' => $name])) { throw new Exception('The specified user evidence with name "' . $name . '" could not be found.'); } return $id; } /** * Get the template competency id using a name. * * @param string $name Template competency name. * @return int The template competency id */ protected function get_templatecompetency_id(string $name): int { global $DB; if (!$id = $DB->get_field('competency_template', 'id', ['name' => $name])) { throw new Exception('The specified template competency with name "' . $name . '" could not be found.'); } return $id; } /** * Get the context id using a contextid. * * @param string $contextid Context id. * @return int The context id */ protected function get_context_id(string $contextid): int { global $DB; if (!$id = $DB->get_field('context', 'id', ['id' => $contextid])) { throw new Exception('The specified context with id "' . $contextid . '" could not be found.'); } return $id; } /** * Get the status id by status name. * * @param string $name Status name. * @return int */ protected function get_status_id(string $name): int { switch ($name) { case 'draft': $status = plan::STATUS_DRAFT; break; case 'in review': $status = plan::STATUS_IN_REVIEW; break; case 'waiting for review': $status = plan::STATUS_WAITING_FOR_REVIEW; break; case 'complete': $status = plan::STATUS_COMPLETE; break; default: $status = plan::STATUS_ACTIVE; break; } return $status; } } tests/external/external_test.php000064400000401703152265256010013133 0ustar00. /** * External tests. * * @package core_competency * @copyright 2016 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_competency\external; use core\invalid_persistent_exception; use core_competency\api; use core_competency\course_competency_settings; use core_competency\external; use core_competency\plan; use core_competency\plan_competency; use core_competency\related_competency; use core_competency\template; use core_competency\template_competency; use core_competency\user_competency; use core_competency\user_competency_plan; use core_external\external_api; use externallib_advanced_testcase; defined('MOODLE_INTERNAL') || die(); global $CFG; require_once($CFG->dirroot . '/webservice/tests/helpers.php'); /** * External testcase. * * @package core_competency * @copyright 2016 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class external_test extends externallib_advanced_testcase { /** @var \stdClass $creator User with enough permissions to create insystem context. */ protected $creator = null; /** @var \stdClass $learningplancreator User with enough permissions to create incategory context. */ protected $catcreator = null; /** @var \stdClass $category Category */ protected $category = null; /** @var \stdClass $user User with enough permissions to view insystem context */ protected $user = null; /** @var \stdClass $catuser User with enough permissions to view incategory context */ protected $catuser = null; /** @var int Creator role id */ protected $creatorrole = null; /** @var int User role id */ protected $userrole = null; /** @var \stdClass $scale1 Scale */ protected $scale1 = null; /** @var \stdClass $scale2 Scale */ protected $scale2 = null; /** @var \stdClass $scale3 Scale */ protected $scale3 = null; /** @var \stdClass $scale4 Scale */ protected $scale4 = null; /** @var string scaleconfiguration */ protected $scaleconfiguration1 = null; /** @var string scaleconfiguration */ protected $scaleconfiguration2 = null; /** @var string catscaleconfiguration */ protected $scaleconfiguration3 = null; /** @var string category scale configuration. */ protected $scaleconfiguration4 = null; /** @var \core_course_category course category record. */ protected $othercategory = null; /** * Setup function- we will create a course and add an assign instance to it. */ protected function setUp(): void { global $DB, $CFG; parent::setUp(); $this->resetAfterTest(true); // Create some users. $creator = $this->getDataGenerator()->create_user(); $user = $this->getDataGenerator()->create_user(); $catuser = $this->getDataGenerator()->create_user(); $category = $this->getDataGenerator()->create_category(); $othercategory = $this->getDataGenerator()->create_category(); $catcreator = $this->getDataGenerator()->create_user(); $syscontext = \context_system::instance(); $catcontext = \context_coursecat::instance($category->id); $othercatcontext = \context_coursecat::instance($othercategory->id); // Fetching default authenticated user role. $authrole = $DB->get_record('role', array('id' => $CFG->defaultuserroleid)); // Reset all default authenticated users permissions. unassign_capability('moodle/competency:competencygrade', $authrole->id); unassign_capability('moodle/competency:competencymanage', $authrole->id); unassign_capability('moodle/competency:competencyview', $authrole->id); unassign_capability('moodle/competency:planmanage', $authrole->id); unassign_capability('moodle/competency:planmanagedraft', $authrole->id); unassign_capability('moodle/competency:planmanageown', $authrole->id); unassign_capability('moodle/competency:planview', $authrole->id); unassign_capability('moodle/competency:planviewdraft', $authrole->id); unassign_capability('moodle/competency:planviewown', $authrole->id); unassign_capability('moodle/competency:planviewowndraft', $authrole->id); unassign_capability('moodle/competency:templatemanage', $authrole->id); unassign_capability('moodle/competency:templateview', $authrole->id); unassign_capability('moodle/cohort:manage', $authrole->id); unassign_capability('moodle/competency:coursecompetencyconfigure', $authrole->id); // Creating specific roles. $this->creatorrole = create_role('Creator role', 'creatorrole', 'learning plan creator role description'); $this->userrole = create_role('User role', 'userrole', 'learning plan user role description'); assign_capability('moodle/competency:competencymanage', CAP_ALLOW, $this->creatorrole, $syscontext->id); assign_capability('moodle/competency:coursecompetencyconfigure', CAP_ALLOW, $this->creatorrole, $syscontext->id); assign_capability('moodle/competency:competencyview', CAP_ALLOW, $this->userrole, $syscontext->id); assign_capability('moodle/competency:planmanage', CAP_ALLOW, $this->creatorrole, $syscontext->id); assign_capability('moodle/competency:planmanagedraft', CAP_ALLOW, $this->creatorrole, $syscontext->id); assign_capability('moodle/competency:planmanageown', CAP_ALLOW, $this->creatorrole, $syscontext->id); assign_capability('moodle/competency:planview', CAP_ALLOW, $this->creatorrole, $syscontext->id); assign_capability('moodle/competency:planviewdraft', CAP_ALLOW, $this->creatorrole, $syscontext->id); assign_capability('moodle/competency:templatemanage', CAP_ALLOW, $this->creatorrole, $syscontext->id); assign_capability('moodle/competency:competencygrade', CAP_ALLOW, $this->creatorrole, $syscontext->id); assign_capability('moodle/cohort:manage', CAP_ALLOW, $this->creatorrole, $syscontext->id); assign_capability('moodle/competency:templateview', CAP_ALLOW, $this->userrole, $syscontext->id); assign_capability('moodle/competency:planviewown', CAP_ALLOW, $this->userrole, $syscontext->id); assign_capability('moodle/competency:planviewowndraft', CAP_ALLOW, $this->userrole, $syscontext->id); role_assign($this->creatorrole, $creator->id, $syscontext->id); role_assign($this->creatorrole, $catcreator->id, $catcontext->id); role_assign($this->userrole, $user->id, $syscontext->id); role_assign($this->userrole, $catuser->id, $catcontext->id); $this->creator = $creator; $this->catcreator = $catcreator; $this->user = $user; $this->catuser = $catuser; $this->category = $category; $this->othercategory = $othercategory; $this->scale1 = $this->getDataGenerator()->create_scale(array("scale" => "value1, value2")); $this->scale2 = $this->getDataGenerator()->create_scale(array("scale" => "value3, value4")); $this->scale3 = $this->getDataGenerator()->create_scale(array("scale" => "value5, value6")); $this->scale4 = $this->getDataGenerator()->create_scale(array("scale" => "value7, value8")); $this->scaleconfiguration1 = '[{"scaleid":"'.$this->scale1->id.'"},' . '{"name":"value1","id":1,"scaledefault":1,"proficient":0},' . '{"name":"value2","id":2,"scaledefault":0,"proficient":1}]'; $this->scaleconfiguration2 = '[{"scaleid":"'.$this->scale2->id.'"},' . '{"name":"value3","id":1,"scaledefault":1,"proficient":0},' . '{"name":"value4","id":2,"scaledefault":0,"proficient":1}]'; $this->scaleconfiguration3 = '[{"scaleid":"'.$this->scale3->id.'"},' . '{"name":"value5","id":1,"scaledefault":1,"proficient":0},' . '{"name":"value6","id":2,"scaledefault":0,"proficient":1}]'; $this->scaleconfiguration4 = '[{"scaleid":"'.$this->scale4->id.'"},'. '{"name":"value8","id":1,"scaledefault":1,"proficient":0},' . '{"name":"value8","id":2,"scaledefault":0,"proficient":1}]'; accesslib_clear_all_caches_for_unit_testing(); } protected function create_competency_framework($number = 1, $system = true) { $scalename = 'scale' . $number; $scalepropname = 'scaleconfiguration' . $number; $framework = array( 'shortname' => 'shortname' . $number, 'idnumber' => 'idnumber' . $number, 'description' => 'description' . $number, 'descriptionformat' => FORMAT_HTML, 'scaleid' => $this->$scalename->id, 'scaleconfiguration' => $this->$scalepropname, 'visible' => true, 'contextid' => $system ? \context_system::instance()->id : \context_coursecat::instance($this->category->id)->id ); $result = external::create_competency_framework($framework); return (object) external_api::clean_returnvalue(external::create_competency_framework_returns(), $result); } protected function create_plan($number, $userid, $templateid, $status, $duedate) { $plan = array( 'name' => 'name' . $number, 'description' => 'description' . $number, 'descriptionformat' => FORMAT_HTML, 'userid' => $userid, 'templateid' => empty($templateid) ? null : $templateid, 'status' => $status, 'duedate' => $duedate ); $result = external::create_plan($plan); return (object) external_api::clean_returnvalue(external::create_plan_returns(), $result); } protected function create_template($number, $system) { $template = array( 'shortname' => 'shortname' . $number, 'description' => 'description' . $number, 'descriptionformat' => FORMAT_HTML, 'duedate' => 0, 'visible' => true, 'contextid' => $system ? \context_system::instance()->id : \context_coursecat::instance($this->category->id)->id ); $result = external::create_template($template); return (object) external_api::clean_returnvalue(external::create_template_returns(), $result); } protected function update_template($templateid, $number) { $template = array( 'id' => $templateid, 'shortname' => 'shortname' . $number, 'description' => 'description' . $number, 'descriptionformat' => FORMAT_HTML, 'visible' => true ); $result = external::update_template($template); return external_api::clean_returnvalue(external::update_template_returns(), $result); } protected function update_plan($planid, $number, $userid, $templateid, $status, $duedate) { $plan = array( 'id' => $planid, 'name' => 'name' . $number, 'description' => 'description' . $number, 'descriptionformat' => FORMAT_HTML, 'userid' => $userid, 'templateid' => $templateid, 'status' => $status, 'duedate' => $duedate ); $result = external::update_plan($plan); return external_api::clean_returnvalue(external::update_plan_returns(), $result); } protected function update_competency_framework($id, $number = 1, $system = true) { $scalename = 'scale' . $number; $scalepropname = 'scaleconfiguration' . $number; $framework = array( 'id' => $id, 'shortname' => 'shortname' . $number, 'idnumber' => 'idnumber' . $number, 'description' => 'description' . $number, 'descriptionformat' => FORMAT_HTML, 'scaleid' => $this->$scalename->id, 'scaleconfiguration' => $this->$scalepropname, 'visible' => true, 'contextid' => $system ? \context_system::instance()->id : \context_coursecat::instance($this->category->id)->id ); $result = external::update_competency_framework($framework); return external_api::clean_returnvalue(external::update_competency_framework_returns(), $result); } protected function create_competency($number, $frameworkid) { $competency = array( 'shortname' => 'shortname' . $number, 'idnumber' => 'idnumber' . $number, 'description' => 'description' . $number, 'descriptionformat' => FORMAT_HTML, 'competencyframeworkid' => $frameworkid ); $result = external::create_competency($competency); return (object) external_api::clean_returnvalue(external::create_competency_returns(), $result); } protected function update_competency($id, $number) { $competency = array( 'id' => $id, 'shortname' => 'shortname' . $number, 'idnumber' => 'idnumber' . $number, 'description' => 'description' . $number, 'descriptionformat' => FORMAT_HTML ); $result = external::update_competency($competency); return external_api::clean_returnvalue(external::update_competency_returns(), $result); } /** * Test we can't create a competency framework with only read permissions. */ public function test_create_competency_frameworks_with_read_permissions(): void { $this->setUser($this->user); $this->expectException(\required_capability_exception::class); $result = $this->create_competency_framework(1, true); } /** * Test we can't create a competency framework with only read permissions. */ public function test_create_competency_frameworks_with_read_permissions_in_category(): void { $this->setUser($this->catuser); $this->expectException(\required_capability_exception::class); $result = $this->create_competency_framework(1, false); } /** * Test we can create a competency framework with manage permissions. */ public function test_create_competency_frameworks_with_manage_permissions(): void { $this->setUser($this->creator); $result = $this->create_competency_framework(1, true); $this->assertGreaterThan(0, $result->timecreated); $this->assertGreaterThan(0, $result->timemodified); $this->assertEquals($this->creator->id, $result->usermodified); $this->assertEquals('shortname1', $result->shortname); $this->assertEquals('idnumber1', $result->idnumber); $this->assertEquals('description1', $result->description); $this->assertEquals(FORMAT_HTML, $result->descriptionformat); $this->assertEquals($this->scale1->id, $result->scaleid); $this->assertEquals($this->scaleconfiguration1, $result->scaleconfiguration); $this->assertEquals(true, $result->visible); } /** * Test we can create a competency framework with manage permissions. */ public function test_create_competency_frameworks_with_manage_permissions_in_category(): void { $this->setUser($this->catcreator); $result = $this->create_competency_framework(1, false); $this->assertGreaterThan(0, $result->timecreated); $this->assertGreaterThan(0, $result->timemodified); $this->assertEquals($this->catcreator->id, $result->usermodified); $this->assertEquals('shortname1', $result->shortname); $this->assertEquals('idnumber1', $result->idnumber); $this->assertEquals('description1', $result->description); $this->assertEquals(FORMAT_HTML, $result->descriptionformat); $this->assertEquals($this->scale1->id, $result->scaleid); $this->assertEquals($this->scaleconfiguration1, $result->scaleconfiguration); $this->assertEquals(true, $result->visible); try { $result = $this->create_competency_framework(1, true); $this->fail('User cannot create a framework at system level.'); } catch (\required_capability_exception $e) { // All good. } } /** * Test we cannot create a competency framework with nasty data. */ public function test_create_competency_frameworks_with_nasty_data(): void { $this->setUser($this->creator); $framework = array( 'shortname' => 'short', 'idnumber' => 'id;"number', 'description' => 'de<>\\..scription', 'descriptionformat' => FORMAT_HTML, 'scaleid' => $this->scale1->id, 'scaleconfiguration' => $this->scaleconfiguration1, 'visible' => true, 'contextid' => \context_system::instance()->id ); $this->expectException(\invalid_parameter_exception::class); $result = external::create_competency_framework($framework); } /** * Test we can read a competency framework with manage permissions. */ public function test_read_competency_frameworks_with_manage_permissions(): void { $this->setUser($this->creator); $result = $this->create_competency_framework(1, true); $id = $result->id; $result = external::read_competency_framework($id); $result = (object) external_api::clean_returnvalue(external::read_competency_framework_returns(), $result); $this->assertGreaterThan(0, $result->timecreated); $this->assertGreaterThan(0, $result->timemodified); $this->assertEquals($this->creator->id, $result->usermodified); $this->assertEquals('shortname1', $result->shortname); $this->assertEquals('idnumber1', $result->idnumber); $this->assertEquals('description1', $result->description); $this->assertEquals(FORMAT_HTML, $result->descriptionformat); $this->assertEquals($this->scale1->id, $result->scaleid); $this->assertEquals($this->scaleconfiguration1, $result->scaleconfiguration); $this->assertEquals(true, $result->visible); } /** * Test we can read a competency framework with manage permissions. */ public function test_read_competency_frameworks_with_manage_permissions_in_category(): void { $this->setUser($this->creator); $insystem = $this->create_competency_framework(1, true); $incat = $this->create_competency_framework(2, false); $this->setUser($this->catcreator); $id = $incat->id; $result = external::read_competency_framework($id); $result = (object) external_api::clean_returnvalue(external::read_competency_framework_returns(), $result); $this->assertGreaterThan(0, $result->timecreated); $this->assertGreaterThan(0, $result->timemodified); $this->assertEquals($this->creator->id, $result->usermodified); $this->assertEquals('shortname2', $result->shortname); $this->assertEquals('idnumber2', $result->idnumber); $this->assertEquals('description2', $result->description); $this->assertEquals(FORMAT_HTML, $result->descriptionformat); $this->assertEquals($this->scale2->id, $result->scaleid); $this->assertEquals($this->scaleconfiguration2, $result->scaleconfiguration); $this->assertEquals(true, $result->visible); try { $id = $insystem->id; $result = external::read_competency_framework($id); $result = (object) external_api::clean_returnvalue(external::read_competency_framework_returns(), $result); $this->fail('User cannot read a framework at system level.'); } catch (\required_capability_exception $e) { // All good. } } /** * Test we can read a competency framework with read permissions. */ public function test_read_competency_frameworks_with_read_permissions(): void { $this->setUser($this->creator); $result = $this->create_competency_framework(1, true); // Switch users to someone with less permissions. $this->setUser($this->user); $id = $result->id; $result = external::read_competency_framework($id); $result = (object) external_api::clean_returnvalue(external::read_competency_framework_returns(), $result); $this->assertGreaterThan(0, $result->timecreated); $this->assertGreaterThan(0, $result->timemodified); $this->assertEquals($this->creator->id, $result->usermodified); $this->assertEquals('shortname1', $result->shortname); $this->assertEquals('idnumber1', $result->idnumber); $this->assertEquals('description1', $result->description); $this->assertEquals(FORMAT_HTML, $result->descriptionformat); $this->assertEquals($this->scale1->id, $result->scaleid); $this->assertEquals($this->scaleconfiguration1, $result->scaleconfiguration); $this->assertEquals(true, $result->visible); } /** * Test we can read a competency framework with read permissions. */ public function test_read_competency_frameworks_with_read_permissions_in_category(): void { $this->setUser($this->creator); $insystem = $this->create_competency_framework(1, true); $incat = $this->create_competency_framework(2, false); // Switch users to someone with less permissions. $this->setUser($this->catuser); $id = $incat->id; $result = external::read_competency_framework($id); $result = (object) external_api::clean_returnvalue(external::read_competency_framework_returns(), $result); $this->assertGreaterThan(0, $result->timecreated); $this->assertGreaterThan(0, $result->timemodified); $this->assertEquals($this->creator->id, $result->usermodified); $this->assertEquals('shortname2', $result->shortname); $this->assertEquals('idnumber2', $result->idnumber); $this->assertEquals('description2', $result->description); $this->assertEquals(FORMAT_HTML, $result->descriptionformat); $this->assertEquals($this->scale2->id, $result->scaleid); $this->assertEquals($this->scaleconfiguration2, $result->scaleconfiguration); $this->assertEquals(true, $result->visible); // Switching to user with no permissions. try { $result = external::read_competency_framework($insystem->id); $this->fail('Current user cannot should not be able to read the framework.'); } catch (\required_capability_exception $e) { // All good. } } /** * Test we can delete a competency framework with manage permissions. */ public function test_delete_competency_frameworks_with_manage_permissions(): void { $this->setUser($this->creator); $result = $this->create_competency_framework(1, true); $id = $result->id; $result = external::delete_competency_framework($id); $result = external_api::clean_returnvalue(external::delete_competency_framework_returns(), $result); $this->assertTrue($result); } /** * Test we can delete a competency framework with manage permissions. */ public function test_delete_competency_frameworks_with_manage_permissions_in_category(): void { $this->setUser($this->creator); $insystem = $this->create_competency_framework(1, true); $incat = $this->create_competency_framework(2, false); $this->setUser($this->catcreator); $id = $incat->id; $result = external::delete_competency_framework($id); $result = external_api::clean_returnvalue(external::delete_competency_framework_returns(), $result); $this->assertTrue($result); try { $id = $insystem->id; $result = external::delete_competency_framework($id); $result = external_api::clean_returnvalue(external::delete_competency_framework_returns(), $result); $this->fail('Current user cannot should not be able to delete the framework.'); } catch (\required_capability_exception $e) { // All good. } } /** * Test we can delete a competency framework with read permissions. */ public function test_delete_competency_frameworks_with_read_permissions(): void { $this->setUser($this->creator); $result = $this->create_competency_framework(1, true); $id = $result->id; // Switch users to someone with less permissions. $this->setUser($this->user); $this->expectException(\required_capability_exception::class); $result = external::delete_competency_framework($id); } /** * Test we can update a competency framework with manage permissions. */ public function test_update_competency_frameworks_with_manage_permissions(): void { $this->setUser($this->creator); $result = $this->create_competency_framework(1, true); $result = $this->update_competency_framework($result->id, 2, true); $this->assertTrue($result); } /** * Test we can update a competency framework with manage permissions. */ public function test_update_competency_frameworks_with_manage_permissions_in_category(): void { $this->setUser($this->creator); $insystem = $this->create_competency_framework(1, true); $incat = $this->create_competency_framework(2, false); $this->setUser($this->catcreator); $id = $incat->id; $result = $this->update_competency_framework($incat->id, 3, false); $this->assertTrue($result); try { $result = $this->update_competency_framework($insystem->id, 4, true); $this->fail('Current user should not be able to update the framework.'); } catch (\required_capability_exception $e) { // All good. } } public function test_update_framework_scale(): void { $this->setUser($this->creator); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $s1 = $this->getDataGenerator()->create_scale(); $f1 = $lpg->create_framework(array('scaleid' => $s1->id)); $f2 = $lpg->create_framework(array('scaleid' => $s1->id)); $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $f2->get('id'))); $this->assertEquals($s1->id, $f1->get('scaleid')); // Make the scale of f2 being used. $lpg->create_user_competency(array('userid' => $this->user->id, 'competencyid' => $c2->get('id'))); // Changing the framework where the scale is not used. $result = $this->update_competency_framework($f1->get('id'), 3, true); $f1 = new \core_competency\competency_framework($f1->get('id')); $this->assertEquals($this->scale3->id, $f1->get('scaleid')); // Changing the framework where the scale is used. try { $result = $this->update_competency_framework($f2->get('id'), 4, true); $this->fail('The scale cannot be changed once used.'); } catch (invalid_persistent_exception $e) { $this->assertMatchesRegularExpression('/scaleid/', $e->getMessage()); } } /** * Test we can update a competency framework with read permissions. */ public function test_update_competency_frameworks_with_read_permissions(): void { $this->setUser($this->creator); $result = $this->create_competency_framework(1, true); $this->setUser($this->user); $this->expectException(\required_capability_exception::class); $result = $this->update_competency_framework($result->id, 2, true); } /** * Test we can list and count competency frameworks with manage permissions. */ public function test_list_and_count_competency_frameworks_with_manage_permissions(): void { $this->setUser($this->creator); $result = $this->create_competency_framework(1, true); $result = $this->create_competency_framework(2, true); $result = $this->create_competency_framework(3, true); $result = $this->create_competency_framework(4, false); $result = external::count_competency_frameworks(array('contextid' => \context_system::instance()->id), 'self'); $result = external_api::clean_returnvalue(external::count_competency_frameworks_returns(), $result); $this->assertEquals($result, 3); $result = external::list_competency_frameworks('shortname', 'ASC', 0, 10, array('contextid' => \context_system::instance()->id), 'self', false); $result = external_api::clean_returnvalue(external::list_competency_frameworks_returns(), $result); $this->assertEquals(count($result), 3); $result = (object) $result[0]; $this->assertGreaterThan(0, $result->timecreated); $this->assertGreaterThan(0, $result->timemodified); $this->assertEquals($this->creator->id, $result->usermodified); $this->assertEquals('shortname1', $result->shortname); $this->assertEquals('idnumber1', $result->idnumber); $this->assertEquals('description1', $result->description); $this->assertEquals(FORMAT_HTML, $result->descriptionformat); $this->assertEquals($this->scale1->id, $result->scaleid); $this->assertEquals($this->scaleconfiguration1, $result->scaleconfiguration); $this->assertEquals(true, $result->visible); } public function test_list_competency_frameworks_with_query(): void { $this->setUser($this->creator); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $framework1 = $lpg->create_framework(array( 'shortname' => 'shortname_beetroot', 'idnumber' => 'idnumber_cinnamon', 'description' => 'description', 'descriptionformat' => FORMAT_HTML, 'visible' => true, 'contextid' => \context_system::instance()->id )); $framework2 = $lpg->create_framework(array( 'shortname' => 'shortname_citrus', 'idnumber' => 'idnumber_beer', 'description' => 'description', 'descriptionformat' => FORMAT_HTML, 'visible' => true, 'contextid' => \context_system::instance()->id )); // Search on both ID number and shortname. $result = external::list_competency_frameworks('shortname', 'ASC', 0, 10, array('contextid' => \context_system::instance()->id), 'self', false, 'bee'); $result = external_api::clean_returnvalue(external::list_competency_frameworks_returns(), $result); $this->assertCount(2, $result); $f = (object) array_shift($result); $this->assertEquals($framework1->get('id'), $f->id); $f = (object) array_shift($result); $this->assertEquals($framework2->get('id'), $f->id); // Search on ID number. $result = external::list_competency_frameworks('shortname', 'ASC', 0, 10, array('contextid' => \context_system::instance()->id), 'self', false, 'beer'); $result = external_api::clean_returnvalue(external::list_competency_frameworks_returns(), $result); $this->assertCount(1, $result); $f = (object) array_shift($result); $this->assertEquals($framework2->get('id'), $f->id); // Search on shortname. $result = external::list_competency_frameworks('shortname', 'ASC', 0, 10, array('contextid' => \context_system::instance()->id), 'self', false, 'cinnamon'); $result = external_api::clean_returnvalue(external::list_competency_frameworks_returns(), $result); $this->assertCount(1, $result); $f = (object) array_shift($result); $this->assertEquals($framework1->get('id'), $f->id); // No match. $result = external::list_competency_frameworks('shortname', 'ASC', 0, 10, array('contextid' => \context_system::instance()->id), 'self', false, 'pwnd!'); $result = external_api::clean_returnvalue(external::list_competency_frameworks_returns(), $result); $this->assertCount(0, $result); } /** * Test we can list and count competency frameworks with read permissions. */ public function test_list_and_count_competency_frameworks_with_read_permissions(): void { $this->setUser($this->creator); $result = $this->create_competency_framework(1, true); $result = $this->create_competency_framework(2, true); $result = $this->create_competency_framework(3, true); $result = $this->create_competency_framework(4, false); $this->setUser($this->user); $result = external::count_competency_frameworks(array('contextid' => \context_system::instance()->id), 'self'); $result = external_api::clean_returnvalue(external::count_competency_frameworks_returns(), $result); $this->assertEquals($result, 3); $result = external::list_competency_frameworks('shortname', 'ASC', 0, 10, array('contextid' => \context_system::instance()->id), 'self', false); $result = external_api::clean_returnvalue(external::list_competency_frameworks_returns(), $result); $this->assertEquals(count($result), 3); $result = (object) $result[0]; $this->assertGreaterThan(0, $result->timecreated); $this->assertGreaterThan(0, $result->timemodified); $this->assertEquals($this->creator->id, $result->usermodified); $this->assertEquals('shortname1', $result->shortname); $this->assertEquals('idnumber1', $result->idnumber); $this->assertEquals('description1', $result->description); $this->assertEquals(FORMAT_HTML, $result->descriptionformat); $this->assertEquals($this->scale1->id, $result->scaleid); $this->assertEquals($this->scaleconfiguration1, $result->scaleconfiguration); $this->assertEquals(true, $result->visible); } /** * Test we can't create a competency with only read permissions. */ public function test_create_competency_with_read_permissions(): void { $framework = $this->getDataGenerator()->get_plugin_generator('core_competency')->create_framework(); $this->setUser($this->user); $this->expectException(\required_capability_exception::class); $competency = $this->create_competency(1, $framework->get('id')); } /** * Test we can create a competency with manage permissions. */ public function test_create_competency_with_manage_permissions(): void { $this->setUser($this->creator); $framework = $this->create_competency_framework(1, true); $competency = $this->create_competency(1, $framework->id); $this->assertGreaterThan(0, $competency->timecreated); $this->assertGreaterThan(0, $competency->timemodified); $this->assertEquals($this->creator->id, $competency->usermodified); $this->assertEquals('shortname1', $competency->shortname); $this->assertEquals('idnumber1', $competency->idnumber); $this->assertEquals('description1', $competency->description); $this->assertEquals(FORMAT_HTML, $competency->descriptionformat); $this->assertEquals(0, $competency->parentid); $this->assertEquals($framework->id, $competency->competencyframeworkid); } /** * Test we can create a competency with manage permissions. */ public function test_create_competency_with_manage_permissions_in_category(): void { $this->setUser($this->creator); $insystem = $this->create_competency_framework(1, true); $incat = $this->create_competency_framework(2, false); $this->setUser($this->catcreator); $competency = $this->create_competency(1, $incat->id); $this->assertGreaterThan(0, $competency->timecreated); $this->assertGreaterThan(0, $competency->timemodified); $this->assertEquals($this->catcreator->id, $competency->usermodified); $this->assertEquals('shortname1', $competency->shortname); $this->assertEquals('idnumber1', $competency->idnumber); $this->assertEquals('description1', $competency->description); $this->assertEquals(FORMAT_HTML, $competency->descriptionformat); $this->assertEquals(0, $competency->parentid); $this->assertEquals($incat->id, $competency->competencyframeworkid); try { $competency = $this->create_competency(2, $insystem->id); $this->fail('User should not be able to create a competency in system context.'); } catch (\required_capability_exception $e) { // All good. } } /** * Test we cannot create a competency with nasty data. */ public function test_create_competency_with_nasty_data(): void { $this->setUser($this->creator); $framework = $this->create_competency_framework(1, true); $competency = array( 'shortname' => 'shortname', 'idnumber' => 'id;"number', 'description' => 'de<>\\..scription', 'descriptionformat' => FORMAT_HTML, 'competencyframeworkid' => $framework->id, 'sortorder' => 0 ); $this->expectException(\invalid_parameter_exception::class); $this->expectExceptionMessage('Invalid external api parameter'); $result = external::create_competency($competency); } /** * Test we can read a competency with manage permissions. */ public function test_read_competencies_with_manage_permissions(): void { $this->setUser($this->creator); $framework = $this->create_competency_framework(1, true); $competency = $this->create_competency(1, $framework->id); $id = $competency->id; $result = external::read_competency($id); $result = (object) external_api::clean_returnvalue(external::read_competency_returns(), $result); $this->assertGreaterThan(0, $result->timecreated); $this->assertGreaterThan(0, $result->timemodified); $this->assertEquals($this->creator->id, $result->usermodified); $this->assertEquals('shortname1', $result->shortname); $this->assertEquals('idnumber1', $result->idnumber); $this->assertEquals('description1', $result->description); $this->assertEquals(FORMAT_HTML, $result->descriptionformat); $this->assertEquals(0, $result->parentid); $this->assertEquals($framework->id, $result->competencyframeworkid); } /** * Test we can read a competency with manage permissions. */ public function test_read_competencies_with_manage_permissions_in_category(): void { $this->setUser($this->creator); $sysframework = $this->create_competency_framework(1, true); $insystem = $this->create_competency(1, $sysframework->id); $catframework = $this->create_competency_framework(2, false); $incat = $this->create_competency(2, $catframework->id); $this->setUser($this->catcreator); $id = $incat->id; $result = external::read_competency($id); $result = (object) external_api::clean_returnvalue(external::read_competency_returns(), $result); $this->assertGreaterThan(0, $result->timecreated); $this->assertGreaterThan(0, $result->timemodified); $this->assertEquals($this->creator->id, $result->usermodified); $this->assertEquals('shortname2', $result->shortname); $this->assertEquals('idnumber2', $result->idnumber); $this->assertEquals('description2', $result->description); $this->assertEquals(FORMAT_HTML, $result->descriptionformat); $this->assertEquals(0, $result->parentid); $this->assertEquals($catframework->id, $result->competencyframeworkid); try { external::read_competency($insystem->id); $this->fail('User should not be able to read a competency in system context.'); } catch (\required_capability_exception $e) { // All good. } } /** * Test we can read a competency with read permissions. */ public function test_read_competencies_with_read_permissions(): void { $this->setUser($this->creator); $framework = $this->create_competency_framework(1, true); $competency = $this->create_competency(1, $framework->id); // Switch users to someone with less permissions. $this->setUser($this->user); $id = $competency->id; $result = external::read_competency($id); $result = (object) external_api::clean_returnvalue(external::read_competency_returns(), $result); $this->assertGreaterThan(0, $result->timecreated); $this->assertGreaterThan(0, $result->timemodified); $this->assertEquals($this->creator->id, $result->usermodified); $this->assertEquals('shortname1', $result->shortname); $this->assertEquals('idnumber1', $result->idnumber); $this->assertEquals('description1', $result->description); $this->assertEquals(FORMAT_HTML, $result->descriptionformat); $this->assertEquals(0, $result->parentid); $this->assertEquals($framework->id, $result->competencyframeworkid); } /** * Test we can read a competency with read permissions. */ public function test_read_competencies_with_read_permissions_in_category(): void { $this->setUser($this->creator); $sysframework = $this->create_competency_framework(1, true); $insystem = $this->create_competency(1, $sysframework->id); $catframework = $this->create_competency_framework(2, false); $incat = $this->create_competency(2, $catframework->id); // Switch users to someone with less permissions. $this->setUser($this->catuser); $id = $incat->id; $result = external::read_competency($id); $result = (object) external_api::clean_returnvalue(external::read_competency_returns(), $result); $this->assertGreaterThan(0, $result->timecreated); $this->assertGreaterThan(0, $result->timemodified); $this->assertEquals($this->creator->id, $result->usermodified); $this->assertEquals('shortname2', $result->shortname); $this->assertEquals('idnumber2', $result->idnumber); $this->assertEquals('description2', $result->description); $this->assertEquals(FORMAT_HTML, $result->descriptionformat); $this->assertEquals(0, $result->parentid); $this->assertEquals($catframework->id, $result->competencyframeworkid); try { external::read_competency($insystem->id); $this->fail('User should not be able to read a competency in system context.'); } catch (\required_capability_exception $e) { // All good. } } /** * Test we can delete a competency with manage permissions. */ public function test_delete_competency_with_manage_permissions(): void { $this->setUser($this->creator); $framework = $this->create_competency_framework(1, true); $result = $this->create_competency(1, $framework->id); $id = $result->id; $result = external::delete_competency($id); $result = external_api::clean_returnvalue(external::delete_competency_returns(), $result); $this->assertTrue($result); } /** * Test we can delete a competency with manage permissions. */ public function test_delete_competency_with_manage_permissions_in_category(): void { $this->setUser($this->creator); $sysframework = $this->create_competency_framework(1, true); $insystem = $this->create_competency(1, $sysframework->id); $catframework = $this->create_competency_framework(2, false); $incat = $this->create_competency(2, $catframework->id); $this->setUser($this->catcreator); $id = $incat->id; $result = external::delete_competency($id); $result = external_api::clean_returnvalue(external::delete_competency_returns(), $result); $this->assertTrue($result); try { $result = external::delete_competency($insystem->id); $this->fail('User should not be able to delete a competency in system context.'); } catch (\required_capability_exception $e) { // All good. } } /** * Test we can delete a competency with read permissions. */ public function test_delete_competency_with_read_permissions(): void { $this->setUser($this->creator); $framework = $this->create_competency_framework(1, true); $result = $this->create_competency(1, $framework->id); $id = $result->id; // Switch users to someone with less permissions. $this->setUser($this->user); $this->expectException(\required_capability_exception::class); $result = external::delete_competency($id); } /** * Test we can update a competency with manage permissions. */ public function test_update_competency_with_manage_permissions(): void { $this->setUser($this->creator); $framework = $this->create_competency_framework(1, true); $result = $this->create_competency(1, $framework->id); $result = $this->update_competency($result->id, 2); $this->assertTrue($result); } /** * Test we can update a competency with manage permissions. */ public function test_update_competency_with_manage_permissions_in_category(): void { $this->setUser($this->creator); $sysframework = $this->create_competency_framework(1, true); $insystem = $this->create_competency(1, $sysframework->id); $catframework = $this->create_competency_framework(2, false); $incat = $this->create_competency(2, $catframework->id); $this->setUser($this->catcreator); $result = $this->update_competency($incat->id, 2); $this->assertTrue($result); try { $result = $this->update_competency($insystem->id, 3); $this->fail('User should not be able to update a competency in system context.'); } catch (\required_capability_exception $e) { // All good. } } /** * Test we can update a competency with read permissions. */ public function test_update_competency_with_read_permissions(): void { $this->setUser($this->creator); $framework = $this->create_competency_framework(1, true); $result = $this->create_competency(1, $framework->id); $this->setUser($this->user); $this->expectException(\required_capability_exception::class); $result = $this->update_competency($result->id, 2); } /** * Test count competencies with filters. */ public function test_count_competencies_with_filters(): void { $this->setUser($this->creator); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $f1 = $lpg->create_framework(); $f2 = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'shortname' => 'A')); $c3 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c4 = $lpg->create_competency(array('competencyframeworkid' => $f2->get('id'))); $c5 = $lpg->create_competency(array('competencyframeworkid' => $f2->get('id'))); $result = external::count_competencies(array(array('column' => 'competencyframeworkid', 'value' => $f2->get('id')))); $result = external_api::clean_returnvalue(external::count_competencies_returns(), $result); $this->assertEquals(2, $result); $result = external::count_competencies(array(array('column' => 'competencyframeworkid', 'value' => $f1->get('id')))); $result = external_api::clean_returnvalue(external::count_competencies_returns(), $result); $this->assertEquals(3, $result); $result = external::count_competencies(array(array('column' => 'shortname', 'value' => 'A'))); $result = external_api::clean_returnvalue(external::count_competencies_returns(), $result); $this->assertEquals(1, $result); } /** * Test we can list and count competencies with manage permissions. */ public function test_list_and_count_competencies_with_manage_permissions(): void { $this->setUser($this->creator); $framework = $this->create_competency_framework(1, true); $result = $this->create_competency(1, $framework->id); $result = $this->create_competency(2, $framework->id); $result = $this->create_competency(3, $framework->id); $result = external::count_competencies(array()); $result = external_api::clean_returnvalue(external::count_competencies_returns(), $result); $this->assertEquals($result, 3); array('id' => $result = external::list_competencies(array(), 'shortname', 'ASC', 0, 10, \context_system::instance()->id)); $result = external_api::clean_returnvalue(external::list_competencies_returns(), $result); $this->assertEquals(count($result), 3); $result = (object) $result[0]; $this->assertGreaterThan(0, $result->timecreated); $this->assertGreaterThan(0, $result->timemodified); $this->assertEquals($this->creator->id, $result->usermodified); $this->assertEquals('shortname1', $result->shortname); $this->assertEquals('idnumber1', $result->idnumber); $this->assertEquals('description1', $result->description); } /** * Test we can list and count competencies with read permissions. */ public function test_list_and_count_competencies_with_read_permissions(): void { $this->setUser($this->creator); $framework = $this->create_competency_framework(1, true); $result = $this->create_competency(1, $framework->id); $result = $this->create_competency(2, $framework->id); $result = $this->create_competency(3, $framework->id); $this->setUser($this->user); $result = external::count_competencies(array()); $result = external_api::clean_returnvalue(external::count_competencies_returns(), $result); $this->assertEquals($result, 3); array('id' => $result = external::list_competencies(array(), 'shortname', 'ASC', 0, 10, \context_system::instance()->id)); $result = external_api::clean_returnvalue(external::list_competencies_returns(), $result); $this->assertEquals(count($result), 3); $result = (object) $result[0]; $this->assertGreaterThan(0, $result->timecreated); $this->assertGreaterThan(0, $result->timemodified); $this->assertEquals($this->creator->id, $result->usermodified); $this->assertEquals('shortname1', $result->shortname); $this->assertEquals('idnumber1', $result->idnumber); $this->assertEquals('description1', $result->description); } /** * Test we can search for competencies. */ public function test_search_competencies_with_read_permissions(): void { $this->setUser($this->creator); $framework = $this->create_competency_framework(1, true); $result = $this->create_competency(1, $framework->id); $result = $this->create_competency(2, $framework->id); $result = $this->create_competency(3, $framework->id); $this->setUser($this->user); $result = external::search_competencies('short', $framework->id); $result = external_api::clean_returnvalue(external::search_competencies_returns(), $result); $this->assertEquals(count($result), 3); $result = (object) $result[0]; $this->assertGreaterThan(0, $result->timecreated); $this->assertGreaterThan(0, $result->timemodified); $this->assertEquals($this->creator->id, $result->usermodified); $this->assertEquals('shortname1', $result->shortname); $this->assertEquals('idnumber1', $result->idnumber); $this->assertEquals('description1', $result->description); } /** * Test plans creation and updates. */ public function test_create_and_update_plans(): void { $syscontext = \context_system::instance(); $this->setUser($this->creator); $plan0 = $this->create_plan(1, $this->creator->id, 0, plan::STATUS_ACTIVE, 0); $this->setUser($this->user); try { $plan1 = $this->create_plan(2, $this->user->id, 0, plan::STATUS_DRAFT, 0); $this->fail('Exception expected due to not permissions to create draft plans'); } catch (\moodle_exception $e) { $this->assertEquals('nopermissions', $e->errorcode); } assign_capability('moodle/competency:planmanageowndraft', CAP_ALLOW, $this->userrole, $syscontext->id); accesslib_clear_all_caches_for_unit_testing(); $this->setUser($this->user); $plan2 = $this->create_plan(3, $this->user->id, 0, plan::STATUS_DRAFT, 0); // Basic update on the plan. $this->assertNotEquals('Updated plan 2 name', $plan2->name); $plan2 = external::update_plan(['id' => $plan2->id, 'name' => 'Updated plan 2 name']); $this->assertEquals('Updated plan 2 name', $plan2->name); try { $plan3 = $this->create_plan(4, $this->user->id, 0, plan::STATUS_ACTIVE, 0); $this->fail('Exception expected due to not permissions to create active plans'); } catch (\moodle_exception $e) { $this->assertEquals('nopermissions', $e->errorcode); } try { $plan3 = $this->update_plan($plan2->id, 4, $this->user->id, 0, plan::STATUS_COMPLETE, 0); $this->fail('We cannot complete a plan using api::update_plan().'); } catch (\coding_exception $e) { $this->assertTrue(true); } assign_capability('moodle/competency:planmanageown', CAP_ALLOW, $this->userrole, $syscontext->id); accesslib_clear_all_caches_for_unit_testing(); $plan3 = $this->create_plan(4, $this->user->id, 0, plan::STATUS_ACTIVE, 0); try { $plan4 = $this->create_plan(6, $this->creator->id, 0, plan::STATUS_COMPLETE, 0); $this->fail('Plans cannot be created as complete.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/A plan cannot be created as complete./', $e->getMessage()); } try { $plan0 = $this->update_plan($plan0->id, 1, $this->user->id, 0, plan::STATUS_ACTIVE, 0); } catch (\moodle_exception $e) { $this->assertEquals('nopermissions', $e->errorcode); } unassign_capability('moodle/competency:planmanageown', $this->userrole, $syscontext->id); unassign_capability('moodle/competency:planmanageowndraft', $this->userrole, $syscontext->id); accesslib_clear_all_caches_for_unit_testing(); try { // Cannot be updated even if they created it. $this->update_plan($plan2->id, 1, $this->user->id, 0, plan::STATUS_ACTIVE, 0); $this->fail('The user can not update their own plan without permissions.'); } catch (\required_capability_exception $e) { $this->assertMatchesRegularExpression('/Manage learning plans./', $e->getMessage()); } } /** * Test complete plan. */ public function test_complete_plan(): void { $syscontext = \context_system::instance(); $this->setUser($this->creator); $this->setUser($this->user); assign_capability('moodle/competency:planmanageowndraft', CAP_ALLOW, $this->userrole, $syscontext->id); assign_capability('moodle/competency:planmanageown', CAP_ALLOW, $this->userrole, $syscontext->id); accesslib_clear_all_caches_for_unit_testing(); $this->setUser($this->user); $plan = $this->create_plan(1, $this->user->id, 0, plan::STATUS_ACTIVE, 0); $result = external::complete_plan($plan->id); $this->assertTrue($result); } /** * Test reopen plan. */ public function test_reopen_plan(): void { $syscontext = \context_system::instance(); $this->setUser($this->creator); $this->setUser($this->user); assign_capability('moodle/competency:planmanageowndraft', CAP_ALLOW, $this->userrole, $syscontext->id); assign_capability('moodle/competency:planmanageown', CAP_ALLOW, $this->userrole, $syscontext->id); accesslib_clear_all_caches_for_unit_testing(); $this->setUser($this->user); $plan = $this->create_plan(1, $this->user->id, 0, plan::STATUS_ACTIVE, 0); external::complete_plan($plan->id); $result = external::reopen_plan($plan->id); $this->assertTrue($result); } /** * Test that we can read plans. */ public function test_read_plans(): void { global $OUTPUT; $this->setUser($this->creator); $syscontext = \context_system::instance(); $plan1 = $this->create_plan(1, $this->user->id, 0, plan::STATUS_DRAFT, 0); $plan2 = $this->create_plan(2, $this->user->id, 0, plan::STATUS_ACTIVE, 0); $plan3 = $this->create_plan(3, $this->user->id, 0, plan::STATUS_ACTIVE, 0); external::complete_plan($plan3->id); $plan3 = (object) external::read_plan($plan3->id); $data = external::read_plan($plan1->id); $this->assertEquals((array)$plan1, external::read_plan($plan1->id)); $data = external::read_plan($plan2->id); $this->assertEquals((array)$plan2, external::read_plan($plan2->id)); $data = external::read_plan($plan3->id); $this->assertEquals((array)$plan3, external::read_plan($plan3->id)); $this->setUser($this->user); // The normal user can not edit these plans. $plan1->canmanage = false; $plan2->canmanage = false; $plan3->canmanage = false; $plan1->canbeedited = false; $plan2->canbeedited = false; $plan3->canbeedited = false; $plan1->canrequestreview = true; $plan2->canrequestreview = true; $plan3->canrequestreview = true; $plan1->canreview = false; $plan2->canreview = false; $plan3->canreview = false; $plan1->iscompleteallowed = false; $plan2->iscompleteallowed = false; $plan3->iscompleteallowed = false; $plan1->isrequestreviewallowed = true; $plan2->isrequestreviewallowed = true; $plan3->isrequestreviewallowed = true; $plan1->isapproveallowed = false; $plan2->isapproveallowed = false; $plan3->isapproveallowed = false; $plan1->isunapproveallowed = false; $plan2->isunapproveallowed = false; $plan3->isunapproveallowed = false; $plan3->isreopenallowed = false; $plan1->commentarea['canpost'] = false; $plan1->commentarea['canview'] = true; // Prevent the user from seeing their own non-draft plans. assign_capability('moodle/competency:plancommentown', CAP_PROHIBIT, $this->userrole, $syscontext->id, true); assign_capability('moodle/competency:planviewown', CAP_PROHIBIT, $this->userrole, $syscontext->id, true); assign_capability('moodle/competency:planviewowndraft', CAP_ALLOW, $this->userrole, $syscontext->id, true); accesslib_clear_all_caches_for_unit_testing(); $this->assertEquals((array)$plan1, external::read_plan($plan1->id)); try { external::read_plan($plan2->id); $this->fail('Exception expected due to not permissions to read plan'); } catch (\moodle_exception $e) { $this->assertEquals('nopermissions', $e->errorcode); } try { external::read_plan($plan3->id); $this->fail('Exception expected due to not permissions to read plan'); } catch (\moodle_exception $e) { $this->assertEquals('nopermissions', $e->errorcode); } // Allow user to see their plan. assign_capability('moodle/competency:plancommentown', CAP_ALLOW, $this->userrole, $syscontext->id, true); assign_capability('moodle/competency:planviewown', CAP_ALLOW, $this->userrole, $syscontext->id, true); assign_capability('moodle/competency:planmanageowndraft', CAP_PROHIBIT, $this->userrole, $syscontext->id, true); accesslib_clear_all_caches_for_unit_testing(); $plan1->commentarea['canpost'] = true; $plan1->commentarea['canview'] = true; $plan2->commentarea['canpost'] = true; $plan2->isrequestreviewallowed = false; $plan3->commentarea['canpost'] = true; $plan3->isrequestreviewallowed = false; $plan1->commentarea['canpostorhascomments'] = true; $plan2->commentarea['canpostorhascomments'] = true; $plan3->commentarea['canpostorhascomments'] = true; $this->assertEquals((array)$plan1, external::read_plan($plan1->id)); $this->assertEquals((array)$plan2, external::read_plan($plan2->id)); $this->assertEquals((array)$plan3, external::read_plan($plan3->id)); // Allow use to manage their own draft plan. assign_capability('moodle/competency:planviewown', CAP_PROHIBIT, $this->userrole, $syscontext->id, true); assign_capability('moodle/competency:planmanageown', CAP_PROHIBIT, $this->userrole, $syscontext->id, true); assign_capability('moodle/competency:planmanageowndraft', CAP_ALLOW, $this->userrole, $syscontext->id, true); accesslib_clear_all_caches_for_unit_testing(); $plan1->canmanage = true; $plan1->canbeedited = true; $plan1->canrequestreview = true; $plan1->isrequestreviewallowed = true; $this->assertEquals((array)$plan1, external::read_plan($plan1->id)); try { external::read_plan($plan2->id); $this->fail('Exception expected due to not permissions to read plan'); } catch (\moodle_exception $e) { $this->assertEquals('nopermissions', $e->errorcode); } try { external::read_plan($plan3->id); $this->fail('Exception expected due to not permissions to read plan'); } catch (\moodle_exception $e) { $this->assertEquals('nopermissions', $e->errorcode); } // Allow use to manage their plan. assign_capability('moodle/competency:planviewown', CAP_PROHIBIT, $this->userrole, $syscontext->id, true); assign_capability('moodle/competency:planmanageowndraft', CAP_PROHIBIT, $this->userrole, $syscontext->id, true); assign_capability('moodle/competency:planmanageown', CAP_ALLOW, $this->userrole, $syscontext->id, true); accesslib_clear_all_caches_for_unit_testing(); $plan1->canmanage = false; $plan1->canbeedited = false; $plan1->canrequestreview = true; $plan1->canreview = true; $plan1->isrequestreviewallowed = true; $plan1->isapproveallowed = true; $plan1->iscompleteallowed = false; $plan2->canmanage = true; $plan2->canbeedited = true; $plan2->canreview = true; $plan2->iscompleteallowed = true; $plan2->isunapproveallowed = true; $plan3->canmanage = true; $plan3->canreview = true; $plan3->isreopenallowed = true; $this->assertEquals((array)$plan1, external::read_plan($plan1->id)); $this->assertEquals((array)$plan2, external::read_plan($plan2->id)); $this->assertEquals((array)$plan3, external::read_plan($plan3->id)); } public function test_delete_plans(): void { $this->setUser($this->creator); $syscontext = \context_system::instance(); $plan1 = $this->create_plan(1, $this->user->id, 0, plan::STATUS_ACTIVE, 0); $plan2 = $this->create_plan(2, $this->user->id, 0, plan::STATUS_ACTIVE, 0); $plan3 = $this->create_plan(3, $this->creator->id, 0, plan::STATUS_ACTIVE, 0); $this->assertTrue(external::delete_plan($plan1->id)); unassign_capability('moodle/competency:planmanage', $this->creatorrole, $syscontext->id); accesslib_clear_all_caches_for_unit_testing(); try { external::delete_plan($plan2->id); $this->fail('Exception expected due to not permissions to manage plans'); } catch (\moodle_exception $e) { $this->assertEquals('nopermissions', $e->errorcode); } $this->setUser($this->user); // Can not delete plans created by other users. try { external::delete_plan($plan2->id); $this->fail('Exception expected due to not permissions to manage plans'); } catch (\moodle_exception $e) { $this->assertEquals('nopermissions', $e->errorcode); } assign_capability('moodle/competency:planmanageown', CAP_ALLOW, $this->userrole, $syscontext->id); accesslib_clear_all_caches_for_unit_testing(); $this->assertTrue(external::delete_plan($plan2->id)); // Can not delete plans created for other users. try { external::delete_plan($plan3->id); $this->fail('Exception expected due to not permissions to manage plans'); } catch (\moodle_exception $e) { $this->assertEquals('nopermissions', $e->errorcode); } $plan4 = $this->create_plan(4, $this->user->id, 0, plan::STATUS_ACTIVE, 0); $this->assertTrue(external::delete_plan($plan4->id)); } public function test_delete_plan_removes_relations(): void { $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $user = $dg->create_user(); $plan = $lpg->create_plan(array('userid' => $user->id)); $framework = $lpg->create_framework(); $comp1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $comp2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $comp3 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $pc1 = $lpg->create_plan_competency(array('planid' => $plan->get('id'), 'competencyid' => $comp1->get('id'))); $pc2 = $lpg->create_plan_competency(array('planid' => $plan->get('id'), 'competencyid' => $comp2->get('id'))); $pc3 = $lpg->create_plan_competency(array('planid' => $plan->get('id'), 'competencyid' => $comp3->get('id'))); // Complete the plan to generate user_competency_plan entries. api::complete_plan($plan); // Confirm the data we have. $this->assertEquals(3, plan_competency::count_records(array('planid' => $plan->get('id')))); $this->assertEquals(3, user_competency_plan::count_records(array('planid' => $plan->get('id'), 'userid' => $user->id))); // Delete the plan now. api::delete_plan($plan->get('id')); $this->assertEquals(0, plan_competency::count_records(array('planid' => $plan->get('id')))); $this->assertEquals(0, user_competency_plan::count_records(array('planid' => $plan->get('id'), 'userid' => $user->id))); } public function test_list_plan_competencies(): void { $this->setUser($this->creator); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $f1 = $lpg->create_framework(); $f2 = $lpg->create_framework(); $c1a = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c1b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c1c = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2a = $lpg->create_competency(array('competencyframeworkid' => $f2->get('id'))); $c2b = $lpg->create_competency(array('competencyframeworkid' => $f2->get('id'))); $tpl = $lpg->create_template(); $lpg->create_template_competency(array('templateid' => $tpl->get('id'), 'competencyid' => $c1a->get('id'))); $lpg->create_template_competency(array('templateid' => $tpl->get('id'), 'competencyid' => $c1c->get('id'))); $lpg->create_template_competency(array('templateid' => $tpl->get('id'), 'competencyid' => $c2b->get('id'))); $plan = $lpg->create_plan(array('userid' => $this->user->id, 'templateid' => $tpl->get('id'))); $uc1a = $lpg->create_user_competency(array('userid' => $this->user->id, 'competencyid' => $c1a->get('id'), 'status' => user_competency::STATUS_IN_REVIEW, 'reviewerid' => $this->creator->id)); $uc1b = $lpg->create_user_competency(array('userid' => $this->user->id, 'competencyid' => $c1b->get('id'))); $uc2b = $lpg->create_user_competency(array('userid' => $this->user->id, 'competencyid' => $c2b->get('id'), 'grade' => 2, 'proficiency' => 1)); $ux1a = $lpg->create_user_competency(array('userid' => $this->creator->id, 'competencyid' => $c1a->get('id'))); $result = external::list_plan_competencies($plan->get('id')); $result = external::clean_returnvalue(external::list_plan_competencies_returns(), $result); $this->assertCount(3, $result); $this->assertEquals($c1a->get('id'), $result[0]['competency']['id']); $this->assertEquals($this->user->id, $result[0]['usercompetency']['userid']); $this->assertArrayNotHasKey('usercompetencyplan', $result[0]); $this->assertEquals($c1c->get('id'), $result[1]['competency']['id']); $this->assertEquals($this->user->id, $result[1]['usercompetency']['userid']); $this->assertArrayNotHasKey('usercompetencyplan', $result[1]); $this->assertEquals($c2b->get('id'), $result[2]['competency']['id']); $this->assertEquals($this->user->id, $result[2]['usercompetency']['userid']); $this->assertArrayNotHasKey('usercompetencyplan', $result[2]); $this->assertEquals(user_competency::STATUS_IN_REVIEW, $result[0]['usercompetency']['status']); $this->assertEquals(null, $result[1]['usercompetency']['grade']); $this->assertEquals(2, $result[2]['usercompetency']['grade']); $this->assertEquals(1, $result[2]['usercompetency']['proficiency']); // Check the return values when the plan status is complete. $completedplan = $lpg->create_plan(array('userid' => $this->user->id, 'templateid' => $tpl->get('id'), 'status' => plan::STATUS_COMPLETE)); $uc1a = $lpg->create_user_competency_plan(array('userid' => $this->user->id, 'competencyid' => $c1a->get('id'), 'planid' => $completedplan->get('id'))); $uc1b = $lpg->create_user_competency_plan(array('userid' => $this->user->id, 'competencyid' => $c1c->get('id'), 'planid' => $completedplan->get('id'))); $uc2b = $lpg->create_user_competency_plan(array('userid' => $this->user->id, 'competencyid' => $c2b->get('id'), 'planid' => $completedplan->get('id'), 'grade' => 2, 'proficiency' => 1)); $ux1a = $lpg->create_user_competency_plan(array('userid' => $this->creator->id, 'competencyid' => $c1a->get('id'), 'planid' => $completedplan->get('id'))); $result = external::list_plan_competencies($completedplan->get('id')); $result = external::clean_returnvalue(external::list_plan_competencies_returns(), $result); $this->assertCount(3, $result); $this->assertEquals($c1a->get('id'), $result[0]['competency']['id']); $this->assertEquals($this->user->id, $result[0]['usercompetencyplan']['userid']); $this->assertArrayNotHasKey('usercompetency', $result[0]); $this->assertEquals($c1c->get('id'), $result[1]['competency']['id']); $this->assertEquals($this->user->id, $result[1]['usercompetencyplan']['userid']); $this->assertArrayNotHasKey('usercompetency', $result[1]); $this->assertEquals($c2b->get('id'), $result[2]['competency']['id']); $this->assertEquals($this->user->id, $result[2]['usercompetencyplan']['userid']); $this->assertArrayNotHasKey('usercompetency', $result[2]); $this->assertEquals(null, $result[1]['usercompetencyplan']['grade']); $this->assertEquals(2, $result[2]['usercompetencyplan']['grade']); $this->assertEquals(1, $result[2]['usercompetencyplan']['proficiency']); } public function test_add_competency_to_template(): void { $this->setUser($this->creator); $syscontext = \context_system::instance(); // Create a template. $template = $this->create_template(1, true); // Create a competency. $framework = $this->create_competency_framework(1, true); $competency = $this->create_competency(1, $framework->id); // Add the competency. external::add_competency_to_template($template->id, $competency->id); // Check that it was added. $this->assertEquals(1, external::count_competencies_in_template($template->id)); // Unassign capability. unassign_capability('moodle/competency:templatemanage', $this->creatorrole, $syscontext->id); accesslib_clear_all_caches_for_unit_testing(); // Check we can not add the competency now. try { external::add_competency_to_template($template->id, $competency->id); $this->fail('Exception expected due to not permissions to manage template competencies'); } catch (\moodle_exception $e) { $this->assertEquals('nopermissions', $e->errorcode); } } public function test_remove_competency_from_template(): void { $syscontext = \context_system::instance(); $this->setUser($this->creator); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); // Create a template. $template = $this->create_template(1, true); // Create a competency. $framework = $lpg->create_framework(); $competency = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); // Add the competency. external::add_competency_to_template($template->id, $competency->get('id')); // Check that it was added. $this->assertEquals(1, external::count_competencies_in_template($template->id)); // Check that we can remove the competency. external::remove_competency_from_template($template->id, $competency->get('id')); // Check that it was removed. $this->assertEquals(0, external::count_competencies_in_template($template->id)); // Unassign capability. unassign_capability('moodle/competency:templatemanage', $this->creatorrole, $syscontext->id); accesslib_clear_all_caches_for_unit_testing(); // Check we can not remove the competency now. try { external::add_competency_to_template($template->id, $competency->get('id')); $this->fail('Exception expected due to not permissions to manage template competencies'); } catch (\moodle_exception $e) { $this->assertEquals('nopermissions', $e->errorcode); } } /** * Test we can re-order competency frameworks. */ public function test_reorder_template_competencies(): void { $this->setUser($this->creator); $syscontext = \context_system::instance(); $onehour = time() + 60 * 60; // Create a template. $template = $this->create_template(1, true); // Create a competency framework. $framework = $this->create_competency_framework(1, true); // Create multiple competencies. $competency1 = $this->create_competency(1, $framework->id); $competency2 = $this->create_competency(2, $framework->id); $competency3 = $this->create_competency(3, $framework->id); $competency4 = $this->create_competency(4, $framework->id); // Add the competencies. external::add_competency_to_template($template->id, $competency1->id); external::add_competency_to_template($template->id, $competency2->id); external::add_competency_to_template($template->id, $competency3->id); external::add_competency_to_template($template->id, $competency4->id); // Test if removing competency from template don't create sortorder holes. external::remove_competency_from_template($template->id, $competency3->id); $templcomp4 = template_competency::get_record(array( 'templateid' => $template->id, 'competencyid' => $competency4->id )); $this->assertEquals(2, $templcomp4->get('sortorder')); // This is a move up. external::reorder_template_competency($template->id, $competency4->id, $competency2->id); $result = external::list_competencies_in_template($template->id); $result = external_api::clean_returnvalue(external::list_competencies_in_template_returns(), $result); $r1 = (object) $result[0]; $r2 = (object) $result[1]; $r3 = (object) $result[2]; $this->assertEquals($competency1->id, $r1->id); $this->assertEquals($competency4->id, $r2->id); $this->assertEquals($competency2->id, $r3->id); // This is a move down. external::reorder_template_competency($template->id, $competency1->id, $competency4->id); $result = external::list_competencies_in_template($template->id); $result = external_api::clean_returnvalue(external::list_competencies_in_template_returns(), $result); $r1 = (object) $result[0]; $r2 = (object) $result[1]; $r3 = (object) $result[2]; $this->assertEquals($competency4->id, $r1->id); $this->assertEquals($competency1->id, $r2->id); $this->assertEquals($competency2->id, $r3->id); $this->expectException('\required_capability_exception'); $this->setUser($this->user); external::reorder_template_competency($template->id, $competency1->id, $competency2->id); } /** * Test we can duplicate learning plan template. */ public function test_duplicate_learning_plan_template(): void { $this->setUser($this->creator); $syscontext = \context_system::instance(); $onehour = time() + 60 * 60; // Create a template. $template = $this->create_template(1, true); // Create a competency framework. $framework = $this->create_competency_framework(1, true); // Create multiple competencies. $competency1 = $this->create_competency(1, $framework->id); $competency2 = $this->create_competency(2, $framework->id); $competency3 = $this->create_competency(3, $framework->id); // Add the competencies. external::add_competency_to_template($template->id, $competency1->id); external::add_competency_to_template($template->id, $competency2->id); external::add_competency_to_template($template->id, $competency3->id); // Duplicate the learning plan template. $duplicatedtemplate = external::duplicate_template($template->id); $result = external::list_competencies_in_template($template->id); $resultduplicated = external::list_competencies_in_template($duplicatedtemplate->id); $this->assertEquals(count($result), count($resultduplicated)); $this->assertStringContainsString($template->shortname, $duplicatedtemplate->shortname); $this->assertEquals($duplicatedtemplate->description, $template->description); $this->assertEquals($duplicatedtemplate->descriptionformat, $template->descriptionformat); $this->assertEquals($duplicatedtemplate->visible, $template->visible); } /** * Test that we can return scale values for a scale with the scale ID. */ public function test_get_scale_values(): void { global $DB; $this->setUser($this->creator); // Create a scale. $record = new \stdClass(); $record->courseid = 0; $record->userid = $this->creator->id; $record->name = 'Test scale'; $record->scale = 'Poor, Not good, Okay, Fine, Excellent'; $record->description = '

Test scale description.

'; $record->descriptionformat = 1; $record->timemodified = time(); $scaleid = $DB->insert_record('scale', $record); // Expected return value. $expected = array(array( 'id' => 1, 'name' => 'Poor' ), array( 'id' => 2, 'name' => 'Not good' ), array( 'id' => 3, 'name' => 'Okay' ), array( 'id' => 4, 'name' => 'Fine' ), array( 'id' => 5, 'name' => 'Excellent' ) ); // Call the webservice. $result = external::get_scale_values($scaleid); $this->assertEquals($expected, $result); } /** * Create a template. */ public function test_create_template(): void { $syscontextid = \context_system::instance()->id; $catcontextid = \context_coursecat::instance($this->category->id)->id; // A user without permission. $this->setUser($this->user); try { $result = $this->create_template(1, true); $this->fail('Invalid permissions'); } catch (\required_capability_exception $e) { // All good. } // A user without permission in a category. $this->setUser($this->catuser); try { $result = $this->create_template(1, false); $this->fail('Invalid permissions'); } catch (\required_capability_exception $e) { // All good. } // A user with permissions in the system. $this->setUser($this->creator); $result = $this->create_template(1, true); $this->assertEquals('shortname1', $result->shortname); $this->assertEquals($syscontextid, $result->contextid); $this->assertNotEmpty($result->id); $result = $this->create_template(2, false); $this->assertEquals('shortname2', $result->shortname); $this->assertEquals($catcontextid, $result->contextid); $this->assertNotEmpty($result->id); // A user with permissions in the category. $this->setUser($this->catcreator); try { $result = $this->create_template(3, true); $this->fail('Invalid permissions'); } catch (\required_capability_exception $e) { // All good. } $result = $this->create_template(3, false); $this->assertEquals('shortname3', $result->shortname); $this->assertEquals($catcontextid, $result->contextid); $this->assertNotEmpty($result->id); } /** * Read a template. */ public function test_read_template(): void { $syscontextid = \context_system::instance()->id; $catcontextid = \context_coursecat::instance($this->category->id)->id; // Set a due date for the next year. $date = new \DateTime('now'); $date->modify('+1 year'); $duedate = $date->getTimestamp(); // Creating two templates. $this->setUser($this->creator); $systemplate = $this->create_template(1, true); $cattemplate = $this->create_template(2, false); // User without permissions to read in system. assign_capability('moodle/competency:templateview', CAP_PROHIBIT, $this->userrole, $syscontextid, true); accesslib_clear_all_caches_for_unit_testing(); $this->setUser($this->user); $this->assertFalse(has_capability('moodle/competency:templateview', \context_system::instance())); try { external::read_template($systemplate->id); $this->fail('Invalid permissions'); } catch (\required_capability_exception $e) { // All good. } try { external::read_template($cattemplate->id); $this->fail('Invalid permissions'); } catch (\required_capability_exception $e) { // All good. } // User with permissions to read in a category. assign_capability('moodle/competency:templateview', CAP_PREVENT, $this->userrole, $syscontextid, true); assign_capability('moodle/competency:templateview', CAP_ALLOW, $this->userrole, $catcontextid, true); accesslib_clear_all_caches_for_unit_testing(); $this->assertFalse(has_capability('moodle/competency:templateview', \context_system::instance())); $this->assertTrue(has_capability('moodle/competency:templateview', \context_coursecat::instance($this->category->id))); try { external::read_template($systemplate->id); $this->fail('Invalid permissions'); } catch (\required_capability_exception $e) { // All good. } $result = external::read_template($cattemplate->id); $result = external_api::clean_returnvalue(external::read_template_returns(), $result); $this->assertEquals($cattemplate->id, $result['id']); $this->assertEquals('shortname2', $result['shortname']); $this->assertEquals('description2', $result['description']); $this->assertEquals(FORMAT_HTML, $result['descriptionformat']); $this->assertEquals(1, $result['visible']); $this->assertEquals(0, $result['duedate']); $this->assertEquals(userdate(0), $result['duedateformatted']); // User with permissions to read in the system. assign_capability('moodle/competency:templateview', CAP_ALLOW, $this->userrole, $syscontextid, true); accesslib_clear_all_caches_for_unit_testing(); $this->assertTrue(has_capability('moodle/competency:templateview', \context_system::instance())); $result = external::read_template($systemplate->id); $result = external_api::clean_returnvalue(external::read_template_returns(), $result); $this->assertEquals($systemplate->id, $result['id']); $this->assertEquals('shortname1', $result['shortname']); $this->assertEquals('description1', $result['description']); $this->assertEquals(FORMAT_HTML, $result['descriptionformat']); $this->assertEquals(true, $result['visible']); $this->assertEquals(0, $result['duedate']); $this->assertEquals(userdate(0), $result['duedateformatted']); $result = external::read_template($cattemplate->id); $result = external_api::clean_returnvalue(external::read_template_returns(), $result); $this->assertEquals($cattemplate->id, $result['id']); $this->assertEquals('shortname2', $result['shortname']); $this->assertEquals('description2', $result['description']); $this->assertEquals(FORMAT_HTML, $result['descriptionformat']); $this->assertEquals(true, $result['visible']); $this->assertEquals(0, $result['duedate']); $this->assertEquals(userdate(0), $result['duedateformatted']); } /** * Update a template. */ public function test_update_template(): void { $syscontextid = \context_system::instance()->id; $catcontextid = \context_coursecat::instance($this->category->id)->id; // Set a due date for the next year. $date = new \DateTime('now'); $date->modify('+1 year'); $duedate = $date->getTimestamp(); // Creating two templates. $this->setUser($this->creator); $systemplate = $this->create_template(1, true); $cattemplate = $this->create_template(2, false); // Trying to update in a without permissions. $this->setUser($this->user); try { $this->update_template($systemplate->id, 3); $this->fail('Invalid permissions'); } catch (\required_capability_exception $e) { // All good. } try { $this->update_template($cattemplate->id, 3); $this->fail('Invalid permissions'); } catch (\required_capability_exception $e) { // All good. } // User with permissions to update in category. $this->setUser($this->catcreator); try { $this->update_template($systemplate->id, 3); $this->fail('Invalid permissions'); } catch (\required_capability_exception $e) { // All good. } $result = $this->update_template($cattemplate->id, 3); $this->assertTrue($result); $result = external::read_template($cattemplate->id); $result = external_api::clean_returnvalue(external::read_template_returns(), $result); $this->assertEquals($cattemplate->id, $result['id']); $this->assertEquals('shortname3', $result['shortname']); $this->assertEquals("description3", $result['description']); $this->assertEquals(FORMAT_HTML, $result['descriptionformat']); $this->assertEquals(true, $result['visible']); $this->assertEquals(0, $result['duedate']); $this->assertEquals(userdate(0), $result['duedateformatted']); // User with permissions to update in the system. $this->setUser($this->creator); $result = $this->update_template($systemplate->id, 4); $this->assertTrue($result); $result = external::read_template($systemplate->id); $result = external_api::clean_returnvalue(external::read_template_returns(), $result); $this->assertEquals($systemplate->id, $result['id']); $this->assertEquals('shortname4', $result['shortname']); $this->assertEquals('description4', $result['description']); $this->assertEquals(FORMAT_HTML, $result['descriptionformat']); $this->assertEquals(true, $result['visible']); $this->assertEquals(0, $result['duedate']); $this->assertEquals(userdate(0), $result['duedateformatted']); $result = $this->update_template($cattemplate->id, 5); $this->assertTrue($result); $result = external::read_template($cattemplate->id); $result = external_api::clean_returnvalue(external::read_template_returns(), $result); $this->assertEquals($cattemplate->id, $result['id']); $this->assertEquals('shortname5', $result['shortname']); $this->assertEquals('description5', $result['description']); $this->assertEquals(FORMAT_HTML, $result['descriptionformat']); $this->assertEquals(1, $result['visible']); $this->assertEquals(0, $result['duedate']); $this->assertEquals(userdate(0), $result['duedateformatted']); } /** * Delete a template. */ public function test_delete_template(): void { global $DB; $syscontextid = \context_system::instance()->id; $catcontextid = \context_coursecat::instance($this->category->id)->id; // Creating a few templates. $this->setUser($this->creator); $sys1 = $this->create_template(1, true); $cat1 = $this->create_template(2, false); $cat2 = $this->create_template(3, false); $this->assertTrue($DB->record_exists(template::TABLE, array('id' => $sys1->id))); $this->assertTrue($DB->record_exists(template::TABLE, array('id' => $cat1->id))); $this->assertTrue($DB->record_exists(template::TABLE, array('id' => $cat2->id))); // User without permissions. $this->setUser($this->user); try { external::delete_template($sys1->id); $this->fail('Invalid permissions'); } catch (\required_capability_exception $e) { // All good. } try { external::delete_template($cat1->id); $this->fail('Invalid permissions'); } catch (\required_capability_exception $e) { // All good. } // User with category permissions. $this->setUser($this->catcreator); try { external::delete_template($sys1->id); $this->fail('Invalid permissions'); } catch (\required_capability_exception $e) { // All good. } $result = external::delete_template($cat1->id); $result = external_api::clean_returnvalue(external::delete_template_returns(), $result); $this->assertTrue($result); $this->assertFalse($DB->record_exists(template::TABLE, array('id' => $cat1->id))); // User with system permissions. $this->setUser($this->creator); $result = external::delete_template($sys1->id); $result = external_api::clean_returnvalue(external::delete_template_returns(), $result); $this->assertTrue($result); $result = external::delete_template($cat2->id); $result = external_api::clean_returnvalue(external::delete_template_returns(), $result); $this->assertTrue($result); $this->assertFalse($DB->record_exists(template::TABLE, array('id' => $sys1->id))); $this->assertFalse($DB->record_exists(template::TABLE, array('id' => $cat2->id))); } /** * List templates. */ public function test_list_templates(): void { $syscontextid = \context_system::instance()->id; $catcontextid = \context_coursecat::instance($this->category->id)->id; // Creating a few templates. $this->setUser($this->creator); $sys1 = $this->create_template(1, true); $sys2 = $this->create_template(2, true); $cat1 = $this->create_template(3, false); $cat2 = $this->create_template(4, false); // User without permission. $this->setUser($this->user); assign_capability('moodle/competency:templateview', CAP_PROHIBIT, $this->userrole, $syscontextid, true); accesslib_clear_all_caches_for_unit_testing(); try { external::list_templates('id', 'ASC', 0, 10, array('contextid' => $syscontextid), 'children', false); $this->fail('Invalid permissions'); } catch (\required_capability_exception $e) { // All good. } // User with category permissions. assign_capability('moodle/competency:templateview', CAP_PREVENT, $this->userrole, $syscontextid, true); assign_capability('moodle/competency:templateview', CAP_ALLOW, $this->userrole, $catcontextid, true); accesslib_clear_all_caches_for_unit_testing(); $result = external::list_templates('id', 'ASC', 0, 10, array('contextid' => $syscontextid), 'children', false); $result = external_api::clean_returnvalue(external::list_templates_returns(), $result); $this->assertCount(2, $result); $this->assertEquals($cat1->id, $result[0]['id']); $this->assertEquals($cat2->id, $result[1]['id']); // User with system permissions. assign_capability('moodle/competency:templateview', CAP_ALLOW, $this->userrole, $syscontextid, true); accesslib_clear_all_caches_for_unit_testing(); $result = external::list_templates('id', 'DESC', 0, 3, array('contextid' => $catcontextid), 'parents', false); $result = external_api::clean_returnvalue(external::list_templates_returns(), $result); $this->assertCount(3, $result); $this->assertEquals($cat2->id, $result[0]['id']); $this->assertEquals($cat1->id, $result[1]['id']); $this->assertEquals($sys2->id, $result[2]['id']); } /** * List templates using competency. */ public function test_list_templates_using_competency(): void { $this->setUser($this->creator); // Create a template. $template1 = $this->create_template(1, true); $template2 = $this->create_template(2, true); $template3 = $this->create_template(3, true); $template4 = $this->create_template(4, true); // Create a competency. $framework = $this->create_competency_framework(1, true); $competency1 = $this->create_competency(1, $framework->id); $competency2 = $this->create_competency(2, $framework->id); // Add the competency. external::add_competency_to_template($template1->id, $competency1->id); external::add_competency_to_template($template2->id, $competency1->id); external::add_competency_to_template($template3->id, $competency1->id); external::add_competency_to_template($template4->id, $competency2->id); $listcomp1 = external::list_templates_using_competency($competency1->id); $listcomp2 = external::list_templates_using_competency($competency2->id); // Test count_templates_using_competency. $counttempcomp1 = external::count_templates_using_competency($competency1->id); $counttempcomp2 = external::count_templates_using_competency($competency2->id); $comptemp1 = $listcomp1[0]; $comptemp2 = $listcomp1[1]; $comptemp3 = $listcomp1[2]; $comptemp4 = $listcomp2[0]; $this->assertCount(3, $listcomp1); $this->assertCount(1, $listcomp2); $this->assertEquals(3, $counttempcomp1); $this->assertEquals(1, $counttempcomp2); $this->assertEquals($template1->id, $comptemp1->id); $this->assertEquals($template2->id, $comptemp2->id); $this->assertEquals($template3->id, $comptemp3->id); $this->assertEquals($template4->id, $comptemp4->id); } public function test_count_templates(): void { $syscontextid = \context_system::instance()->id; $catcontextid = \context_coursecat::instance($this->category->id)->id; // Creating a few templates. $this->setUser($this->creator); $sys1 = $this->create_template(1, true); $sys2 = $this->create_template(2, true); $cat1 = $this->create_template(3, false); $cat2 = $this->create_template(4, false); $cat3 = $this->create_template(5, false); // User without permission. $this->setUser($this->user); assign_capability('moodle/competency:templateview', CAP_PROHIBIT, $this->userrole, $syscontextid, true); accesslib_clear_all_caches_for_unit_testing(); try { external::count_templates(array('contextid' => $syscontextid), 'children'); $this->fail('Invalid permissions'); } catch (\required_capability_exception $e) { // All good. } // User with category permissions. assign_capability('moodle/competency:templateview', CAP_PREVENT, $this->userrole, $syscontextid, true); assign_capability('moodle/competency:templateview', CAP_ALLOW, $this->userrole, $catcontextid, true); accesslib_clear_all_caches_for_unit_testing(); $result = external::count_templates(array('contextid' => $syscontextid), 'children'); $result = external_api::clean_returnvalue(external::count_templates_returns(), $result); $this->assertEquals(3, $result); // User with system permissions. assign_capability('moodle/competency:templateview', CAP_ALLOW, $this->userrole, $syscontextid, true); accesslib_clear_all_caches_for_unit_testing(); $result = external::count_templates(array('contextid' => $catcontextid), 'parents'); $result = external_api::clean_returnvalue(external::count_templates_returns(), $result); $this->assertEquals(5, $result); } /** * Test that we can add related competencies. * * @return void */ public function test_add_related_competency(): void { global $DB; $this->setUser($this->creator); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $framework = $lpg->create_framework(); $framework2 = $lpg->create_framework(); $competency1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $competency2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $competency3 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $competency4 = $lpg->create_competency(array('competencyframeworkid' => $framework2->get('id'))); // The lower one always as competencyid. $result = external::add_related_competency($competency1->get('id'), $competency2->get('id')); $result = external_api::clean_returnvalue(external::add_related_competency_returns(), $result); $this->assertTrue($result); $this->assertTrue($DB->record_exists_select( related_competency::TABLE, 'competencyid = :cid AND relatedcompetencyid = :rid', array( 'cid' => $competency1->get('id'), 'rid' => $competency2->get('id') ) )); $this->assertFalse($DB->record_exists_select( related_competency::TABLE, 'competencyid = :cid AND relatedcompetencyid = :rid', array( 'cid' => $competency2->get('id'), 'rid' => $competency1->get('id') ) )); $result = external::add_related_competency($competency3->get('id'), $competency1->get('id')); $result = external_api::clean_returnvalue(external::add_related_competency_returns(), $result); $this->assertTrue($result); $this->assertTrue($DB->record_exists_select( related_competency::TABLE, 'competencyid = :cid AND relatedcompetencyid = :rid', array( 'cid' => $competency1->get('id'), 'rid' => $competency3->get('id') ) )); $this->assertFalse($DB->record_exists_select( related_competency::TABLE, 'competencyid = :cid AND relatedcompetencyid = :rid', array( 'cid' => $competency3->get('id'), 'rid' => $competency1->get('id') ) )); // We can not allow a duplicate relation, not even in the other direction. $this->assertEquals(1, $DB->count_records_select(related_competency::TABLE, 'competencyid = :cid AND relatedcompetencyid = :rid', array('cid' => $competency1->get('id'), 'rid' => $competency2->get('id')))); $this->assertEquals(0, $DB->count_records_select(related_competency::TABLE, 'competencyid = :cid AND relatedcompetencyid = :rid', array('rid' => $competency1->get('id'), 'cid' => $competency2->get('id')))); $result = external::add_related_competency($competency2->get('id'), $competency1->get('id')); $result = external_api::clean_returnvalue(external::add_related_competency_returns(), $result); $this->assertTrue($result); $this->assertEquals(1, $DB->count_records_select(related_competency::TABLE, 'competencyid = :cid AND relatedcompetencyid = :rid', array('cid' => $competency1->get('id'), 'rid' => $competency2->get('id')))); $this->assertEquals(0, $DB->count_records_select(related_competency::TABLE, 'competencyid = :cid AND relatedcompetencyid = :rid', array('rid' => $competency1->get('id'), 'cid' => $competency2->get('id')))); // Check that we cannot create links across frameworks. try { external::add_related_competency($competency1->get('id'), $competency4->get('id')); $this->fail('Exception expected due mis-use of shared competencies'); } catch (invalid_persistent_exception $e) { // Yay! } // User without permission. $this->setUser($this->user); // Check we can not add the related competency now. try { external::add_related_competency($competency1->get('id'), $competency3->get('id')); $this->fail('Exception expected due to not permissions to manage template competencies'); } catch (\moodle_exception $e) { $this->assertEquals('nopermissions', $e->errorcode); } } /** * Test that we can remove related competencies. * * @return void */ public function test_remove_related_competency(): void { $this->setUser($this->creator); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $framework = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c3 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $rc1 = $lpg->create_related_competency(array('competencyid' => $c1->get('id'), 'relatedcompetencyid' => $c2->get('id'))); $rc2 = $lpg->create_related_competency(array('competencyid' => $c2->get('id'), 'relatedcompetencyid' => $c3->get('id'))); $this->assertEquals(2, related_competency::count_records()); // Returns false when the relation does not exist. $result = external::remove_related_competency($c1->get('id'), $c3->get('id')); $result = external_api::clean_returnvalue(external::remove_related_competency_returns(), $result); $this->assertFalse($result); // Returns true on success. $result = external::remove_related_competency($c2->get('id'), $c3->get('id')); $result = external_api::clean_returnvalue(external::remove_related_competency_returns(), $result); $this->assertTrue($result); $this->assertEquals(1, related_competency::count_records()); // We don't need to specify competencyid and relatedcompetencyid in the right order. $result = external::remove_related_competency($c2->get('id'), $c1->get('id')); $result = external_api::clean_returnvalue(external::remove_related_competency_returns(), $result); $this->assertTrue($result); $this->assertEquals(0, related_competency::count_records()); } /** * Test that we can search and include related competencies. * * @return void */ public function test_search_competencies_including_related(): void { $this->setUser($this->creator); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $framework = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c3 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c4 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c5 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); // We have 1-2, 1-3, 2-4, and no relation between 2-3 nor 1-4 nor 5. $rc12 = $lpg->create_related_competency(array('competencyid' => $c1->get('id'), 'relatedcompetencyid' => $c2->get('id'))); $rc13 = $lpg->create_related_competency(array('competencyid' => $c1->get('id'), 'relatedcompetencyid' => $c3->get('id'))); $rc24 = $lpg->create_related_competency(array('competencyid' => $c2->get('id'), 'relatedcompetencyid' => $c4->get('id'))); $result = external::search_competencies('comp', $framework->get('id'), true); $result = external_api::clean_returnvalue(external::search_competencies_returns(), $result); $this->assertCount(5, $result); } /** * Test that we can add competency to plan if we have the right capability. * * @return void */ public function test_add_competency_to_plan(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $usermanage = $dg->create_user(); $user = $dg->create_user(); $syscontext = \context_system::instance(); // Creating specific roles. $managerole = $dg->create_role(array( 'name' => 'User manage', 'shortname' => 'manage' )); assign_capability('moodle/competency:planmanage', CAP_ALLOW, $managerole, $syscontext->id); assign_capability('moodle/competency:planview', CAP_ALLOW, $managerole, $syscontext->id); $dg->role_assign($managerole, $usermanage->id, $syscontext->id); $this->setUser($usermanage); $plan = array ( 'userid' => $usermanage->id, 'status' => \core_competency\plan::STATUS_ACTIVE ); $pl1 = $lpg->create_plan($plan); $framework = $lpg->create_framework(); $competency = $lpg->create_competency( array('competencyframeworkid' => $framework->get('id')) ); $this->assertTrue(external::add_competency_to_plan($pl1->get('id'), $competency->get('id'))); // A competency cannot be added to plan based on template. $template = $lpg->create_template(); $plan = array ( 'userid' => $usermanage->id, 'status' => \core_competency\plan::STATUS_ACTIVE, 'templateid' => $template->get('id') ); $pl2 = $lpg->create_plan($plan); try { external::add_competency_to_plan($pl2->get('id'), $competency->get('id')); $this->fail('A competency cannot be added to plan based on template'); } catch (\coding_exception $ex) { $this->assertTrue(true); } // User without capability cannot add competency to a plan. $this->setUser($user); try { external::add_competency_to_plan($pl1->get('id'), $competency->get('id')); $this->fail('User without capability cannot add competency to a plan'); } catch (\required_capability_exception $ex) { $this->assertTrue(true); } } /** * Test that we can add competency to plan if we have the right capability. * * @return void */ public function test_remove_competency_from_plan(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $usermanage = $dg->create_user(); $user = $dg->create_user(); $syscontext = \context_system::instance(); // Creating specific roles. $managerole = $dg->create_role(array( 'name' => 'User manage', 'shortname' => 'manage' )); assign_capability('moodle/competency:planmanage', CAP_ALLOW, $managerole, $syscontext->id); assign_capability('moodle/competency:planview', CAP_ALLOW, $managerole, $syscontext->id); $dg->role_assign($managerole, $usermanage->id, $syscontext->id); $this->setUser($usermanage); $plan = array ( 'userid' => $usermanage->id, 'status' => \core_competency\plan::STATUS_ACTIVE ); $pl1 = $lpg->create_plan($plan); $framework = $lpg->create_framework(); $competency = $lpg->create_competency( array('competencyframeworkid' => $framework->get('id')) ); $lpg->create_plan_competency( array( 'planid' => $pl1->get('id'), 'competencyid' => $competency->get('id') ) ); $this->assertTrue(external::remove_competency_from_plan($pl1->get('id'), $competency->get('id'))); $this->assertCount(0, $pl1->get_competencies()); } /** * Test that we can add competency to plan if we have the right capability. * * @return void */ public function test_reorder_plan_competency(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $usermanage = $dg->create_user(); $user = $dg->create_user(); $syscontext = \context_system::instance(); // Creating specific roles. $managerole = $dg->create_role(array( 'name' => 'User manage', 'shortname' => 'manage' )); assign_capability('moodle/competency:planmanage', CAP_ALLOW, $managerole, $syscontext->id); assign_capability('moodle/competency:planview', CAP_ALLOW, $managerole, $syscontext->id); $dg->role_assign($managerole, $usermanage->id, $syscontext->id); $this->setUser($usermanage); $plan = array ( 'userid' => $usermanage->id, 'status' => \core_competency\plan::STATUS_ACTIVE ); $pl1 = $lpg->create_plan($plan); $framework = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c3 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c4 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c5 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $lpg->create_plan_competency(array('planid' => $pl1->get('id'), 'competencyid' => $c1->get('id'), 'sortorder' => 1)); $lpg->create_plan_competency(array('planid' => $pl1->get('id'), 'competencyid' => $c2->get('id'), 'sortorder' => 2)); $lpg->create_plan_competency(array('planid' => $pl1->get('id'), 'competencyid' => $c3->get('id'), 'sortorder' => 3)); $lpg->create_plan_competency(array('planid' => $pl1->get('id'), 'competencyid' => $c4->get('id'), 'sortorder' => 4)); $lpg->create_plan_competency(array('planid' => $pl1->get('id'), 'competencyid' => $c5->get('id'), 'sortorder' => 5)); // Test if removing competency from plan don't create sortorder holes. external::remove_competency_from_plan($pl1->get('id'), $c4->get('id')); $plancomp5 = plan_competency::get_record(array( 'planid' => $pl1->get('id'), 'competencyid' => $c5->get('id') )); $this->assertEquals(3, $plancomp5->get('sortorder')); $this->assertTrue(external::reorder_plan_competency($pl1->get('id'), $c2->get('id'), $c5->get('id'))); $this->assertTrue(external::reorder_plan_competency($pl1->get('id'), $c3->get('id'), $c1->get('id'))); $plancompetencies = plan_competency::get_records(array('planid' => $pl1->get('id')), 'sortorder', 'ASC'); $plcmp1 = $plancompetencies[0]; $plcmp2 = $plancompetencies[1]; $plcmp3 = $plancompetencies[2]; $plcmp4 = $plancompetencies[3]; $this->assertEquals($plcmp1->get('competencyid'), $c3->get('id')); $this->assertEquals($plcmp2->get('competencyid'), $c1->get('id')); $this->assertEquals($plcmp3->get('competencyid'), $c5->get('id')); $this->assertEquals($plcmp4->get('competencyid'), $c2->get('id')); } /** * Test resolving sortorder when we creating competency. */ public function test_fix_sortorder_when_creating_competency(): void { $this->resetAfterTest(true); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $framework = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'), 'sortorder' => 20)); $c3 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'), 'sortorder' => 1)); $this->assertEquals(0, $c1->get('sortorder')); $this->assertEquals(1, $c2->get('sortorder')); $this->assertEquals(2, $c3->get('sortorder')); } /** * Test resolving sortorder when we delete competency. */ public function test_fix_sortorder_when_delete_competency(): void { $this->resetAfterTest(true); $this->setUser($this->creator); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $framework = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c2a = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'), 'parentid' => $c2->get('id'))); $c2b = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'), 'parentid' => $c2->get('id'))); $c2c = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'), 'parentid' => $c2->get('id'))); $c2d = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'), 'parentid' => $c2->get('id'))); $this->assertEquals(0, $c1->get('sortorder')); $this->assertEquals(1, $c2->get('sortorder')); $this->assertEquals(0, $c2a->get('sortorder')); $this->assertEquals(1, $c2b->get('sortorder')); $this->assertEquals(2, $c2c->get('sortorder')); $this->assertEquals(3, $c2d->get('sortorder')); $result = external::delete_competency($c1->get('id')); $result = external_api::clean_returnvalue(external::delete_competency_returns(), $result); $c2->read(); $c2a->read(); $c2b->read(); $c2c->read(); $c2d->read(); $this->assertEquals(0, $c2->get('sortorder')); $this->assertEquals(0, $c2a->get('sortorder')); $this->assertEquals(1, $c2b->get('sortorder')); $this->assertEquals(2, $c2c->get('sortorder')); $this->assertEquals(3, $c2d->get('sortorder')); $result = external::delete_competency($c2b->get('id')); $result = external_api::clean_returnvalue(external::delete_competency_returns(), $result); $c2->read(); $c2a->read(); $c2c->read(); $c2d->read(); $this->assertEquals(0, $c2->get('sortorder')); $this->assertEquals(0, $c2a->get('sortorder')); $this->assertEquals(1, $c2c->get('sortorder')); $this->assertEquals(2, $c2d->get('sortorder')); } /** * Test resolving sortorder when moving a competency. */ public function test_fix_sortorder_when_moving_competency(): void { $this->resetAfterTest(true); $this->setUser($this->creator); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $framework = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c1a = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'), 'parentid' => $c1->get('id'))); $c1b = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'), 'parentid' => $c1->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c2a = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'), 'parentid' => $c2->get('id'))); $c2b = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'), 'parentid' => $c2->get('id'))); $this->assertEquals(0, $c1->get('sortorder')); $this->assertEquals(0, $c1a->get('sortorder')); $this->assertEquals(1, $c1b->get('sortorder')); $this->assertEquals(1, $c2->get('sortorder')); $this->assertEquals(0, $c2a->get('sortorder')); $this->assertEquals(1, $c2b->get('sortorder')); $result = external::set_parent_competency($c2a->get('id'), $c1->get('id')); $result = external_api::clean_returnvalue(external::set_parent_competency_returns(), $result); $c1->read(); $c1a->read(); $c1b->read(); $c2->read(); $c2a->read(); $c2b->read(); $this->assertEquals(0, $c1->get('sortorder')); $this->assertEquals(0, $c1a->get('sortorder')); $this->assertEquals(1, $c1b->get('sortorder')); $this->assertEquals(2, $c2a->get('sortorder')); $this->assertEquals(1, $c2->get('sortorder')); $this->assertEquals(0, $c2b->get('sortorder')); // Move a root node. $result = external::set_parent_competency($c2->get('id'), $c1b->get('id')); $result = external_api::clean_returnvalue(external::set_parent_competency_returns(), $result); $c1->read(); $c1a->read(); $c1b->read(); $c2->read(); $c2a->read(); $c2b->read(); $this->assertEquals(0, $c1->get('sortorder')); $this->assertEquals(0, $c1a->get('sortorder')); $this->assertEquals(1, $c1b->get('sortorder')); $this->assertEquals(0, $c2->get('sortorder')); $this->assertEquals(0, $c2b->get('sortorder')); $this->assertEquals(2, $c2a->get('sortorder')); } public function test_grade_competency(): void { global $CFG; $this->setUser($this->creator); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $f1 = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $evidence = external::grade_competency($this->user->id, $c1->get('id'), 1, 'Evil note'); $this->assertEquals('The competency rating was manually set.', $evidence->description); $this->assertEquals('A', $evidence->gradename); $this->assertEquals('Evil note', $evidence->note); $this->setUser($this->user); $this->expectException('\required_capability_exception'); $evidence = external::grade_competency($this->user->id, $c1->get('id'), 1); } public function test_grade_competency_in_course(): void { global $CFG; $this->setUser($this->creator); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $course = $dg->create_course(['fullname' => 'Evil course']); $dg->enrol_user($this->creator->id, $course->id, 'editingteacher'); $dg->enrol_user($this->user->id, $course->id, 'student'); $f1 = $lpg->create_framework(); $c1 = $lpg->create_competency(['competencyframeworkid' => $f1->get('id')]); $lpg->create_course_competency(['courseid' => $course->id, 'competencyid' => $c1->get('id')]); $evidence = external::grade_competency_in_course($course->id, $this->user->id, $c1->get('id'), 1, 'Evil note'); $this->assertEquals('The competency rating was manually set in the course \'Course: Evil course\'.', $evidence->description); $this->assertEquals('A', $evidence->gradename); $this->assertEquals('Evil note', $evidence->note); $this->setUser($this->user); $this->expectException('\required_capability_exception'); $evidence = external::grade_competency_in_course($course->id, $this->user->id, $c1->get('id'), 1); } public function test_grade_competency_in_plan(): void { global $CFG; $this->setUser($this->creator); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $f1 = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $tpl = $lpg->create_template(); $lpg->create_template_competency(array('templateid' => $tpl->get('id'), 'competencyid' => $c1->get('id'))); $plan = $lpg->create_plan(array('userid' => $this->user->id, 'templateid' => $tpl->get('id'), 'name' => 'Evil')); $uc = $lpg->create_user_competency(array('userid' => $this->user->id, 'competencyid' => $c1->get('id'))); $evidence = external::grade_competency_in_plan($plan->get('id'), $c1->get('id'), 1, 'Evil note'); $this->assertEquals('The competency rating was manually set in the learning plan \'Evil\'.', $evidence->description); $this->assertEquals('A', $evidence->gradename); $this->assertEquals('Evil note', $evidence->note); $this->setUser($this->user); $this->expectException('\required_capability_exception'); $evidence = external::grade_competency_in_plan($plan->get('id'), $c1->get('id'), 1); } /** * Test update course competency settings. */ public function test_update_course_competency_settings(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $course = $dg->create_course(); $roleid = $dg->create_role(); $noobroleid = $dg->create_role(); $context = \context_course::instance($course->id); $compmanager = $this->getDataGenerator()->create_user(); $compnoob = $this->getDataGenerator()->create_user(); assign_capability('moodle/competency:coursecompetencyconfigure', CAP_ALLOW, $roleid, $context->id, true); assign_capability('moodle/competency:coursecompetencyview', CAP_ALLOW, $roleid, $context->id, true); assign_capability('moodle/competency:coursecompetencyview', CAP_ALLOW, $noobroleid, $context->id, true); role_assign($roleid, $compmanager->id, $context->id); role_assign($noobroleid, $compnoob->id, $context->id); $dg->enrol_user($compmanager->id, $course->id, $roleid); $dg->enrol_user($compnoob->id, $course->id, $noobroleid); $this->setUser($compmanager); // Start the test. $result = external::update_course_competency_settings($course->id, array('pushratingstouserplans' => true)); $settings = course_competency_settings::get_by_courseid($course->id); $this->assertTrue((bool)$settings->get('pushratingstouserplans')); $result = external::update_course_competency_settings($course->id, array('pushratingstouserplans' => false)); $settings = course_competency_settings::get_by_courseid($course->id); $this->assertFalse((bool)$settings->get('pushratingstouserplans')); $this->setUser($compnoob); $this->expectException('\required_capability_exception'); $result = external::update_course_competency_settings($course->id, array('pushratingstouserplans' => true)); } /** * Test that we can list competencies with a filter. * * @return void */ public function test_list_competencies_with_filter(): void { $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $framework = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c3 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c4 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c5 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); // Test if removing competency from plan don't create sortorder holes. $filters = []; $sort = 'id'; $order = 'ASC'; $skip = 0; $limit = 0; $result = external::list_competencies($filters, $sort, $order, $skip, $limit); $this->assertCount(5, $result); $result = external::list_competencies($filters, $sort, $order, 2, $limit); $this->assertCount(3, $result); $result = external::list_competencies($filters, $sort, $order, 2, 2); $this->assertCount(2, $result); $filter = $result[0]->shortname; $filters[0] = ['column' => 'shortname', 'value' => $filter]; $result = external::list_competencies($filters, $sort, $order, $skip, $limit); $this->assertCount(1, $result); $this->assertEquals($filter, $result[0]->shortname); } /** * Test that we can list competencies with a course module. * * @return void */ public function test_list_competencies_with_course_module(): void { $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $course = $dg->create_course(); $framework = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c3 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c4 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c5 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $cc1 = api::add_competency_to_course($course->id, $c1->get('id')); $cc2 = api::add_competency_to_course($course->id, $c2->get('id')); $cc3 = api::add_competency_to_course($course->id, $c3->get('id')); $pagegenerator = $this->getDataGenerator()->get_plugin_generator('mod_page'); $page = $pagegenerator->create_instance(array('course' => $course->id)); $cm = get_coursemodule_from_instance('page', $page->id); // Add a link and list again. $ccm1 = api::add_competency_to_course_module($cm, $c1->get('id')); $ccm2 = api::add_competency_to_course_module($cm, $c2->get('id')); // Test list competencies for this course module. $total = external::count_course_module_competencies($cm->id); $result = external::list_course_module_competencies($cm->id); $this->assertCount($total, $result); // Now we should have an array and each element of the array should have a competency and // a coursemodulecompetency. foreach ($result as $instance) { $cmc = $instance['coursemodulecompetency']; $c = $instance['competency']; $this->assertEquals($cmc->competencyid, $c->id); } } } tests/privacy/provider_test.php000064400000460641152265256010013004 0ustar00. /** * Data provider tests. * * @package core_competency * @category test * @copyright 2018 Frédéric Massart * @author Frédéric Massart * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_competency\privacy; defined('MOODLE_INTERNAL') || die(); global $CFG, $DB; use core_privacy\tests\provider_testcase; use core_privacy\local\request\contextlist; use core_privacy\local\request\approved_contextlist; use core_privacy\local\request\approved_userlist; use core_privacy\local\request\transform; use core_privacy\local\request\userlist; use core_privacy\local\request\writer; use core_competency\api; use core_competency\privacy\provider; /** * Data provider testcase class. * * @package core_competency * @category test * @copyright 2018 Frédéric Massart * @author Frédéric Massart * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @covers \core_competency\privacy\provider */ final class provider_test extends provider_testcase { public function setUp(): void { global $PAGE; parent::setUp(); $this->resetAfterTest(); // We need this or exporters (core\external\exporter) do not receive the right renderer. $PAGE->get_renderer('core'); } public function test_get_contexts_for_userid_with_usermodified_for_framework(): void { $dg = $this->getDataGenerator(); $ccg = $dg->get_plugin_generator('core_competency'); $cat1 = $dg->create_category(); $cat2 = $dg->create_category(); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $u3 = $dg->create_user(); $u4 = $dg->create_user(); $sysctx = \context_system::instance(); $cat1ctx = \context_coursecat::instance($cat1->id); $cat2ctx = \context_coursecat::instance($cat2->id); // Test recovery through framework context. $this->setUser($u1); $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), []); $f1 = $ccg->create_framework(); $contextlist = provider::get_contexts_for_userid($u1->id); $this->assert_contextlist($contextlist, [$sysctx]); $f2 = $ccg->create_framework(['contextid' => $cat1ctx->id]); $contextlist = provider::get_contexts_for_userid($u1->id); $this->assert_contextlist($contextlist, [$sysctx, $cat1ctx]); // Test recovery of category context alone. $this->setUser($u2); $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), []); $ccg->create_framework(['contextid' => $cat2ctx->id]); $contextlist = provider::get_contexts_for_userid($u2->id); $this->assert_contextlist($contextlist, [$cat2ctx]); // Test recovery through competency. $this->setUser($u3); $this->assert_contextlist(provider::get_contexts_for_userid($u3->id), []); $c1 = $ccg->create_competency(['competencyframeworkid' => $f1->get('id')]); $c2 = $ccg->create_competency(['competencyframeworkid' => $f1->get('id')]); $c3 = $ccg->create_competency(['competencyframeworkid' => $f1->get('id')]); $contextlist = provider::get_contexts_for_userid($u3->id); $this->assert_contextlist($contextlist, [$sysctx]); $c4 = $ccg->create_competency(['competencyframeworkid' => $f2->get('id')]); $c5 = $ccg->create_competency(['competencyframeworkid' => $f2->get('id')]); $c6 = $ccg->create_competency(['competencyframeworkid' => $f2->get('id')]); $contextlist = provider::get_contexts_for_userid($u3->id); $this->assert_contextlist($contextlist, [$sysctx, $cat1ctx]); // Test recovery through related competency. $this->setUser($u4); $this->assert_contextlist(provider::get_contexts_for_userid($u4->id), []); $cr = $ccg->create_related_competency(['competencyid' => $c1->get('id'), 'relatedcompetencyid' => $c2->get('id')]); $contextlist = provider::get_contexts_for_userid($u4->id); $this->assert_contextlist($contextlist, [$sysctx]); $cr = $ccg->create_related_competency(['competencyid' => $c4->get('id'), 'relatedcompetencyid' => $c5->get('id')]); $contextlist = provider::get_contexts_for_userid($u4->id); $this->assert_contextlist($contextlist, [$sysctx, $cat1ctx]); } public function test_get_users_in_context_with_usermodified_for_framework(): void { $dg = $this->getDataGenerator(); $ccg = $dg->get_plugin_generator('core_competency'); $cat1 = $dg->create_category(); $cat2 = $dg->create_category(); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $u3 = $dg->create_user(); $u4 = $dg->create_user(); $sysctx = \context_system::instance(); $cat1ctx = \context_coursecat::instance($cat1->id); $cat2ctx = \context_coursecat::instance($cat2->id); // Add frameworks. $this->setUser($u1); $f1 = $ccg->create_framework(); $f2 = $ccg->create_framework(['contextid' => $cat1ctx->id]); $this->setUser($u2); $ccg->create_framework(['contextid' => $cat2ctx->id]); // Add competencies. $this->setUser($u3); $c1 = $ccg->create_competency(['competencyframeworkid' => $f1->get('id')]); $c2 = $ccg->create_competency(['competencyframeworkid' => $f1->get('id')]); $c3 = $ccg->create_competency(['competencyframeworkid' => $f1->get('id')]); $c4 = $ccg->create_competency(['competencyframeworkid' => $f2->get('id')]); $c5 = $ccg->create_competency(['competencyframeworkid' => $f2->get('id')]); $c6 = $ccg->create_competency(['competencyframeworkid' => $f2->get('id')]); // Add related competencies. $this->setUser($u4); $cr = $ccg->create_related_competency(['competencyid' => $c1->get('id'), 'relatedcompetencyid' => $c2->get('id')]); $cr = $ccg->create_related_competency(['competencyid' => $c4->get('id'), 'relatedcompetencyid' => $c5->get('id')]); // Test correct users appear in each context. $component = 'core_competency'; $userlist = new userlist($sysctx, $component); provider::get_users_in_context($userlist); $expected = [$u1->id, $u3->id, $u4->id]; $this->assert_array_match($expected, $userlist->get_userids()); $userlist = new userlist($cat1ctx, $component); provider::get_users_in_context($userlist); $expected = [$u1->id, $u3->id, $u4->id]; $this->assert_array_match($expected, $userlist->get_userids()); $userlist = new userlist($cat2ctx, $component); provider::get_users_in_context($userlist); $expected = [$u2->id]; $this->assert_array_match($expected, $userlist->get_userids()); } public function test_get_contexts_for_userid_with_usermodified_for_template(): void { $dg = $this->getDataGenerator(); $ccg = $dg->get_plugin_generator('core_competency'); $cat1 = $dg->create_category(); $cat2 = $dg->create_category(); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $u3 = $dg->create_user(); $u4 = $dg->create_user(); $cohort = $dg->create_cohort(); $sysctx = \context_system::instance(); $cat1ctx = \context_coursecat::instance($cat1->id); $cat2ctx = \context_coursecat::instance($cat2->id); $f1 = $ccg->create_framework(); $f2 = $ccg->create_framework(['contextid' => $cat1ctx->id]); $f3 = $ccg->create_framework(['contextid' => $cat2ctx->id]); $cs = []; foreach ([$f1, $f2, $f3] as $f) { $cs[$f->get('id')] = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); } // Test recovery through template context. $this->setUser($u1); $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), []); $t1 = $ccg->create_template(); $contextlist = provider::get_contexts_for_userid($u1->id); $this->assert_contextlist($contextlist, [$sysctx]); $t2 = $ccg->create_template(['contextid' => $cat1ctx->id]); $contextlist = provider::get_contexts_for_userid($u1->id); $this->assert_contextlist($contextlist, [$sysctx, $cat1ctx]); // Test recovery of category context alone. $this->setUser($u2); $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), []); $ccg->create_template(['contextid' => $cat2ctx->id]); $contextlist = provider::get_contexts_for_userid($u2->id); $this->assert_contextlist($contextlist, [$cat2ctx]); // Test recovery through template competency. $this->setUser($u3); $this->assert_contextlist(provider::get_contexts_for_userid($u3->id), []); $c1 = $ccg->create_template_competency(['competencyid' => $cs[$f1->get('id')]->get('id'), 'templateid' => $t1->get('id')]); $contextlist = provider::get_contexts_for_userid($u3->id); $this->assert_contextlist($contextlist, [$sysctx]); $c4 = $ccg->create_template_competency(['competencyid' => $cs[$f2->get('id')]->get('id'), 'templateid' => $t2->get('id')]); $contextlist = provider::get_contexts_for_userid($u3->id); $this->assert_contextlist($contextlist, [$sysctx, $cat1ctx]); // Test recovery through template cohort. $this->setUser($u4); $this->assert_contextlist(provider::get_contexts_for_userid($u4->id), []); $c1 = $ccg->create_template_cohort(['cohortid' => $cohort->id, 'templateid' => $t1->get('id')]); $contextlist = provider::get_contexts_for_userid($u4->id); $this->assert_contextlist($contextlist, [$sysctx]); $c4 = $ccg->create_template_cohort(['cohortid' => $cohort->id, 'templateid' => $t2->get('id')]); $contextlist = provider::get_contexts_for_userid($u4->id); $this->assert_contextlist($contextlist, [$sysctx, $cat1ctx]); } public function test_get_users_in_context_with_usermodified_for_template(): void { $dg = $this->getDataGenerator(); $ccg = $dg->get_plugin_generator('core_competency'); $cat1 = $dg->create_category(); $cat2 = $dg->create_category(); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $u3 = $dg->create_user(); $u4 = $dg->create_user(); $cohort = $dg->create_cohort(); $sysctx = \context_system::instance(); $cat1ctx = \context_coursecat::instance($cat1->id); $cat2ctx = \context_coursecat::instance($cat2->id); $f1 = $ccg->create_framework(); $f2 = $ccg->create_framework(['contextid' => $cat1ctx->id]); $f3 = $ccg->create_framework(['contextid' => $cat2ctx->id]); $cs = []; foreach ([$f1, $f2, $f3] as $f) { $cs[$f->get('id')] = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); } // Create template context. $this->setUser($u1); $t1 = $ccg->create_template(); $t2 = $ccg->create_template(['contextid' => $cat1ctx->id]); // Add to category context. $this->setUser($u2); $ccg->create_template(['contextid' => $cat2ctx->id]); // Create template competencies. $this->setUser($u3); $c1 = $ccg->create_template_competency(['competencyid' => $cs[$f1->get('id')]->get('id'), 'templateid' => $t1->get('id')]); $c4 = $ccg->create_template_competency(['competencyid' => $cs[$f2->get('id')]->get('id'), 'templateid' => $t2->get('id')]); // Create template cohorts. $this->setUser($u4); $c1 = $ccg->create_template_cohort(['cohortid' => $cohort->id, 'templateid' => $t1->get('id')]); $c4 = $ccg->create_template_cohort(['cohortid' => $cohort->id, 'templateid' => $t2->get('id')]); // Test correct users appear in each context. $component = 'core_competency'; $userlist = new userlist($sysctx, $component); provider::get_users_in_context($userlist); $expected = [$u1->id, $u3->id, $u4->id]; $this->assert_array_match($expected, $userlist->get_userids()); $userlist = new userlist($cat1ctx, $component); provider::get_users_in_context($userlist); $expected = [$u1->id, $u3->id, $u4->id]; $this->assert_array_match($expected, $userlist->get_userids()); $userlist = new userlist($cat2ctx, $component); provider::get_users_in_context($userlist); $expected = [$u2->id]; $this->assert_array_match($expected, $userlist->get_userids()); } public function test_get_contexts_for_userid_with_usermodified_for_course(): void { $dg = $this->getDataGenerator(); $ccg = $dg->get_plugin_generator('core_competency'); $c1 = $dg->create_course(); $c2 = $dg->create_course(); $u0 = $dg->create_user(); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $u3 = $dg->create_user(); $u4 = $dg->create_user(); $c1ctx = \context_course::instance($c1->id); $c2ctx = \context_course::instance($c2->id); $f = $ccg->create_framework(); $comp1 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $comp2 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $this->setUser($u1); $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), []); $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), []); $ccg->create_course_competency(['courseid' => $c1->id, 'competencyid' => $comp1->get('id')]); $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), [$c1ctx]); $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), []); $this->setUser($u2); $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), [$c1ctx]); $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), []); $ccg->create_course_competency(['courseid' => $c2->id, 'competencyid' => $comp2->get('id')]); $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), [$c1ctx]); $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), [$c2ctx]); $ccg->create_course_competency(['courseid' => $c1->id, 'competencyid' => $comp2->get('id')]); $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), [$c1ctx]); $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), [$c1ctx, $c2ctx]); $this->setUser($u3); $this->assert_contextlist(provider::get_contexts_for_userid($u3->id), []); $ccs = new \core_competency\course_competency_settings(null, (object) ['courseid' => $c1->id]); $ccs->create(); $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), [$c1ctx]); $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), [$c1ctx, $c2ctx]); $this->assert_contextlist(provider::get_contexts_for_userid($u3->id), [$c1ctx]); $this->setUser($u4); $this->assert_contextlist(provider::get_contexts_for_userid($u4->id), []); $ccg->create_user_competency_course(['courseid' => $c2->id, 'userid' => $u0->id, 'competencyid' => $comp1->get('id')]); $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), [$c1ctx]); $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), [$c1ctx, $c2ctx]); $this->assert_contextlist(provider::get_contexts_for_userid($u3->id), [$c1ctx]); $this->assert_contextlist(provider::get_contexts_for_userid($u4->id), [$c2ctx]); } public function test_get_users_in_context_with_usermodified_for_course(): void { $dg = $this->getDataGenerator(); $ccg = $dg->get_plugin_generator('core_competency'); $c1 = $dg->create_course(); $c2 = $dg->create_course(); $u0 = $dg->create_user(); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $u3 = $dg->create_user(); $u4 = $dg->create_user(); $c1ctx = \context_course::instance($c1->id); $c2ctx = \context_course::instance($c2->id); $f = $ccg->create_framework(); $comp1 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $comp2 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $this->setUser($u1); $ccg->create_course_competency(['courseid' => $c1->id, 'competencyid' => $comp1->get('id')]); $this->setUser($u2); $ccg->create_course_competency(['courseid' => $c2->id, 'competencyid' => $comp2->get('id')]); $ccg->create_course_competency(['courseid' => $c1->id, 'competencyid' => $comp2->get('id')]); $this->setUser($u3); $ccs = new \core_competency\course_competency_settings(null, (object) ['courseid' => $c1->id]); $ccs->create(); $this->setUser($u4); $ccg->create_user_competency_course(['courseid' => $c2->id, 'userid' => $u0->id, 'competencyid' => $comp1->get('id')]); // Test correct users appear in each context. $component = 'core_competency'; $userlist = new userlist($c1ctx, $component); provider::get_users_in_context($userlist); $expected = [$u1->id, $u2->id, $u3->id]; $this->assert_array_match($expected, $userlist->get_userids()); $userlist = new userlist($c2ctx, $component); provider::get_users_in_context($userlist); $expected = [$u0->id, $u2->id, $u4->id]; $this->assert_array_match($expected, $userlist->get_userids()); } public function test_get_contexts_for_userid_with_usermodified_for_module(): void { $dg = $this->getDataGenerator(); $ccg = $dg->get_plugin_generator('core_competency'); $c1 = $dg->create_course(); $m1 = $dg->create_module('choice', ['course' => $c1]); $m2 = $dg->create_module('choice', ['course' => $c1]); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $m1ctx = \context_module::instance($m1->cmid); $m2ctx = \context_module::instance($m2->cmid); $f = $ccg->create_framework(); $comp1 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $comp2 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $this->setUser($u1); $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), []); $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), []); $ccg->create_course_module_competency(['cmid' => $m1->cmid, 'competencyid' => $comp1->get('id')]); $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), [$m1ctx]); $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), []); $this->setUser($u2); $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), [$m1ctx]); $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), []); $ccg->create_course_module_competency(['cmid' => $m2->cmid, 'competencyid' => $comp2->get('id')]); $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), [$m1ctx]); $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), [$m2ctx]); $ccg->create_course_module_competency(['cmid' => $m1->cmid, 'competencyid' => $comp2->get('id')]); $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), [$m1ctx]); $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), [$m1ctx, $m2ctx]); } public function test_get_users_in_context_with_usermodified_for_module(): void { $dg = $this->getDataGenerator(); $ccg = $dg->get_plugin_generator('core_competency'); $c1 = $dg->create_course(); $m1 = $dg->create_module('choice', ['course' => $c1]); $m2 = $dg->create_module('choice', ['course' => $c1]); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $m1ctx = \context_module::instance($m1->cmid); $m2ctx = \context_module::instance($m2->cmid); $f = $ccg->create_framework(); $comp1 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $comp2 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $this->setUser($u1); $ccg->create_course_module_competency(['cmid' => $m1->cmid, 'competencyid' => $comp1->get('id')]); $this->setUser($u2); $ccg->create_course_module_competency(['cmid' => $m2->cmid, 'competencyid' => $comp2->get('id')]); $ccg->create_course_module_competency(['cmid' => $m1->cmid, 'competencyid' => $comp2->get('id')]); // Test correct users appear in each context. $component = 'core_competency'; $userlist = new userlist($m1ctx, $component); provider::get_users_in_context($userlist); $expected = [$u1->id, $u2->id]; $this->assert_array_match($expected, $userlist->get_userids()); $userlist = new userlist($m2ctx, $component); provider::get_users_in_context($userlist); $expected = [$u2->id]; $this->assert_array_match($expected, $userlist->get_userids()); } public function test_get_contexts_for_userid_with_usermodified_for_plan(): void { $dg = $this->getDataGenerator(); $ccg = $dg->get_plugin_generator('core_competency'); $u0 = $dg->create_user(); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $u3 = $dg->create_user(); $u0ctx = \context_user::instance($u0->id); $f = $ccg->create_framework(); $comp1 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $comp2 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $this->setUser($u1); $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), []); $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), []); $this->assert_contextlist(provider::get_contexts_for_userid($u3->id), []); $plan = $ccg->create_plan(['userid' => $u0->id]); $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), [$u0ctx]); $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), []); $this->assert_contextlist(provider::get_contexts_for_userid($u3->id), []); $this->setUser($u2); $ccg->create_plan_competency(['planid' => $plan->get('id'), 'competencyid' => $comp1->get('id')]); $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), [$u0ctx]); $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), [$u0ctx]); $this->assert_contextlist(provider::get_contexts_for_userid($u3->id), []); $this->setUser($u3); $ccg->create_user_competency_plan(['planid' => $plan->get('id'), 'competencyid' => $comp1->get('id'), 'userid' => $u0->id]); $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), [$u0ctx]); $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), [$u0ctx]); $this->assert_contextlist(provider::get_contexts_for_userid($u3->id), [$u0ctx]); } public function test_get_users_in_context_with_usermodified_for_plan(): void { $dg = $this->getDataGenerator(); $ccg = $dg->get_plugin_generator('core_competency'); $u0 = $dg->create_user(); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $u3 = $dg->create_user(); $u0ctx = \context_user::instance($u0->id); $f = $ccg->create_framework(); $comp1 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $comp2 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $this->setUser($u1); $plan = $ccg->create_plan(['userid' => $u0->id]); $this->setUser($u2); $ccg->create_plan_competency(['planid' => $plan->get('id'), 'competencyid' => $comp1->get('id')]); $this->setUser($u3); $ccg->create_user_competency_plan(['planid' => $plan->get('id'), 'competencyid' => $comp1->get('id'), 'userid' => $u0->id]); // Test correct users appear in the context. $component = 'core_competency'; $userlist = new userlist($u0ctx, $component); provider::get_users_in_context($userlist); $expected = [$u0->id, $u1->id, $u2->id, $u3->id]; $this->assert_array_match($expected, $userlist->get_userids()); } public function test_get_contexts_for_userid_with_usermodified_for_competency_data(): void { $dg = $this->getDataGenerator(); $ccg = $dg->get_plugin_generator('core_competency'); $u0 = $dg->create_user(); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $u3 = $dg->create_user(); $u4 = $dg->create_user(); $u5 = $dg->create_user(); $u6 = $dg->create_user(); $u7 = $dg->create_user(); $u8 = $dg->create_user(); $u0ctx = \context_user::instance($u0->id); $f = $ccg->create_framework(); $comp1 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $comp2 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $this->setUser($u1); $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), []); $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), []); $this->assert_contextlist(provider::get_contexts_for_userid($u3->id), []); $this->assert_contextlist(provider::get_contexts_for_userid($u4->id), []); $this->assert_contextlist(provider::get_contexts_for_userid($u5->id), []); $this->assert_contextlist(provider::get_contexts_for_userid($u6->id), []); $uc = $ccg->create_user_competency(['userid' => $u0->id, 'competencyid' => $comp1->get('id'), 'reviewerid' => $u6->id]); $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), [$u0ctx]); $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), []); $this->assert_contextlist(provider::get_contexts_for_userid($u3->id), []); $this->assert_contextlist(provider::get_contexts_for_userid($u4->id), []); $this->assert_contextlist(provider::get_contexts_for_userid($u5->id), []); $this->assert_contextlist(provider::get_contexts_for_userid($u6->id), [$u0ctx]); $this->setUser($u2); $e = $ccg->create_evidence(['usercompetencyid' => $uc->get('id'), 'actionuserid' => $u5->id]); $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), [$u0ctx]); $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), [$u0ctx]); $this->assert_contextlist(provider::get_contexts_for_userid($u3->id), []); $this->assert_contextlist(provider::get_contexts_for_userid($u4->id), []); $this->assert_contextlist(provider::get_contexts_for_userid($u5->id), [$u0ctx]); $this->assert_contextlist(provider::get_contexts_for_userid($u6->id), [$u0ctx]); $this->setUser($u3); $ccg->create_user_evidence(['userid' => $u0->id]); $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), [$u0ctx]); $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), [$u0ctx]); $this->assert_contextlist(provider::get_contexts_for_userid($u3->id), [$u0ctx]); $this->assert_contextlist(provider::get_contexts_for_userid($u4->id), []); $this->assert_contextlist(provider::get_contexts_for_userid($u5->id), [$u0ctx]); $this->assert_contextlist(provider::get_contexts_for_userid($u6->id), [$u0ctx]); $this->setUser($u4); $ccg->create_user_evidence(['userid' => $u0->id]); $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), [$u0ctx]); $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), [$u0ctx]); $this->assert_contextlist(provider::get_contexts_for_userid($u3->id), [$u0ctx]); $this->assert_contextlist(provider::get_contexts_for_userid($u4->id), [$u0ctx]); $this->assert_contextlist(provider::get_contexts_for_userid($u5->id), [$u0ctx]); $this->assert_contextlist(provider::get_contexts_for_userid($u6->id), [$u0ctx]); // Comment on competency. $this->allow_anyone_to_comment_anywhere(); $this->assert_contextlist(provider::get_contexts_for_userid($u7->id), []); $this->setUser($u7); $comments = $uc->get_comment_object(); $comments->add('Hello there!'); $this->assert_contextlist(provider::get_contexts_for_userid($u7->id), [$u0ctx]); // Comment on plan. $this->assert_contextlist(provider::get_contexts_for_userid($u8->id), []); $this->setUser($u8); $plan = $ccg->create_plan(['userid' => $u0->id]); $comments = $plan->get_comment_object(); $comments->add('Hi, planet!'); $this->assert_contextlist(provider::get_contexts_for_userid($u8->id), [$u0ctx]); } public function test_get_users_in_context_with_usermodified_for_competency_data(): void { $dg = $this->getDataGenerator(); $ccg = $dg->get_plugin_generator('core_competency'); $u0 = $dg->create_user(); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $u3 = $dg->create_user(); $u4 = $dg->create_user(); $u5 = $dg->create_user(); $u6 = $dg->create_user(); $u7 = $dg->create_user(); $u8 = $dg->create_user(); $u0ctx = \context_user::instance($u0->id); $f = $ccg->create_framework(); $comp1 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $this->setUser($u1); $uc = $ccg->create_user_competency(['userid' => $u0->id, 'competencyid' => $comp1->get('id'), 'reviewerid' => $u6->id]); $this->setUser($u2); $e = $ccg->create_evidence(['usercompetencyid' => $uc->get('id'), 'actionuserid' => $u5->id]); $this->setUser($u3); $ccg->create_user_evidence(['userid' => $u0->id]); $this->setUser($u4); $ccg->create_user_evidence(['userid' => $u0->id]); // Comment on competency. $this->allow_anyone_to_comment_anywhere(); $this->setUser($u7); $comments = $uc->get_comment_object(); $comments->add('Hello there!'); // Comment on plan. $this->setUser($u8); $plan = $ccg->create_plan(['userid' => $u0->id]); $comments = $plan->get_comment_object(); $comments->add('Hi, planet!'); // Test correct users appear in the context. $component = 'core_competency'; $userlist = new userlist($u0ctx, $component); provider::get_users_in_context($userlist); $expected = [$u0->id, $u1->id, $u2->id, $u3->id, $u4->id, $u5->id, $u6->id, $u7->id, $u8->id]; $this->assert_array_match($expected, $userlist->get_userids()); } public function test_get_contexts_for_userid_with_actual_data_and_actual_data_is_goooood(): void { $dg = $this->getDataGenerator(); $ccg = $dg->get_plugin_generator('core_competency'); $c1 = $dg->create_course(); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $u3 = $dg->create_user(); $u4 = $dg->create_user(); $u5 = $dg->create_user(); $c1ctx = \context_course::instance($c1->id); $u1ctx = \context_user::instance($u1->id); $u2ctx = \context_user::instance($u2->id); $u3ctx = \context_user::instance($u3->id); $u4ctx = \context_user::instance($u4->id); $f = $ccg->create_framework(); $comp1 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $comp2 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), []); $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), []); $this->assert_contextlist(provider::get_contexts_for_userid($u3->id), []); $this->assert_contextlist(provider::get_contexts_for_userid($u4->id), []); $this->assert_contextlist(provider::get_contexts_for_userid($u5->id), []); $ccg->create_plan(['userid' => $u1->id]); $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), [$u1ctx]); $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), []); $this->assert_contextlist(provider::get_contexts_for_userid($u3->id), []); $this->assert_contextlist(provider::get_contexts_for_userid($u4->id), []); $this->assert_contextlist(provider::get_contexts_for_userid($u5->id), []); $ccg->create_user_competency(['userid' => $u2->id, 'competencyid' => $comp1->get('id')]); $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), [$u1ctx]); $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), [$u2ctx]); $this->assert_contextlist(provider::get_contexts_for_userid($u3->id), []); $this->assert_contextlist(provider::get_contexts_for_userid($u4->id), []); $this->assert_contextlist(provider::get_contexts_for_userid($u5->id), []); $ccg->create_user_competency_course(['userid' => $u3->id, 'competencyid' => $comp1->get('id'), 'courseid' => $c1->id]); $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), [$u1ctx]); $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), [$u2ctx]); $this->assert_contextlist(provider::get_contexts_for_userid($u3->id), [$c1ctx]); $this->assert_contextlist(provider::get_contexts_for_userid($u4->id), []); $this->assert_contextlist(provider::get_contexts_for_userid($u5->id), []); $ue = $ccg->create_user_evidence(['userid' => $u4->id]); $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), [$u1ctx]); $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), [$u2ctx]); $this->assert_contextlist(provider::get_contexts_for_userid($u3->id), [$c1ctx]); $this->assert_contextlist(provider::get_contexts_for_userid($u4->id), [$u4ctx]); $this->assert_contextlist(provider::get_contexts_for_userid($u5->id), []); // A user editing a context relationship. $this->setUser($u5); $ccg->create_user_evidence_competency(['userevidenceid' => $ue->get('id'), 'competencyid' => $comp1->get('id')]); $this->setAdminUser(); $this->assert_contextlist(provider::get_contexts_for_userid($u1->id), [$u1ctx]); $this->assert_contextlist(provider::get_contexts_for_userid($u2->id), [$u2ctx]); $this->assert_contextlist(provider::get_contexts_for_userid($u3->id), [$c1ctx]); $this->assert_contextlist(provider::get_contexts_for_userid($u4->id), [$u4ctx]); $this->assert_contextlist(provider::get_contexts_for_userid($u5->id), [$u4ctx]); } public function test_get_users_in_context_with_actual_data_and_actual_data_is_goooood(): void { $dg = $this->getDataGenerator(); $ccg = $dg->get_plugin_generator('core_competency'); $c1 = $dg->create_course(); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $u3 = $dg->create_user(); $u4 = $dg->create_user(); $c1ctx = \context_course::instance($c1->id); $u1ctx = \context_user::instance($u1->id); $u2ctx = \context_user::instance($u2->id); $u3ctx = \context_user::instance($u3->id); $u4ctx = \context_user::instance($u4->id); $f = $ccg->create_framework(); $comp1 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $comp2 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $ccg->create_plan(['userid' => $u1->id]); $ccg->create_user_competency(['userid' => $u2->id, 'competencyid' => $comp1->get('id')]); $ccg->create_user_competency_course(['userid' => $u3->id, 'competencyid' => $comp1->get('id'), 'courseid' => $c1->id]); $ccg->create_user_evidence(['userid' => $u4->id]); // Test correct users appear in each context. $component = 'core_competency'; $userlist = new userlist($u1ctx, $component); provider::get_users_in_context($userlist); $this->assert_array_match([$u1->id], $userlist->get_userids()); $userlist = new userlist($u2ctx, $component); provider::get_users_in_context($userlist); $this->assert_array_match([$u2->id], $userlist->get_userids()); $userlist = new userlist($c1ctx, $component); provider::get_users_in_context($userlist); $this->assert_array_match([$u3->id], $userlist->get_userids()); $userlist = new userlist($u4ctx, $component); provider::get_users_in_context($userlist); $this->assert_array_match([$u4->id], $userlist->get_userids()); } public function test_delete_data_for_user(): void { $dg = $this->getDataGenerator(); $ccg = $dg->get_plugin_generator('core_competency'); $c1 = $dg->create_course(); $c2 = $dg->create_course(); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $c1ctx = \context_course::instance($c1->id); $u1ctx = \context_user::instance($u1->id); $u2ctx = \context_user::instance($u2->id); $f = $ccg->create_framework(); $comp1 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $comp2 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $ue1a = $ccg->create_user_evidence(['userid' => $u1->id]); $ue1b = $ccg->create_user_evidence(['userid' => $u1->id]); $ue2 = $ccg->create_user_evidence(['userid' => $u2->id]); $uec1a = $ccg->create_user_evidence_competency(['userevidenceid' => $ue1a->get('id'), 'competencyid' => $comp1->get('id')]); $uec1b = $ccg->create_user_evidence_competency(['userevidenceid' => $ue1b->get('id'), 'competencyid' => $comp2->get('id')]); $uec2 = $ccg->create_user_evidence_competency(['userevidenceid' => $ue2->get('id'), 'competencyid' => $comp1->get('id')]); $p1a = $ccg->create_plan(['userid' => $u1->id]); $p1b = $ccg->create_plan(['userid' => $u1->id]); $p2 = $ccg->create_plan(['userid' => $u2->id]); $pc1a = $ccg->create_plan_competency(['planid' => $p1a->get('id'), 'competencyid' => $comp1->get('id')]); $pc1b = $ccg->create_plan_competency(['planid' => $p1b->get('id'), 'competencyid' => $comp2->get('id')]); $pc2 = $ccg->create_plan_competency(['planid' => $p2->get('id'), 'competencyid' => $comp1->get('id')]); $ucp1a = $ccg->create_user_competency_plan(['userid' => $u1->id, 'planid' => $p1a->get('id'), 'competencyid' => $comp1->get('id')]); $ucp1b = $ccg->create_user_competency_plan(['userid' => $u1->id, 'planid' => $p1b->get('id'), 'competencyid' => $comp2->get('id')]); $ucp2 = $ccg->create_user_competency_plan(['userid' => $u2->id, 'planid' => $p2->get('id'), 'competencyid' => $comp1->get('id')]); $uc1a = $ccg->create_user_competency(['userid' => $u1->id, 'competencyid' => $comp1->get('id')]); $uc1b = $ccg->create_user_competency(['userid' => $u1->id, 'competencyid' => $comp2->get('id')]); $uc2 = $ccg->create_user_competency(['userid' => $u2->id, 'competencyid' => $comp2->get('id')]); $e1a = $ccg->create_evidence(['usercompetencyid' => $uc1a->get('id')]); $e1b = $ccg->create_evidence(['usercompetencyid' => $uc1b->get('id')]); $e2 = $ccg->create_evidence(['usercompetencyid' => $uc2->get('id')]); $ucc1a = $ccg->create_user_competency_course(['userid' => $u1->id, 'courseid' => $c1->id, 'competencyid' => $comp1->get('id')]); $ucc1b = $ccg->create_user_competency_course(['userid' => $u1->id, 'courseid' => $c2->id, 'competencyid' => $comp1->get('id')]); $ucc2 = $ccg->create_user_competency_course(['userid' => $u2->id, 'courseid' => $c1->id, 'competencyid' => $comp1->get('id')]); // User 1 comments on both plans. $this->allow_anyone_to_comment_anywhere(); $this->setUser($u1); $p1a->get_comment_object()->add('Hi...'); $p1a->get_comment_object()->add('mister'); $p2->get_comment_object()->add('Ahoy!'); // User 2 comments on both competencies. $this->setUser($u2); $uc1a->get_comment_object()->add('Hi, too!'); $uc1a->get_comment_object()->add('How are you?'); $uc2->get_comment_object()->add('Ahoy, too!'); $p1acommentobj = $p1a->get_comment_object(); $p2commentobj = $p2->get_comment_object(); $uc1acommentobj = $uc1a->get_comment_object(); $uc2commentobj = $uc2->get_comment_object(); $this->setAdminUser(); $this->assertTrue(\core_competency\user_evidence::record_exists($ue1a->get('id'))); $this->assertTrue(\core_competency\user_evidence::record_exists($ue1b->get('id'))); $this->assertTrue(\core_competency\user_evidence::record_exists($ue2->get('id'))); $this->assertTrue(\core_competency\user_evidence_competency::record_exists($uec1a->get('id'))); $this->assertTrue(\core_competency\user_evidence_competency::record_exists($uec1b->get('id'))); $this->assertTrue(\core_competency\user_evidence_competency::record_exists($uec2->get('id'))); $this->assertTrue(\core_competency\plan::record_exists($p1a->get('id'))); $this->assertTrue(\core_competency\plan::record_exists($p1b->get('id'))); $this->assertTrue(\core_competency\plan::record_exists($p2->get('id'))); $this->assertTrue(\core_competency\plan_competency::record_exists($pc1a->get('id'))); $this->assertTrue(\core_competency\plan_competency::record_exists($pc1b->get('id'))); $this->assertTrue(\core_competency\plan_competency::record_exists($pc2->get('id'))); $this->assertTrue(\core_competency\user_competency_plan::record_exists($ucp1a->get('id'))); $this->assertTrue(\core_competency\user_competency_plan::record_exists($ucp1b->get('id'))); $this->assertTrue(\core_competency\user_competency_plan::record_exists($ucp2->get('id'))); $this->assertTrue(\core_competency\user_competency::record_exists($uc1a->get('id'))); $this->assertTrue(\core_competency\user_competency::record_exists($uc1b->get('id'))); $this->assertTrue(\core_competency\user_competency::record_exists($uc2->get('id'))); $this->assertTrue(\core_competency\evidence::record_exists($e1a->get('id'))); $this->assertTrue(\core_competency\evidence::record_exists($e1b->get('id'))); $this->assertTrue(\core_competency\evidence::record_exists($e2->get('id'))); $this->assertTrue(\core_competency\user_competency_course::record_exists($ucc1a->get('id'))); $this->assertTrue(\core_competency\user_competency_course::record_exists($ucc1b->get('id'))); $this->assertTrue(\core_competency\user_competency_course::record_exists($ucc2->get('id'))); $this->assert_has_comments($p1acommentobj); $this->assertEquals(2, $this->get_comments_count($p1acommentobj, $u1->id)); $this->assertEquals(0, $this->get_comments_count($p1acommentobj, $u2->id)); $this->assert_has_comments($p2commentobj); $this->assertEquals(1, $this->get_comments_count($p2commentobj, $u1->id)); $this->assertEquals(0, $this->get_comments_count($p2commentobj, $u2->id)); $this->assert_has_comments($uc1acommentobj); $this->assertEquals(0, $this->get_comments_count($uc1acommentobj, $u1->id)); $this->assertEquals(2, $this->get_comments_count($uc1acommentobj, $u2->id)); $this->assert_has_comments($uc2commentobj); $this->assertEquals(0, $this->get_comments_count($uc2commentobj, $u1->id)); $this->assertEquals(1, $this->get_comments_count($uc2commentobj, $u2->id)); // Deleting user context only. $appctx = new approved_contextlist($u1, 'core_competency', [$u1ctx->id]); provider::delete_data_for_user($appctx); $this->assertFalse(\core_competency\user_evidence::record_exists($ue1a->get('id'))); $this->assertFalse(\core_competency\user_evidence::record_exists($ue1b->get('id'))); $this->assertFalse(\core_competency\user_evidence_competency::record_exists($uec1a->get('id'))); $this->assertFalse(\core_competency\user_evidence_competency::record_exists($uec1b->get('id'))); $this->assertFalse(\core_competency\plan::record_exists($p1a->get('id'))); $this->assertFalse(\core_competency\plan::record_exists($p1b->get('id'))); $this->assertFalse(\core_competency\plan_competency::record_exists($pc1a->get('id'))); $this->assertFalse(\core_competency\plan_competency::record_exists($pc1b->get('id'))); $this->assertFalse(\core_competency\user_competency_plan::record_exists($ucp1a->get('id'))); $this->assertFalse(\core_competency\user_competency_plan::record_exists($ucp1b->get('id'))); $this->assertFalse(\core_competency\user_competency::record_exists($uc1a->get('id'))); $this->assertFalse(\core_competency\user_competency::record_exists($uc1b->get('id'))); $this->assertFalse(\core_competency\evidence::record_exists($e1a->get('id'))); $this->assertFalse(\core_competency\evidence::record_exists($e1b->get('id'))); $this->assert_has_no_comments($p1acommentobj); $this->assertEquals(0, $this->get_comments_count($p1acommentobj, $u1->id)); $this->assertEquals(0, $this->get_comments_count($p1acommentobj, $u2->id)); $this->assert_has_no_comments($uc1acommentobj); $this->assertEquals(0, $this->get_comments_count($uc1acommentobj, $u1->id)); $this->assertEquals(0, $this->get_comments_count($uc1acommentobj, $u2->id)); // This should not have been affected. $this->assertTrue(\core_competency\user_competency_course::record_exists($ucc1a->get('id'))); $this->assertTrue(\core_competency\user_competency_course::record_exists($ucc1b->get('id'))); $this->assertTrue(\core_competency\user_evidence::record_exists($ue2->get('id'))); $this->assertTrue(\core_competency\user_evidence_competency::record_exists($uec2->get('id'))); $this->assertTrue(\core_competency\plan::record_exists($p2->get('id'))); $this->assertTrue(\core_competency\plan_competency::record_exists($pc2->get('id'))); $this->assertTrue(\core_competency\user_competency_plan::record_exists($ucp2->get('id'))); $this->assertTrue(\core_competency\user_competency::record_exists($uc2->get('id'))); $this->assertTrue(\core_competency\evidence::record_exists($e2->get('id'))); $this->assertTrue(\core_competency\user_competency_course::record_exists($ucc2->get('id'))); $this->assert_has_comments($p2commentobj); $this->assertEquals(1, $this->get_comments_count($p2commentobj, $u1->id)); $this->assertEquals(0, $this->get_comments_count($p2commentobj, $u2->id)); $this->assert_has_comments($uc2commentobj); $this->assertEquals(0, $this->get_comments_count($uc2commentobj, $u1->id)); $this->assertEquals(1, $this->get_comments_count($uc2commentobj, $u2->id)); // Deleting course context as well. $appctx = new approved_contextlist($u1, 'core_competency', [$u1ctx->id, $c1ctx->id]); provider::delete_data_for_user($appctx); $this->assertFalse(\core_competency\user_competency_course::record_exists($ucc1a->get('id'))); // The rest belongs to another course, or the other user. $this->assertTrue(\core_competency\user_competency_course::record_exists($ucc1b->get('id'))); $this->assertTrue(\core_competency\user_evidence::record_exists($ue2->get('id'))); $this->assertTrue(\core_competency\user_evidence_competency::record_exists($uec2->get('id'))); $this->assertTrue(\core_competency\plan::record_exists($p2->get('id'))); $this->assertTrue(\core_competency\plan_competency::record_exists($pc2->get('id'))); $this->assertTrue(\core_competency\user_competency_plan::record_exists($ucp2->get('id'))); $this->assertTrue(\core_competency\user_competency::record_exists($uc2->get('id'))); $this->assertTrue(\core_competency\evidence::record_exists($e2->get('id'))); $this->assertTrue(\core_competency\user_competency_course::record_exists($ucc2->get('id'))); } public function test_delete_data_for_user_with_other_user_context(): void { $dg = $this->getDataGenerator(); $ccg = $dg->get_plugin_generator('core_competency'); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $u1ctx = \context_user::instance($u1->id); $u2ctx = \context_user::instance($u2->id); $f = $ccg->create_framework(); $comp1 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); // Create a bunch of data for user 1. $ue1a = $ccg->create_user_evidence(['userid' => $u1->id]); $uec1a = $ccg->create_user_evidence_competency(['userevidenceid' => $ue1a->get('id'), 'competencyid' => $comp1->get('id')]); $p1a = $ccg->create_plan(['userid' => $u1->id]); $pc1a = $ccg->create_plan_competency(['planid' => $p1a->get('id'), 'competencyid' => $comp1->get('id')]); $ucp1a = $ccg->create_user_competency_plan(['userid' => $u1->id, 'planid' => $p1a->get('id'), 'competencyid' => $comp1->get('id')]); $uc1a = $ccg->create_user_competency(['userid' => $u1->id, 'competencyid' => $comp1->get('id')]); $e1a = $ccg->create_evidence(['usercompetencyid' => $uc1a->get('id')]); $p2a = $ccg->create_plan(['userid' => $u2->id]); // User 2 comments. $this->allow_anyone_to_comment_anywhere(); $this->setUser($u2); $p1a->get_comment_object()->add('Hi...'); $p2a->get_comment_object()->add('Hi, hi!'); $uc1a->get_comment_object()->add('Hi, too!'); // Confirm state. $this->setAdminUser(); $this->assertTrue(\core_competency\user_evidence::record_exists($ue1a->get('id'))); $this->assertTrue(\core_competency\user_evidence_competency::record_exists($uec1a->get('id'))); $this->assertTrue(\core_competency\plan::record_exists($p1a->get('id'))); $this->assertTrue(\core_competency\plan_competency::record_exists($pc1a->get('id'))); $this->assertTrue(\core_competency\user_competency_plan::record_exists($ucp1a->get('id'))); $this->assertTrue(\core_competency\user_competency::record_exists($uc1a->get('id'))); $this->assertTrue(\core_competency\evidence::record_exists($e1a->get('id'))); $this->assert_has_comments($p1a->get_comment_object()); $this->assertEquals(1, $this->get_comments_count($p1a->get_comment_object(), $u2->id)); $this->assert_has_comments($p2a->get_comment_object()); $this->assertEquals(1, $this->get_comments_count($p2a->get_comment_object(), $u2->id)); $this->assert_has_comments($uc1a->get_comment_object()); $this->assertEquals(1, $this->get_comments_count($uc1a->get_comment_object(), $u2->id)); $this->assertTrue(\core_competency\plan::record_exists($p2a->get('id'))); // Delete for user 2, but we pass u1 context. provider::delete_data_for_user(new approved_contextlist($u2, 'core_competency', [$u1ctx->id])); // Nothing should have happened. $this->assertTrue(\core_competency\user_evidence::record_exists($ue1a->get('id'))); $this->assertTrue(\core_competency\user_evidence_competency::record_exists($uec1a->get('id'))); $this->assertTrue(\core_competency\plan::record_exists($p1a->get('id'))); $this->assertTrue(\core_competency\plan_competency::record_exists($pc1a->get('id'))); $this->assertTrue(\core_competency\user_competency_plan::record_exists($ucp1a->get('id'))); $this->assertTrue(\core_competency\user_competency::record_exists($uc1a->get('id'))); $this->assertTrue(\core_competency\evidence::record_exists($e1a->get('id'))); $this->assert_has_comments($p1a->get_comment_object()); $this->assertEquals(1, $this->get_comments_count($p1a->get_comment_object(), $u2->id)); $this->assert_has_comments($p2a->get_comment_object()); $this->assertEquals(1, $this->get_comments_count($p2a->get_comment_object(), $u2->id)); $this->assert_has_comments($uc1a->get_comment_object()); $this->assertEquals(1, $this->get_comments_count($uc1a->get_comment_object(), $u2->id)); $this->assertTrue(\core_competency\plan::record_exists($p2a->get('id'))); // Delete for user 2, but we pass u1 and u2 context. $p2acommentobj = $p2a->get_comment_object(); provider::delete_data_for_user(new approved_contextlist($u2, 'core_competency', [$u1ctx->id, $u2ctx->id])); // The plan got deleted. $this->assertFalse(\core_competency\plan::record_exists($p2a->get('id'))); $this->assert_has_no_comments($p2acommentobj); // Nothing should have happened for u1. $this->assertTrue(\core_competency\user_evidence::record_exists($ue1a->get('id'))); $this->assertTrue(\core_competency\user_evidence_competency::record_exists($uec1a->get('id'))); $this->assertTrue(\core_competency\plan::record_exists($p1a->get('id'))); $this->assertTrue(\core_competency\plan_competency::record_exists($pc1a->get('id'))); $this->assertTrue(\core_competency\user_competency_plan::record_exists($ucp1a->get('id'))); $this->assertTrue(\core_competency\user_competency::record_exists($uc1a->get('id'))); $this->assertTrue(\core_competency\evidence::record_exists($e1a->get('id'))); $this->assert_has_comments($p1a->get_comment_object()); $this->assertEquals(1, $this->get_comments_count($p1a->get_comment_object(), $u2->id)); $this->assert_has_comments($uc1a->get_comment_object()); $this->assertEquals(1, $this->get_comments_count($uc1a->get_comment_object(), $u2->id)); } public function test_delete_data_for_users(): void { $dg = $this->getDataGenerator(); $ccg = $dg->get_plugin_generator('core_competency'); $c1 = $dg->create_course(); $c2 = $dg->create_course(); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $c1ctx = \context_course::instance($c1->id); $u1ctx = \context_user::instance($u1->id); $f = $ccg->create_framework(); $comp1 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $comp2 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $ue1a = $ccg->create_user_evidence(['userid' => $u1->id]); $ue1b = $ccg->create_user_evidence(['userid' => $u1->id]); $ue2 = $ccg->create_user_evidence(['userid' => $u2->id]); $uec1a = $ccg->create_user_evidence_competency(['userevidenceid' => $ue1a->get('id'), 'competencyid' => $comp1->get('id')]); $uec1b = $ccg->create_user_evidence_competency(['userevidenceid' => $ue1b->get('id'), 'competencyid' => $comp2->get('id')]); $uec2 = $ccg->create_user_evidence_competency(['userevidenceid' => $ue2->get('id'), 'competencyid' => $comp1->get('id')]); $p1a = $ccg->create_plan(['userid' => $u1->id]); $p1b = $ccg->create_plan(['userid' => $u1->id]); $p2 = $ccg->create_plan(['userid' => $u2->id]); $pc1a = $ccg->create_plan_competency(['planid' => $p1a->get('id'), 'competencyid' => $comp1->get('id')]); $pc1b = $ccg->create_plan_competency(['planid' => $p1b->get('id'), 'competencyid' => $comp2->get('id')]); $pc2 = $ccg->create_plan_competency(['planid' => $p2->get('id'), 'competencyid' => $comp1->get('id')]); $ucp1a = $ccg->create_user_competency_plan(['userid' => $u1->id, 'planid' => $p1a->get('id'), 'competencyid' => $comp1->get('id')]); $ucp1b = $ccg->create_user_competency_plan(['userid' => $u1->id, 'planid' => $p1b->get('id'), 'competencyid' => $comp2->get('id')]); $ucp2 = $ccg->create_user_competency_plan(['userid' => $u2->id, 'planid' => $p2->get('id'), 'competencyid' => $comp1->get('id')]); $uc1a = $ccg->create_user_competency(['userid' => $u1->id, 'competencyid' => $comp1->get('id')]); $uc1b = $ccg->create_user_competency(['userid' => $u1->id, 'competencyid' => $comp2->get('id')]); $uc2 = $ccg->create_user_competency(['userid' => $u2->id, 'competencyid' => $comp2->get('id')]); $e1a = $ccg->create_evidence(['usercompetencyid' => $uc1a->get('id')]); $e1b = $ccg->create_evidence(['usercompetencyid' => $uc1b->get('id')]); $e2 = $ccg->create_evidence(['usercompetencyid' => $uc2->get('id')]); $ucc1a = $ccg->create_user_competency_course(['userid' => $u1->id, 'courseid' => $c1->id, 'competencyid' => $comp1->get('id')]); $ucc1b = $ccg->create_user_competency_course(['userid' => $u1->id, 'courseid' => $c2->id, 'competencyid' => $comp1->get('id')]); $ucc2 = $ccg->create_user_competency_course(['userid' => $u2->id, 'courseid' => $c1->id, 'competencyid' => $comp1->get('id')]); // User 1 comments on both plans. $this->allow_anyone_to_comment_anywhere(); $this->setUser($u1); $p1a->get_comment_object()->add('Hi...'); $p1a->get_comment_object()->add('mister'); $p2->get_comment_object()->add('Ahoy!'); // User 2 comments on both competencies. $this->setUser($u2); $uc1a->get_comment_object()->add('Hi, too!'); $uc1a->get_comment_object()->add('How are you?'); $uc2->get_comment_object()->add('Ahoy, too!'); $p1acommentobj = $p1a->get_comment_object(); $p2commentobj = $p2->get_comment_object(); $uc1acommentobj = $uc1a->get_comment_object(); $uc2commentobj = $uc2->get_comment_object(); $this->setAdminUser(); $this->assertTrue(\core_competency\user_evidence::record_exists($ue1a->get('id'))); $this->assertTrue(\core_competency\user_evidence::record_exists($ue1b->get('id'))); $this->assertTrue(\core_competency\user_evidence::record_exists($ue2->get('id'))); $this->assertTrue(\core_competency\user_evidence_competency::record_exists($uec1a->get('id'))); $this->assertTrue(\core_competency\user_evidence_competency::record_exists($uec1b->get('id'))); $this->assertTrue(\core_competency\user_evidence_competency::record_exists($uec2->get('id'))); $this->assertTrue(\core_competency\plan::record_exists($p1a->get('id'))); $this->assertTrue(\core_competency\plan::record_exists($p1b->get('id'))); $this->assertTrue(\core_competency\plan::record_exists($p2->get('id'))); $this->assertTrue(\core_competency\plan_competency::record_exists($pc1a->get('id'))); $this->assertTrue(\core_competency\plan_competency::record_exists($pc1b->get('id'))); $this->assertTrue(\core_competency\plan_competency::record_exists($pc2->get('id'))); $this->assertTrue(\core_competency\user_competency_plan::record_exists($ucp1a->get('id'))); $this->assertTrue(\core_competency\user_competency_plan::record_exists($ucp1b->get('id'))); $this->assertTrue(\core_competency\user_competency_plan::record_exists($ucp2->get('id'))); $this->assertTrue(\core_competency\user_competency::record_exists($uc1a->get('id'))); $this->assertTrue(\core_competency\user_competency::record_exists($uc1b->get('id'))); $this->assertTrue(\core_competency\user_competency::record_exists($uc2->get('id'))); $this->assertTrue(\core_competency\evidence::record_exists($e1a->get('id'))); $this->assertTrue(\core_competency\evidence::record_exists($e1b->get('id'))); $this->assertTrue(\core_competency\evidence::record_exists($e2->get('id'))); $this->assertTrue(\core_competency\user_competency_course::record_exists($ucc1a->get('id'))); $this->assertTrue(\core_competency\user_competency_course::record_exists($ucc1b->get('id'))); $this->assertTrue(\core_competency\user_competency_course::record_exists($ucc2->get('id'))); $this->assert_has_comments($p1acommentobj); $this->assertEquals(2, $this->get_comments_count($p1acommentobj, $u1->id)); $this->assertEquals(0, $this->get_comments_count($p1acommentobj, $u2->id)); $this->assert_has_comments($p2commentobj); $this->assertEquals(1, $this->get_comments_count($p2commentobj, $u1->id)); $this->assertEquals(0, $this->get_comments_count($p2commentobj, $u2->id)); $this->assert_has_comments($uc1acommentobj); $this->assertEquals(0, $this->get_comments_count($uc1acommentobj, $u1->id)); $this->assertEquals(2, $this->get_comments_count($uc1acommentobj, $u2->id)); $this->assert_has_comments($uc2commentobj); $this->assertEquals(0, $this->get_comments_count($uc2commentobj, $u1->id)); $this->assertEquals(1, $this->get_comments_count($uc2commentobj, $u2->id)); // Deleting user context. $userlist = new approved_userlist($u1ctx, 'core_competency', [$u1->id, $u2->id]); provider::delete_data_for_users($userlist); $this->assertFalse(\core_competency\user_evidence::record_exists($ue1a->get('id'))); $this->assertFalse(\core_competency\user_evidence::record_exists($ue1b->get('id'))); $this->assertFalse(\core_competency\user_evidence_competency::record_exists($uec1a->get('id'))); $this->assertFalse(\core_competency\user_evidence_competency::record_exists($uec1b->get('id'))); $this->assertFalse(\core_competency\plan::record_exists($p1a->get('id'))); $this->assertFalse(\core_competency\plan::record_exists($p1b->get('id'))); $this->assertFalse(\core_competency\plan_competency::record_exists($pc1a->get('id'))); $this->assertFalse(\core_competency\plan_competency::record_exists($pc1b->get('id'))); $this->assertFalse(\core_competency\user_competency_plan::record_exists($ucp1a->get('id'))); $this->assertFalse(\core_competency\user_competency_plan::record_exists($ucp1b->get('id'))); $this->assertFalse(\core_competency\user_competency::record_exists($uc1a->get('id'))); $this->assertFalse(\core_competency\user_competency::record_exists($uc1b->get('id'))); $this->assertFalse(\core_competency\evidence::record_exists($e1a->get('id'))); $this->assertFalse(\core_competency\evidence::record_exists($e1b->get('id'))); $this->assert_has_no_comments($p1acommentobj); $this->assertEquals(0, $this->get_comments_count($p1acommentobj, $u1->id)); $this->assertEquals(0, $this->get_comments_count($p1acommentobj, $u2->id)); $this->assert_has_no_comments($uc1acommentobj); $this->assertEquals(0, $this->get_comments_count($uc1acommentobj, $u1->id)); $this->assertEquals(0, $this->get_comments_count($uc1acommentobj, $u2->id)); // This should not have been affected. $this->assertTrue(\core_competency\user_competency_course::record_exists($ucc1a->get('id'))); $this->assertTrue(\core_competency\user_competency_course::record_exists($ucc1b->get('id'))); $this->assertTrue(\core_competency\user_evidence::record_exists($ue2->get('id'))); $this->assertTrue(\core_competency\user_evidence_competency::record_exists($uec2->get('id'))); $this->assertTrue(\core_competency\plan::record_exists($p2->get('id'))); $this->assertTrue(\core_competency\plan_competency::record_exists($pc2->get('id'))); $this->assertTrue(\core_competency\user_competency_plan::record_exists($ucp2->get('id'))); $this->assertTrue(\core_competency\user_competency::record_exists($uc2->get('id'))); $this->assertTrue(\core_competency\evidence::record_exists($e2->get('id'))); $this->assertTrue(\core_competency\user_competency_course::record_exists($ucc2->get('id'))); $this->assert_has_comments($p2commentobj); $this->assertEquals(1, $this->get_comments_count($p2commentobj, $u1->id)); $this->assertEquals(0, $this->get_comments_count($p2commentobj, $u2->id)); $this->assert_has_comments($uc2commentobj); $this->assertEquals(0, $this->get_comments_count($uc2commentobj, $u1->id)); $this->assertEquals(1, $this->get_comments_count($uc2commentobj, $u2->id)); // Deleting course context as well. $userlist = new approved_userlist($c1ctx, 'core_competency', [$u1->id]); provider::delete_data_for_users($userlist); $this->assertFalse(\core_competency\user_competency_course::record_exists($ucc1a->get('id'))); // The rest belongs to another course, or the other user. $this->assertTrue(\core_competency\user_competency_course::record_exists($ucc1b->get('id'))); $this->assertTrue(\core_competency\user_evidence::record_exists($ue2->get('id'))); $this->assertTrue(\core_competency\user_evidence_competency::record_exists($uec2->get('id'))); $this->assertTrue(\core_competency\plan::record_exists($p2->get('id'))); $this->assertTrue(\core_competency\plan_competency::record_exists($pc2->get('id'))); $this->assertTrue(\core_competency\user_competency_plan::record_exists($ucp2->get('id'))); $this->assertTrue(\core_competency\user_competency::record_exists($uc2->get('id'))); $this->assertTrue(\core_competency\evidence::record_exists($e2->get('id'))); $this->assertTrue(\core_competency\user_competency_course::record_exists($ucc2->get('id'))); } public function test_delete_data_for_users_with_other_user_context(): void { $dg = $this->getDataGenerator(); $ccg = $dg->get_plugin_generator('core_competency'); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $u1ctx = \context_user::instance($u1->id); $u2ctx = \context_user::instance($u2->id); $f = $ccg->create_framework(); $comp1 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); // Create a bunch of data for user 1. $ue1a = $ccg->create_user_evidence(['userid' => $u1->id]); $uec1a = $ccg->create_user_evidence_competency(['userevidenceid' => $ue1a->get('id'), 'competencyid' => $comp1->get('id')]); $p1a = $ccg->create_plan(['userid' => $u1->id]); $pc1a = $ccg->create_plan_competency(['planid' => $p1a->get('id'), 'competencyid' => $comp1->get('id')]); $ucp1a = $ccg->create_user_competency_plan(['userid' => $u1->id, 'planid' => $p1a->get('id'), 'competencyid' => $comp1->get('id')]); $uc1a = $ccg->create_user_competency(['userid' => $u1->id, 'competencyid' => $comp1->get('id')]); $e1a = $ccg->create_evidence(['usercompetencyid' => $uc1a->get('id')]); $p2a = $ccg->create_plan(['userid' => $u2->id]); // User 2 comments. $this->allow_anyone_to_comment_anywhere(); $this->setUser($u2); $p1a->get_comment_object()->add('Hi...'); $p2a->get_comment_object()->add('Hi, hi!'); $uc1a->get_comment_object()->add('Hi, too!'); // Confirm state. $this->setAdminUser(); $this->assertTrue(\core_competency\user_evidence::record_exists($ue1a->get('id'))); $this->assertTrue(\core_competency\user_evidence_competency::record_exists($uec1a->get('id'))); $this->assertTrue(\core_competency\plan::record_exists($p1a->get('id'))); $this->assertTrue(\core_competency\plan_competency::record_exists($pc1a->get('id'))); $this->assertTrue(\core_competency\user_competency_plan::record_exists($ucp1a->get('id'))); $this->assertTrue(\core_competency\user_competency::record_exists($uc1a->get('id'))); $this->assertTrue(\core_competency\evidence::record_exists($e1a->get('id'))); $this->assert_has_comments($p1a->get_comment_object()); $this->assertEquals(1, $this->get_comments_count($p1a->get_comment_object(), $u2->id)); $this->assert_has_comments($p2a->get_comment_object()); $this->assertEquals(1, $this->get_comments_count($p2a->get_comment_object(), $u2->id)); $this->assert_has_comments($uc1a->get_comment_object()); $this->assertEquals(1, $this->get_comments_count($uc1a->get_comment_object(), $u2->id)); $this->assertTrue(\core_competency\plan::record_exists($p2a->get('id'))); // Delete for user 2, but we pass u1 context. $userlist = new approved_userlist($u1ctx, 'core_competency', [$u2->id]); provider::delete_data_for_users($userlist); // Nothing should have happened. $this->assertTrue(\core_competency\user_evidence::record_exists($ue1a->get('id'))); $this->assertTrue(\core_competency\user_evidence_competency::record_exists($uec1a->get('id'))); $this->assertTrue(\core_competency\plan::record_exists($p1a->get('id'))); $this->assertTrue(\core_competency\plan_competency::record_exists($pc1a->get('id'))); $this->assertTrue(\core_competency\user_competency_plan::record_exists($ucp1a->get('id'))); $this->assertTrue(\core_competency\user_competency::record_exists($uc1a->get('id'))); $this->assertTrue(\core_competency\evidence::record_exists($e1a->get('id'))); $this->assert_has_comments($p1a->get_comment_object()); $this->assertEquals(1, $this->get_comments_count($p1a->get_comment_object(), $u2->id)); $this->assert_has_comments($p2a->get_comment_object()); $this->assertEquals(1, $this->get_comments_count($p2a->get_comment_object(), $u2->id)); $this->assert_has_comments($uc1a->get_comment_object()); $this->assertEquals(1, $this->get_comments_count($uc1a->get_comment_object(), $u2->id)); $this->assertTrue(\core_competency\plan::record_exists($p2a->get('id'))); // Delete for user 2, in user 2 context. $p2acommentobj = $p2a->get_comment_object(); $userlist = new approved_userlist($u2ctx, 'core_competency', [$u2->id]); provider::delete_data_for_users($userlist); // The plan got deleted. $this->assertFalse(\core_competency\plan::record_exists($p2a->get('id'))); $this->assert_has_no_comments($p2acommentobj); // Nothing should have happened for u1. $this->assertTrue(\core_competency\user_evidence::record_exists($ue1a->get('id'))); $this->assertTrue(\core_competency\user_evidence_competency::record_exists($uec1a->get('id'))); $this->assertTrue(\core_competency\plan::record_exists($p1a->get('id'))); $this->assertTrue(\core_competency\plan_competency::record_exists($pc1a->get('id'))); $this->assertTrue(\core_competency\user_competency_plan::record_exists($ucp1a->get('id'))); $this->assertTrue(\core_competency\user_competency::record_exists($uc1a->get('id'))); $this->assertTrue(\core_competency\evidence::record_exists($e1a->get('id'))); $this->assert_has_comments($p1a->get_comment_object()); $this->assertEquals(1, $this->get_comments_count($p1a->get_comment_object(), $u2->id)); $this->assert_has_comments($uc1a->get_comment_object()); $this->assertEquals(1, $this->get_comments_count($uc1a->get_comment_object(), $u2->id)); } public function test_delete_data_for_all_users_in_context(): void { $dg = $this->getDataGenerator(); $ccg = $dg->get_plugin_generator('core_competency'); $c1 = $dg->create_course(); $c2 = $dg->create_course(); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $c1ctx = \context_course::instance($c1->id); $u1ctx = \context_user::instance($u1->id); $u2ctx = \context_user::instance($u2->id); $f = $ccg->create_framework(); $comp1 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $comp2 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $ue1a = $ccg->create_user_evidence(['userid' => $u1->id]); $ue1b = $ccg->create_user_evidence(['userid' => $u1->id]); $ue2 = $ccg->create_user_evidence(['userid' => $u2->id]); $uec1a = $ccg->create_user_evidence_competency(['userevidenceid' => $ue1a->get('id'), 'competencyid' => $comp1->get('id')]); $uec1b = $ccg->create_user_evidence_competency(['userevidenceid' => $ue1b->get('id'), 'competencyid' => $comp2->get('id')]); $uec2 = $ccg->create_user_evidence_competency(['userevidenceid' => $ue2->get('id'), 'competencyid' => $comp1->get('id')]); $p1a = $ccg->create_plan(['userid' => $u1->id]); $p1b = $ccg->create_plan(['userid' => $u1->id]); $p2 = $ccg->create_plan(['userid' => $u2->id]); $pc1a = $ccg->create_plan_competency(['planid' => $p1a->get('id'), 'competencyid' => $comp1->get('id')]); $pc1b = $ccg->create_plan_competency(['planid' => $p1b->get('id'), 'competencyid' => $comp2->get('id')]); $pc2 = $ccg->create_plan_competency(['planid' => $p2->get('id'), 'competencyid' => $comp1->get('id')]); $ucp1a = $ccg->create_user_competency_plan(['userid' => $u1->id, 'planid' => $p1a->get('id'), 'competencyid' => $comp1->get('id')]); $ucp1b = $ccg->create_user_competency_plan(['userid' => $u1->id, 'planid' => $p1b->get('id'), 'competencyid' => $comp2->get('id')]); $ucp2 = $ccg->create_user_competency_plan(['userid' => $u2->id, 'planid' => $p2->get('id'), 'competencyid' => $comp1->get('id')]); $uc1a = $ccg->create_user_competency(['userid' => $u1->id, 'competencyid' => $comp1->get('id')]); $uc1b = $ccg->create_user_competency(['userid' => $u1->id, 'competencyid' => $comp2->get('id')]); $uc2 = $ccg->create_user_competency(['userid' => $u2->id, 'competencyid' => $comp2->get('id')]); $e1a = $ccg->create_evidence(['usercompetencyid' => $uc1a->get('id')]); $e1b = $ccg->create_evidence(['usercompetencyid' => $uc1b->get('id')]); $e2 = $ccg->create_evidence(['usercompetencyid' => $uc2->get('id')]); $ucc1a = $ccg->create_user_competency_course(['userid' => $u1->id, 'courseid' => $c1->id, 'competencyid' => $comp1->get('id')]); $ucc1b = $ccg->create_user_competency_course(['userid' => $u1->id, 'courseid' => $c2->id, 'competencyid' => $comp1->get('id')]); $ucc2 = $ccg->create_user_competency_course(['userid' => $u2->id, 'courseid' => $c1->id, 'competencyid' => $comp1->get('id')]); $this->assertTrue(\core_competency\user_evidence::record_exists($ue1a->get('id'))); $this->assertTrue(\core_competency\user_evidence::record_exists($ue1b->get('id'))); $this->assertTrue(\core_competency\user_evidence::record_exists($ue2->get('id'))); $this->assertTrue(\core_competency\user_evidence_competency::record_exists($uec1a->get('id'))); $this->assertTrue(\core_competency\user_evidence_competency::record_exists($uec1b->get('id'))); $this->assertTrue(\core_competency\user_evidence_competency::record_exists($uec2->get('id'))); $this->assertTrue(\core_competency\plan::record_exists($p1a->get('id'))); $this->assertTrue(\core_competency\plan::record_exists($p1b->get('id'))); $this->assertTrue(\core_competency\plan::record_exists($p2->get('id'))); $this->assertTrue(\core_competency\plan_competency::record_exists($pc1a->get('id'))); $this->assertTrue(\core_competency\plan_competency::record_exists($pc1b->get('id'))); $this->assertTrue(\core_competency\plan_competency::record_exists($pc2->get('id'))); $this->assertTrue(\core_competency\user_competency_plan::record_exists($ucp1a->get('id'))); $this->assertTrue(\core_competency\user_competency_plan::record_exists($ucp1b->get('id'))); $this->assertTrue(\core_competency\user_competency_plan::record_exists($ucp2->get('id'))); $this->assertTrue(\core_competency\user_competency::record_exists($uc1a->get('id'))); $this->assertTrue(\core_competency\user_competency::record_exists($uc1b->get('id'))); $this->assertTrue(\core_competency\user_competency::record_exists($uc2->get('id'))); $this->assertTrue(\core_competency\evidence::record_exists($e1a->get('id'))); $this->assertTrue(\core_competency\evidence::record_exists($e1b->get('id'))); $this->assertTrue(\core_competency\evidence::record_exists($e2->get('id'))); $this->assertTrue(\core_competency\user_competency_course::record_exists($ucc1a->get('id'))); $this->assertTrue(\core_competency\user_competency_course::record_exists($ucc1b->get('id'))); $this->assertTrue(\core_competency\user_competency_course::record_exists($ucc2->get('id'))); // Deleting the course 1 context. provider::delete_data_for_all_users_in_context($c1ctx); $this->assertFalse(\core_competency\user_competency_course::record_exists($ucc1a->get('id'))); $this->assertFalse(\core_competency\user_competency_course::record_exists($ucc2->get('id'))); // Not affected. $this->assertTrue(\core_competency\user_evidence::record_exists($ue1a->get('id'))); $this->assertTrue(\core_competency\user_evidence::record_exists($ue1b->get('id'))); $this->assertTrue(\core_competency\user_evidence::record_exists($ue2->get('id'))); $this->assertTrue(\core_competency\user_evidence_competency::record_exists($uec1a->get('id'))); $this->assertTrue(\core_competency\user_evidence_competency::record_exists($uec1b->get('id'))); $this->assertTrue(\core_competency\user_evidence_competency::record_exists($uec2->get('id'))); $this->assertTrue(\core_competency\plan::record_exists($p1a->get('id'))); $this->assertTrue(\core_competency\plan::record_exists($p1b->get('id'))); $this->assertTrue(\core_competency\plan::record_exists($p2->get('id'))); $this->assertTrue(\core_competency\plan_competency::record_exists($pc1a->get('id'))); $this->assertTrue(\core_competency\plan_competency::record_exists($pc1b->get('id'))); $this->assertTrue(\core_competency\plan_competency::record_exists($pc2->get('id'))); $this->assertTrue(\core_competency\user_competency_plan::record_exists($ucp1a->get('id'))); $this->assertTrue(\core_competency\user_competency_plan::record_exists($ucp1b->get('id'))); $this->assertTrue(\core_competency\user_competency_plan::record_exists($ucp2->get('id'))); $this->assertTrue(\core_competency\user_competency::record_exists($uc1a->get('id'))); $this->assertTrue(\core_competency\user_competency::record_exists($uc1b->get('id'))); $this->assertTrue(\core_competency\user_competency::record_exists($uc2->get('id'))); $this->assertTrue(\core_competency\evidence::record_exists($e1a->get('id'))); $this->assertTrue(\core_competency\evidence::record_exists($e1b->get('id'))); $this->assertTrue(\core_competency\evidence::record_exists($e2->get('id'))); $this->assertTrue(\core_competency\user_competency_course::record_exists($ucc1b->get('id'))); // Deleting the user 1 context. provider::delete_data_for_all_users_in_context($u1ctx); $this->assertFalse(\core_competency\user_evidence::record_exists($ue1a->get('id'))); $this->assertFalse(\core_competency\user_evidence::record_exists($ue1b->get('id'))); $this->assertFalse(\core_competency\user_evidence_competency::record_exists($uec1a->get('id'))); $this->assertFalse(\core_competency\user_evidence_competency::record_exists($uec1b->get('id'))); $this->assertFalse(\core_competency\plan::record_exists($p1a->get('id'))); $this->assertFalse(\core_competency\plan::record_exists($p1b->get('id'))); $this->assertFalse(\core_competency\plan_competency::record_exists($pc1a->get('id'))); $this->assertFalse(\core_competency\plan_competency::record_exists($pc1b->get('id'))); $this->assertFalse(\core_competency\user_competency_plan::record_exists($ucp1a->get('id'))); $this->assertFalse(\core_competency\user_competency_plan::record_exists($ucp1b->get('id'))); $this->assertFalse(\core_competency\user_competency::record_exists($uc1a->get('id'))); $this->assertFalse(\core_competency\user_competency::record_exists($uc1b->get('id'))); $this->assertFalse(\core_competency\evidence::record_exists($e1a->get('id'))); $this->assertFalse(\core_competency\evidence::record_exists($e1b->get('id'))); // Not affected. $this->assertTrue(\core_competency\user_evidence::record_exists($ue2->get('id'))); $this->assertTrue(\core_competency\user_evidence_competency::record_exists($uec2->get('id'))); $this->assertTrue(\core_competency\plan::record_exists($p2->get('id'))); $this->assertTrue(\core_competency\plan_competency::record_exists($pc2->get('id'))); $this->assertTrue(\core_competency\user_competency_plan::record_exists($ucp2->get('id'))); $this->assertTrue(\core_competency\user_competency::record_exists($uc2->get('id'))); $this->assertTrue(\core_competency\evidence::record_exists($e2->get('id'))); $this->assertTrue(\core_competency\user_competency_course::record_exists($ucc1b->get('id'))); } public function test_export_data_for_user_in_module_context_where_usermodified_matches(): void { $dg = $this->getDataGenerator(); $ccg = $dg->get_plugin_generator('core_competency'); $c1 = $dg->create_course(); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $m1 = $dg->create_module('page', ['course' => $c1]); $m2 = $dg->create_module('page', ['course' => $c1]); $m1ctx = \context_module::instance($m1->cmid); $m2ctx = \context_module::instance($m2->cmid); $f = $ccg->create_framework(); $comp1 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $comp2 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $comp3 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $ccg->create_course_module_competency(['competencyid' => $comp3->get('id'), 'cmid' => $m1->cmid]); $this->setUser($u1); $ccg->create_course_module_competency(['competencyid' => $comp1->get('id'), 'cmid' => $m1->cmid]); $ccg->create_course_module_competency(['competencyid' => $comp2->get('id'), 'cmid' => $m2->cmid]); $this->setUser($u2); $ccg->create_course_module_competency(['competencyid' => $comp3->get('id'), 'cmid' => $m2->cmid]); // Export. $this->setAdminUser(); provider::export_user_data(new approved_contextlist($u1, 'core_competency', [$m1ctx->id])); // Check exported context 1. $data = writer::with_context($m1ctx)->get_data([get_string('competencies', 'core_competency')]); $this->assertCount(1, $data->associations); $this->assertEquals(transform::yesno(true), $data->associations[0]['created_or_modified_by_you']); // Check exported context 2. $data = writer::with_context($m2ctx)->get_data([get_string('competencies', 'core_competency')]); $this->assertEmpty($data); // Export both contexts. provider::export_user_data(new approved_contextlist($u1, 'core_competency', [$m1ctx->id, $m2ctx->id])); // Check exported context 1. $data = writer::with_context($m1ctx)->get_data([get_string('competencies', 'core_competency')]); $this->assertCount(1, $data->associations); $this->assertEquals($comp1->get('shortname'), $data->associations[0]['name']); $this->assertEquals(transform::yesno(true), $data->associations[0]['created_or_modified_by_you']); // Check exported context 2. $data = writer::with_context($m2ctx)->get_data([get_string('competencies', 'core_competency')]); $this->assertCount(1, $data->associations); $this->assertEquals($comp2->get('shortname'), $data->associations[0]['name']); $this->assertEquals(transform::yesno(true), $data->associations[0]['created_or_modified_by_you']); } public function test_export_data_for_user_in_course_context_where_usermodified_matches(): void { $dg = $this->getDataGenerator(); $ccg = $dg->get_plugin_generator('core_competency'); $c1 = $dg->create_course(); $c2 = $dg->create_course(); $u0 = $dg->create_user(); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $u3 = $dg->create_user(); $u4 = $dg->create_user(); $c1ctx = \context_course::instance($c1->id); $c2ctx = \context_course::instance($c2->id); $f = $ccg->create_framework(); $comp1 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $comp2 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $comp3 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $comp4 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $ccg->create_course_competency(['competencyid' => $comp3->get('id'), 'courseid' => $c1->id]); $ccg->create_user_competency_course(['competencyid' => $comp3->get('id'), 'courseid' => $c1->id, 'userid' => $u0->id]); $this->setUser($u1); $ccg->create_course_competency(['competencyid' => $comp1->get('id'), 'courseid' => $c1->id]); $ccg->create_course_competency(['competencyid' => $comp4->get('id'), 'courseid' => $c1->id]); $ccg->create_course_competency(['competencyid' => $comp2->get('id'), 'courseid' => $c2->id]); $ccg->create_user_competency_course(['competencyid' => $comp1->get('id'), 'courseid' => $c1->id, 'userid' => $u0->id]); $ccg->create_user_competency_course(['competencyid' => $comp4->get('id'), 'courseid' => $c1->id, 'userid' => $u0->id]); $ccg->create_user_competency_course(['competencyid' => $comp2->get('id'), 'courseid' => $c2->id, 'userid' => $u0->id]); $ccs = new \core_competency\course_competency_settings(null, (object) ['courseid' => $c1->id]); $ccs->create(); $this->setUser($u2); $ccg->create_course_competency(['competencyid' => $comp3->get('id'), 'courseid' => $c2->id]); $ccg->create_user_competency_course(['competencyid' => $comp3->get('id'), 'courseid' => $c2->id, 'userid' => $u0->id]); $ccs = new \core_competency\course_competency_settings(null, (object) ['courseid' => $c2->id]); $ccs->create(); // Export. $this->setAdminUser(); provider::export_user_data(new approved_contextlist($u1, 'core_competency', [$c1ctx->id])); // Check exported context 1. $data = writer::with_context($c1ctx)->get_related_data([get_string('competencies', 'core_competency')], 'associations'); $this->assertCount(2, $data->competencies); $this->assertEquals($comp1->get('shortname'), $data->competencies[0]['name']); $this->assertEquals(transform::yesno(true), $data->competencies[0]['created_or_modified_by_you']); $this->assertEquals($comp4->get('shortname'), $data->competencies[1]['name']); $this->assertEquals(transform::yesno(true), $data->competencies[1]['created_or_modified_by_you']); $data = writer::with_context($c1ctx)->get_related_data([get_string('competencies', 'core_competency')], 'settings'); $this->assertEquals(transform::yesno(true), $data->created_or_modified_by_you); $data = writer::with_context($c1ctx)->get_related_data([get_string('competencies', 'core_competency')], 'rated_by_me'); $this->assertCount(2, $data->ratings); $this->assertEquals($comp1->get('shortname'), $data->ratings[0]['name']); $this->assertEquals($comp4->get('shortname'), $data->ratings[1]['name']); // Check exported context 2. $data = writer::with_context($c2ctx)->get_related_data([get_string('competencies', 'core_competency')], 'associations'); $this->assertEmpty($data); $data = writer::with_context($c2ctx)->get_related_data([get_string('competencies', 'core_competency')], 'settings'); $this->assertEmpty($data); $data = writer::with_context($c2ctx)->get_related_data([get_string('competencies', 'core_competency')], 'rated_by_me'); $this->assertEmpty($data); // Export both contexts. provider::export_user_data(new approved_contextlist($u1, 'core_competency', [$c1ctx->id, $c2ctx->id])); // Check exported context 1. $data = writer::with_context($c1ctx)->get_related_data([get_string('competencies', 'core_competency')], 'associations'); $this->assertCount(2, $data->competencies); $this->assertEquals($comp1->get('shortname'), $data->competencies[0]['name']); $this->assertEquals(transform::yesno(true), $data->competencies[0]['created_or_modified_by_you']); $this->assertEquals($comp4->get('shortname'), $data->competencies[1]['name']); $this->assertEquals(transform::yesno(true), $data->competencies[1]['created_or_modified_by_you']); $data = writer::with_context($c1ctx)->get_related_data([get_string('competencies', 'core_competency')], 'settings'); $this->assertEquals(transform::yesno(true), $data->created_or_modified_by_you); $data = writer::with_context($c1ctx)->get_related_data([get_string('competencies', 'core_competency')], 'rated_by_me'); $this->assertCount(2, $data->ratings); $this->assertEquals($comp1->get('shortname'), $data->ratings[0]['name']); $this->assertEquals($comp4->get('shortname'), $data->ratings[1]['name']); // Check exported context 2. $data = writer::with_context($c2ctx)->get_related_data([get_string('competencies', 'core_competency')], 'associations'); $this->assertCount(1, $data->competencies); $this->assertEquals($comp2->get('shortname'), $data->competencies[0]['name']); $this->assertEquals(transform::yesno(true), $data->competencies[0]['created_or_modified_by_you']); $data = writer::with_context($c2ctx)->get_related_data([get_string('competencies', 'core_competency')], 'settings'); $this->assertEmpty($data); $data = writer::with_context($c2ctx)->get_related_data([get_string('competencies', 'core_competency')], 'rated_by_me'); $this->assertCount(1, $data->ratings); $this->assertEquals($comp2->get('shortname'), $data->ratings[0]['name']); } public function test_export_data_for_user_in_course_context_with_real_data(): void { $dg = $this->getDataGenerator(); $ccg = $dg->get_plugin_generator('core_competency'); $c1 = $dg->create_course(); $c2 = $dg->create_course(); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $c1ctx = \context_course::instance($c1->id); $c2ctx = \context_course::instance($c2->id); $f = $ccg->create_framework(); $comp1 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $comp2 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $comp3 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $ccg->create_user_competency_course(['competencyid' => $comp1->get('id'), 'courseid' => $c1->id, 'userid' => $u1->id, 'grade' => 1, 'proficiency' => true]); $ccg->create_user_competency_course(['competencyid' => $comp2->get('id'), 'courseid' => $c1->id, 'userid' => $u1->id, 'grade' => 2, 'proficiency' => false]); $ccg->create_user_competency_course(['competencyid' => $comp2->get('id'), 'courseid' => $c2->id, 'userid' => $u1->id, 'grade' => 3, 'proficiency' => false]); $ccg->create_user_competency_course(['competencyid' => $comp3->get('id'), 'courseid' => $c2->id, 'userid' => $u1->id]); $ccg->create_user_competency_course(['competencyid' => $comp3->get('id'), 'courseid' => $c1->id, 'userid' => $u2->id]); $ccg->create_user_competency_course(['competencyid' => $comp3->get('id'), 'courseid' => $c2->id, 'userid' => $u2->id]); // Export user 1, in course 1. provider::export_user_data(new approved_contextlist($u1, 'core_competency', [$c1ctx->id])); // Check course 1. $data = writer::with_context($c1ctx)->get_data([get_string('competencies', 'core_competency')]); $this->assertCount(2, $data->ratings); $this->assertEquals($comp1->get('shortname'), $data->ratings[0]['name']); $this->assertEquals('A', $data->ratings[0]['rating']['rating']); $this->assertEquals(transform::yesno(true), $data->ratings[0]['rating']['proficient']); $this->assertEquals($comp2->get('shortname'), $data->ratings[1]['name']); $this->assertEquals('B', $data->ratings[1]['rating']['rating']); $this->assertEquals(transform::yesno(false), $data->ratings[1]['rating']['proficient']); // Check course 2. $data = writer::with_context($c2ctx)->get_data([get_string('competencies', 'core_competency')]); $this->assertEmpty($data); // Export user 1, in course 2. provider::export_user_data(new approved_contextlist($u1, 'core_competency', [$c2ctx->id])); $data = writer::with_context($c2ctx)->get_data([get_string('competencies', 'core_competency')]); $this->assertCount(2, $data->ratings); $this->assertEquals($comp2->get('shortname'), $data->ratings[0]['name']); $this->assertEquals('C', $data->ratings[0]['rating']['rating']); $this->assertEquals(transform::yesno(false), $data->ratings[0]['rating']['proficient']); $this->assertEquals($comp3->get('shortname'), $data->ratings[1]['name']); $this->assertEquals('-', $data->ratings[1]['rating']['rating']); $this->assertEquals('-', $data->ratings[1]['rating']['proficient']); } public function test_export_data_for_user_in_system_and_category_contexts(): void { $dg = $this->getDataGenerator(); $ccg = $dg->get_plugin_generator('core_competency'); $c1 = $dg->create_cohort(); $c2 = $dg->create_cohort(); $cat1 = $dg->create_category(); $cat2 = $dg->create_category(); $cat1ctx = \context_coursecat::instance($cat1->id); $cat2ctx = \context_coursecat::instance($cat2->id); $sysctx = \context_system::instance(); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $u3 = $dg->create_user(); $u4 = $dg->create_user(); $u2 = $dg->create_user(); $this->setUser($u1); $f1 = $ccg->create_framework(); $f1bis = $ccg->create_framework(); $f2 = $ccg->create_framework(['contextid' => $cat1ctx->id]); $c2a = $ccg->create_competency(['competencyframeworkid' => $f2->get('id')]); $c2b = $ccg->create_competency(['competencyframeworkid' => $f2->get('id')]); $t1 = $ccg->create_template(); $t2 = $ccg->create_template(['contextid' => $cat1ctx->id]); $tc2a = $ccg->create_template_competency(['templateid' => $t2->get('id'), 'competencyid' => $c2a->get('id')]); $tch2 = $ccg->create_template_cohort(['templateid' => $t2->get('id'), 'cohortid' => $c1->id]); $this->setUser($u2); $f3 = $ccg->create_framework(['contextid' => $cat2ctx->id]); $c1a = $ccg->create_competency(['competencyframeworkid' => $f1->get('id')]); $c1b = $ccg->create_competency(['competencyframeworkid' => $f1->get('id')]); $c3a = $ccg->create_competency(['competencyframeworkid' => $f3->get('id')]); $c3b = $ccg->create_competency(['competencyframeworkid' => $f3->get('id')]); $c3c = $ccg->create_competency(['competencyframeworkid' => $f3->get('id')]); $c3d = $ccg->create_competency(['competencyframeworkid' => $f3->get('id')]); $rc1 = $ccg->create_related_competency(['competencyid' => $c2a->get('id'), 'relatedcompetencyid' => $c2b->get('id')]); $t3 = $ccg->create_template(['contextid' => $cat2ctx->id]); $tch1 = $ccg->create_template_cohort(['templateid' => $t1->get('id'), 'cohortid' => $c2->id]); $tc1a = $ccg->create_template_competency(['templateid' => $t1->get('id'), 'competencyid' => $c1a->get('id')]); $tc1b = $ccg->create_template_competency(['templateid' => $t1->get('id'), 'competencyid' => $c2a->get('id')]); $tc3a = $ccg->create_template_competency(['templateid' => $t3->get('id'), 'competencyid' => $c3a->get('id')]); $this->setUser($u1); $rc2 = $ccg->create_related_competency(['competencyid' => $c3a->get('id'), 'relatedcompetencyid' => $c3b->get('id')]); $rc3 = $ccg->create_related_competency(['competencyid' => $c3a->get('id'), 'relatedcompetencyid' => $c3c->get('id')]); $this->setAdminUser(); provider::export_user_data(new approved_contextlist($u1, 'core_competency', [$sysctx->id, $cat1ctx->id, $cat2ctx->id])); // Check frameworks for u1 in system. $data = writer::with_context($sysctx)->get_related_data([get_string('competencies', 'core_competency')], 'frameworks'); $this->assertCount(2, $data->frameworks); $this->assertEquals($f1->get('shortname'), $data->frameworks[0]['name']); $this->assertEquals(transform::yesno(true), $data->frameworks[0]['created_or_modified_by_you']); $this->assertEquals($f1bis->get('shortname'), $data->frameworks[1]['name']); $this->assertEquals(transform::yesno(true), $data->frameworks[1]['created_or_modified_by_you']); $this->assertEmpty($data->frameworks[0]['competencies']); $this->assertEmpty($data->frameworks[1]['competencies']); // Check templates for u1 in system. $data = writer::with_context($sysctx)->get_related_data([get_string('competencies', 'core_competency')], 'templates'); $this->assertCount(1, $data->templates); $this->assertEquals($t1->get('shortname'), $data->templates[0]['name']); $this->assertEquals(transform::yesno(true), $data->templates[0]['created_or_modified_by_you']); $this->assertEmpty($data->templates[0]['competencies']); $this->assertEmpty($data->templates[0]['cohorts']); // Check frameworks for u1 in cat1. $data = writer::with_context($cat1ctx)->get_related_data([get_string('competencies', 'core_competency')], 'frameworks'); $this->assertCount(1, $data->frameworks); $this->assertEquals($f2->get('shortname'), $data->frameworks[0]['name']); $this->assertEquals(transform::yesno(true), $data->frameworks[0]['created_or_modified_by_you']); $this->assertCount(2, $data->frameworks[0]['competencies']); $this->assertEquals($c2a->get('shortname'), $data->frameworks[0]['competencies'][0]['name']); $this->assertEquals(transform::yesno(true), $data->frameworks[0]['competencies'][0]['created_or_modified_by_you']); $this->assertEquals($c2b->get('shortname'), $data->frameworks[0]['competencies'][1]['name']); $this->assertEquals(transform::yesno(true), $data->frameworks[0]['competencies'][1]['created_or_modified_by_you']); // Check templates for u1 in cat1. $data = writer::with_context($cat1ctx)->get_related_data([get_string('competencies', 'core_competency')], 'templates'); $this->assertCount(1, $data->templates); $this->assertEquals($t2->get('shortname'), $data->templates[0]['name']); $this->assertEquals(transform::yesno(true), $data->templates[0]['created_or_modified_by_you']); $this->assertCount(1, $data->templates[0]['competencies']); $this->assertEquals($c2a->get('shortname'), $data->templates[0]['competencies'][0]['name']); $this->assertEquals(transform::yesno(true), $data->templates[0]['competencies'][0]['created_or_modified_by_you']); $this->assertCount(1, $data->templates[0]['cohorts']); $this->assertEquals($c1->name, $data->templates[0]['cohorts'][0]['name']); $this->assertEquals(transform::yesno(true), $data->templates[0]['cohorts'][0]['created_or_modified_by_you']); // Check frameworks for u1 in cat2. $data = writer::with_context($cat2ctx)->get_related_data([get_string('competencies', 'core_competency')], 'frameworks'); $this->assertCount(1, $data->frameworks); $this->assertEquals($f3->get('shortname'), $data->frameworks[0]['name']); $this->assertEquals(transform::yesno(false), $data->frameworks[0]['created_or_modified_by_you']); $this->assertCount(3, $data->frameworks[0]['competencies']); $competency = $data->frameworks[0]['competencies'][0]; $this->assertEquals($c3a->get('shortname'), $competency['name']); $this->assertEquals(transform::yesno(false), $competency['created_or_modified_by_you']); $this->assertCount(2, $competency['related_competencies']); $this->assertEquals($c3b->get('shortname'), $competency['related_competencies'][0]['name']); $this->assertEquals(transform::yesno(true), $competency['related_competencies'][0]['created_or_modified_by_you']); $this->assertEquals($c3c->get('shortname'), $competency['related_competencies'][1]['name']); $this->assertEquals(transform::yesno(true), $competency['related_competencies'][1]['created_or_modified_by_you']); $competency = $data->frameworks[0]['competencies'][1]; $this->assertEquals($c3b->get('shortname'), $competency['name']); $this->assertCount(1, $competency['related_competencies']); $competency = $data->frameworks[0]['competencies'][2]; $this->assertEquals($c3c->get('shortname'), $competency['name']); $this->assertCount(1, $competency['related_competencies']); // Check templates for u1 in cat2. $data = writer::with_context($cat2ctx)->get_related_data([get_string('competencies', 'core_competency')], 'templates'); $this->assertEmpty($data->templates); provider::export_user_data(new approved_contextlist($u2, 'core_competency', [$sysctx->id, $cat1ctx->id, $cat2ctx->id])); // Check frameworks for u2 in system. $data = writer::with_context($sysctx)->get_related_data([get_string('competencies', 'core_competency')], 'frameworks'); $this->assertCount(1, $data->frameworks); $this->assertEquals($f1->get('shortname'), $data->frameworks[0]['name']); $this->assertEquals(transform::yesno(false), $data->frameworks[0]['created_or_modified_by_you']); $this->assertCount(2, $data->frameworks[0]['competencies']); $competency = $data->frameworks[0]['competencies'][0]; $this->assertEquals($c1a->get('shortname'), $competency['name']); $this->assertEquals(transform::yesno(true), $competency['created_or_modified_by_you']); $competency = $data->frameworks[0]['competencies'][1]; $this->assertEquals($c1b->get('shortname'), $competency['name']); $this->assertEquals(transform::yesno(true), $competency['created_or_modified_by_you']); // Check templates for u2 in system. $data = writer::with_context($sysctx)->get_related_data([get_string('competencies', 'core_competency')], 'templates'); $this->assertCount(1, $data->templates); $this->assertEquals($t1->get('shortname'), $data->templates[0]['name']); $this->assertEquals(transform::yesno(false), $data->templates[0]['created_or_modified_by_you']); $this->assertCount(2, $data->templates[0]['competencies']); $competency = $data->templates[0]['competencies'][0]; $this->assertEquals($c1a->get('shortname'), $competency['name']); $this->assertEquals(transform::yesno(true), $competency['created_or_modified_by_you']); $competency = $data->templates[0]['competencies'][1]; $this->assertEquals($c2a->get('shortname'), $competency['name']); $this->assertEquals(transform::yesno(true), $competency['created_or_modified_by_you']); $this->assertCount(1, $data->templates[0]['cohorts']); $this->assertEquals($c2->name, $data->templates[0]['cohorts'][0]['name']); $this->assertEquals(transform::yesno(true), $data->templates[0]['cohorts'][0]['created_or_modified_by_you']); // Check frameworks for u2 in cat1. $data = writer::with_context($cat1ctx)->get_related_data([get_string('competencies', 'core_competency')], 'frameworks'); $this->assertCount(1, $data->frameworks); $this->assertEquals(transform::yesno(false), $data->frameworks[0]['created_or_modified_by_you']); $this->assertCount(2, $data->frameworks[0]['competencies']); $competency = $data->frameworks[0]['competencies'][0]; $this->assertEquals($c2a->get('shortname'), $competency['name']); $this->assertEquals(transform::yesno(false), $competency['created_or_modified_by_you']); $this->assertCount(1, $competency['related_competencies']); $this->assertEquals($c2b->get('shortname'), $competency['related_competencies'][0]['name']); $this->assertEquals(transform::yesno(true), $competency['related_competencies'][0]['created_or_modified_by_you']); // Check templates for u2 in system. $data = writer::with_context($cat2ctx)->get_related_data([get_string('competencies', 'core_competency')], 'templates'); $this->assertCount(1, $data->templates); $this->assertEquals($t3->get('shortname'), $data->templates[0]['name']); $this->assertEquals(transform::yesno(true), $data->templates[0]['created_or_modified_by_you']); $this->assertCount(1, $data->templates[0]['competencies']); $competency = $data->templates[0]['competencies'][0]; $this->assertEquals($c3a->get('shortname'), $competency['name']); $this->assertEquals(transform::yesno(true), $competency['created_or_modified_by_you']); } public function test_export_data_for_user_with_related_learning_plans(): void { global $DB; $path = [ get_string('competencies', 'core_competency'), get_string('privacy:path:relatedtome', 'core_competency'), get_string('privacy:path:plans', 'core_competency'), ]; $yes = transform::yesno(true); $no = transform::yesno(false); $dg = $this->getDataGenerator(); $ccg = $dg->get_plugin_generator('core_competency'); $u0 = $dg->create_user(); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $u3 = $dg->create_user(); $u4 = $dg->create_user(); $u5 = $dg->create_user(); $u6 = $dg->create_user(); $u7 = $dg->create_user(); $u8 = $dg->create_user(); $dg->role_assign($DB->get_field('role', 'id', ['archetype' => 'manager'], IGNORE_MULTIPLE), $u6->id); $u0ctx = \context_user::instance($u0->id); $f = $ccg->create_framework(); $comp1 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $comp2 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $comp3 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $comp4 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $t = $ccg->create_template(); $tc1 = $ccg->create_template_competency(['competencyid' => $comp1->get('id'), 'templateid' => $t->get('id')]); $tc2 = $ccg->create_template_competency(['competencyid' => $comp2->get('id'), 'templateid' => $t->get('id')]); $tc3 = $ccg->create_template_competency(['competencyid' => $comp3->get('id'), 'templateid' => $t->get('id')]); $tc4 = $ccg->create_template_competency(['competencyid' => $comp4->get('id'), 'templateid' => $t->get('id')]); $this->setUser($u1); $p1 = $ccg->create_plan(['templateid' => $t->get('id'), 'userid' => $u0->id]); $this->setUser($u2); $p2 = $ccg->create_plan(['userid' => $u0->id, 'reviewerid' => $u7->id]); $this->setUser($u3); $p1c1 = $ccg->create_plan_competency(['planid' => $p1->get('id'), 'competencyid' => $comp1->get('id')]); $p2c2 = $ccg->create_plan_competency(['planid' => $p2->get('id'), 'competencyid' => $comp2->get('id')]); $p2c3 = $ccg->create_plan_competency(['planid' => $p2->get('id'), 'competencyid' => $comp3->get('id')]); $this->setUser($u4); $uc1 = $ccg->create_user_competency(['competencyid' => $comp1->get('id'), 'userid' => $u0->id, 'grade' => 1, 'proficiency' => true]); $uc2 = $ccg->create_user_competency(['competencyid' => $comp2->get('id'), 'userid' => $u0->id, 'grade' => 2, 'proficiency' => false]); $uc3 = $ccg->create_user_competency(['competencyid' => $comp3->get('id'), 'userid' => $u0->id]); $uc4 = $ccg->create_user_competency(['competencyid' => $comp4->get('id'), 'userid' => $u0->id, 'reviewerid' => $u5->id]); $this->setUser($u5); $p3 = $ccg->create_plan(['userid' => $u0->id]); $p3c1 = $ccg->create_plan_competency(['planid' => $p3->get('id'), 'competencyid' => $comp1->get('id')]); $p3c3 = $ccg->create_plan_competency(['planid' => $p3->get('id'), 'competencyid' => $comp3->get('id')]); // Add comments on plan. $this->allow_anyone_to_comment_anywhere(); $this->setUser($u0); $p1->get_comment_object()->add('Hello.'); $this->setUser($u8); $p1->get_comment_object()->add('Hi.'); // Export data for user 1. writer::reset(); provider::export_user_data(new approved_contextlist($u1, 'core_competency', [$u0ctx->id])); $planpath = array_merge($path, ["{$p1->get('name')} ({$p1->get('id')})"]); $data = writer::with_context($u0ctx)->get_data($planpath); $this->assertEquals($p1->get('name'), $data->name); $this->assertEquals($yes, $data->created_or_modified_by_you); // Export data for user 2. writer::reset(); provider::export_user_data(new approved_contextlist($u2, 'core_competency', [$u0ctx->id])); $planpath = array_merge($path, ["{$p2->get('name')} ({$p2->get('id')})"]); $data = writer::with_context($u0ctx)->get_data($planpath); $this->assertEquals($p2->get('name'), $data->name); $this->assertEquals($yes, $data->created_or_modified_by_you); // Export data for user 3. writer::reset(); provider::export_user_data(new approved_contextlist($u3, 'core_competency', [$u0ctx->id])); $planpath = array_merge($path, ["{$p1->get('name')} ({$p1->get('id')})"]); $data = writer::with_context($u0ctx)->get_data($planpath); $this->assertEquals($p1->get('name'), $data->name); $this->assertEquals($no, $data->created_or_modified_by_you); $this->assertCount(1, $data->competencies); $this->assertEquals($comp1->get('shortname'), $data->competencies[0]['name']); $this->assertEquals($yes, $data->competencies[0]['created_or_modified_by_you']); $planpath = array_merge($path, ["{$p2->get('name')} ({$p2->get('id')})"]); $data = writer::with_context($u0ctx)->get_data($planpath); $this->assertEquals($p2->get('name'), $data->name); $this->assertEquals($no, $data->created_or_modified_by_you); $competencies = $data->competencies; $this->assertCount(2, $competencies); $this->assertEquals($comp2->get('shortname'), $competencies[0]['name']); $this->assertEquals($yes, $competencies[0]['created_or_modified_by_you']); $this->assertEquals($comp3->get('shortname'), $competencies[1]['name']); $this->assertEquals($yes, $competencies[1]['created_or_modified_by_you']); // Export data for user 4. writer::reset(); provider::export_user_data(new approved_contextlist($u4, 'core_competency', [$u0ctx->id])); foreach ([$p1, $p2, $p3] as $plan) { $planpath = array_merge($path, ["{$p2->get('name')} ({$p2->get('id')})"]); $data = writer::with_context($u0ctx)->get_data($planpath); $this->assertEmpty($data); } // Export data for user 5. writer::reset(); provider::export_user_data(new approved_contextlist($u5, 'core_competency', [$u0ctx->id])); $planpath = array_merge($path, ["{$p3->get('name')} ({$p3->get('id')})"]); $data = writer::with_context($u0ctx)->get_data($planpath); $this->assertEquals($p3->get('name'), $data->name); $this->assertEquals($yes, $data->created_or_modified_by_you); $this->assertCount(2, $data->competencies); $competency = $data->competencies[0]; $this->assertEquals($comp1->get('shortname'), $competency['name']); $this->assertEquals($yes, $competency['created_or_modified_by_you']); $competency = $data->competencies[1]; $this->assertEquals($comp3->get('shortname'), $competency['name']); $this->assertEquals($yes, $competency['created_or_modified_by_you']); // Do some stuff. $this->setUser($u6); api::complete_plan($p3); // Export data for user 6. writer::reset(); provider::export_user_data(new approved_contextlist($u6, 'core_competency', [$u0ctx->id])); $planpath = array_merge($path, ["{$p3->get('name')} ({$p3->get('id')})"]); $data = writer::with_context($u0ctx)->get_data($planpath); $this->assertEquals($p3->get('name'), $data->name); $this->assertEquals($yes, $data->created_or_modified_by_you); $this->assertCount(2, $data->competencies); $competency = $data->competencies[0]; $this->assertEquals($comp1->get('shortname'), $competency['name']); $this->assertArrayNotHasKey('created_or_modified_by_you', $competency); $this->assertEquals('A', $competency['rating']['rating']); $this->assertEquals($yes, $competency['rating']['created_or_modified_by_you']); $competency = $data->competencies[1]; $this->assertEquals($comp3->get('shortname'), $competency['name']); $this->assertArrayNotHasKey('created_or_modified_by_you', $competency); $this->assertEquals('-', $competency['rating']['rating']); $this->assertEquals($yes, $competency['rating']['created_or_modified_by_you']); // Export data for user 7. writer::reset(); provider::export_user_data(new approved_contextlist($u7, 'core_competency', [$u0ctx->id])); $planpath = array_merge($path, ["{$p2->get('name')} ({$p2->get('id')})"]); $data = writer::with_context($u0ctx)->get_data($planpath); $this->assertEquals($p2->get('name'), $data->name); $this->assertEquals($no, $data->created_or_modified_by_you); $this->assertEquals($yes, $data->reviewer_is_you); // Export data for user 8. writer::reset(); $this->setUser($u8); provider::export_user_data(new approved_contextlist($u8, 'core_competency', [$u0ctx->id])); $planpath = array_merge($path, ["{$p1->get('name')} ({$p1->get('id')})"]); $data = writer::with_context($u0ctx)->get_data($planpath); $this->assertEquals($p1->get('name'), $data->name); $this->assertEquals($no, $data->created_or_modified_by_you); $this->assertEquals($no, $data->reviewer_is_you); $commentspath = array_merge($planpath, [get_string('commentsubcontext', 'core_comment')]); $data = writer::with_context($u0ctx)->get_data($commentspath); $this->assert_exported_comments(['Hi.'], $data->comments); } public function test_export_data_for_user_with_related_competencies(): void { $path = [ get_string('competencies', 'core_competency'), get_string('privacy:path:relatedtome', 'core_competency'), get_string('competencies', 'core_competency'), ]; $yes = transform::yesno(true); $no = transform::yesno(false); $makecomppath = function($comp) use ($path) { return array_merge($path, ["{$comp->get('shortname')} ({$comp->get('id')})"]); }; $dg = $this->getDataGenerator(); $ccg = $dg->get_plugin_generator('core_competency'); $u0 = $dg->create_user(); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $u3 = $dg->create_user(); $u4 = $dg->create_user(); $u5 = $dg->create_user(); $u0ctx = \context_user::instance($u0->id); $f = $ccg->create_framework(); $comp1 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $comp2 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $comp3 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $comp4 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $this->setUser($u1); api::add_evidence($u0->id, $comp1->get('id'), $u0ctx, \core_competency\evidence::ACTION_LOG, 'privacy:metadata:competency_evidence', 'core_competency'); api::add_evidence($u0->id, $comp1->get('id'), $u0ctx, \core_competency\evidence::ACTION_LOG, 'privacy:metadata:competency_evidence', 'core_competency'); api::add_evidence($u0->id, $comp2->get('id'), $u0ctx, \core_competency\evidence::ACTION_LOG, 'privacy:metadata:competency_evidence', 'core_competency'); $this->setUser($u2); api::add_evidence($u0->id, $comp1->get('id'), $u0ctx, \core_competency\evidence::ACTION_COMPLETE, 'privacy:metadata:competency_evidence', 'core_competency', null, false, null, null, $u3->id); $this->setUser($u3); api::add_evidence($u0->id, $comp2->get('id'), $u0ctx, \core_competency\evidence::ACTION_OVERRIDE, 'privacy:metadata:competency_evidence', 'core_competency', null, false, null, 1, $u4->id, 'Ze note'); $this->setUser($u4); $uc3 = $ccg->create_user_competency(['userid' => $u0->id, 'competencyid' => $comp3->get('id')]); $uc4 = $ccg->create_user_competency(['userid' => $u0->id, 'competencyid' => $comp4->get('id'), 'reviewerid' => $u2->id]); $this->allow_anyone_to_comment_anywhere(); $this->setUser($u0); $uc3->get_comment_object()->add('...'); $this->setUser($u5); $uc3->get_comment_object()->add('Hello!'); $uc3->get_comment_object()->add('It\'s me...'); // Export data for user 1. writer::reset(); provider::export_user_data(new approved_contextlist($u1, 'core_competency', [$u0ctx->id])); $data = writer::with_context($u0ctx)->get_data($makecomppath($comp1)); $competency = (array) $data; $this->assertEquals($comp1->get('shortname'), $competency['name']); $evidence = $competency['evidence']; $this->assertCount(2, $evidence); $this->assertEquals(get_string('privacy:evidence:action:log', 'core_competency'), $evidence[0]['action']); $this->assertEquals('-', $evidence[0]['actionuserid']); $this->assertEquals($no, $evidence[0]['acting_user_is_you']); $this->assertEquals($yes, $evidence[0]['created_or_modified_by_you']); $this->assertEquals(get_string('privacy:evidence:action:log', 'core_competency'), $evidence[1]['action']); $this->assertEquals('-', $evidence[1]['actionuserid']); $this->assertEquals($no, $evidence[1]['acting_user_is_you']); $this->assertEquals($yes, $evidence[1]['created_or_modified_by_you']); $data = writer::with_context($u0ctx)->get_data($makecomppath($comp2)); $competency = (array) $data; $this->assertEquals($comp2->get('shortname'), $competency['name']); $evidence = $competency['evidence']; $this->assertCount(1, $evidence); $this->assertEquals(get_string('privacy:evidence:action:log', 'core_competency'), $evidence[0]['action']); $this->assertEquals('-', $evidence[0]['actionuserid']); $this->assertEquals($no, $evidence[0]['acting_user_is_you']); $this->assertEquals($yes, $evidence[0]['created_or_modified_by_you']); // Export data for user 2. writer::reset(); provider::export_user_data(new approved_contextlist($u2, 'core_competency', [$u0ctx->id])); $data = writer::with_context($u0ctx)->get_data($makecomppath($comp1)); $competency = (array) $data; $this->assertEquals($comp1->get('shortname'), $competency['name']); $evidence = $competency['evidence']; $this->assertCount(1, $evidence); $this->assertEquals(get_string('privacy:evidence:action:complete', 'core_competency'), $evidence[0]['action']); $this->assertEquals($u3->id, $evidence[0]['actionuserid']); $this->assertEquals($no, $evidence[0]['acting_user_is_you']); $this->assertEquals($yes, $evidence[0]['created_or_modified_by_you']); $data = writer::with_context($u0ctx)->get_data($makecomppath($comp4)); $competency = (array) $data; $this->assertEquals($comp4->get('shortname'), $competency['name']); $this->assertCount(0, $competency['evidence']); $this->assertEquals($yes, $competency['rating']['reviewer_is_you']); $this->assertEquals($no, $competency['rating']['created_or_modified_by_you']); // Export data for user 3. writer::reset(); provider::export_user_data(new approved_contextlist($u3, 'core_competency', [$u0ctx->id])); $data = writer::with_context($u0ctx)->get_data($makecomppath($comp1)); $competency = (array) $data; $this->assertEquals($comp1->get('shortname'), $competency['name']); $evidence = $competency['evidence']; $this->assertCount(1, $evidence); $this->assertEquals($u3->id, $evidence[0]['actionuserid']); $this->assertEquals($yes, $evidence[0]['acting_user_is_you']); $this->assertEquals($no, $evidence[0]['created_or_modified_by_you']); $data = writer::with_context($u0ctx)->get_data($makecomppath($comp2)); $competency = (array) $data; $this->assertEquals($comp2->get('shortname'), $competency['name']); $evidence = $competency['evidence']; $this->assertCount(1, $evidence); $this->assertEquals(get_string('privacy:evidence:action:override', 'core_competency'), $evidence[0]['action']); $this->assertEquals($u4->id, $evidence[0]['actionuserid']); $this->assertEquals($no, $evidence[0]['acting_user_is_you']); $this->assertEquals($yes, $evidence[0]['created_or_modified_by_you']); // Export data for user 4. writer::reset(); provider::export_user_data(new approved_contextlist($u4, 'core_competency', [$u0ctx->id])); $data = writer::with_context($u0ctx)->get_data($makecomppath($comp2)); $competency = (array) $data; $this->assertEquals($comp2->get('shortname'), $competency['name']); $this->assertNull($competency['rating']); $this->assertCount(1, $competency['evidence']); $evidence = $competency['evidence'][0]; $this->assertEquals($u4->id, $evidence['actionuserid']); $this->assertEquals($yes, $evidence['acting_user_is_you']); $this->assertEquals($no, $evidence['created_or_modified_by_you']); $data = writer::with_context($u0ctx)->get_data($makecomppath($comp3)); $competency = (array) $data; $this->assertEquals($comp3->get('shortname'), $competency['name']); $this->assertEquals($no, $competency['rating']['reviewer_is_you']); $this->assertEquals($yes, $competency['rating']['created_or_modified_by_you']); $this->assertEmpty($competency['evidence']); $data = writer::with_context($u0ctx)->get_data($makecomppath($comp4)); $competency = (array) $data; $this->assertEquals($comp4->get('shortname'), $competency['name']); $this->assertEquals($no, $competency['rating']['reviewer_is_you']); $this->assertEquals($yes, $competency['rating']['created_or_modified_by_you']); $this->assertEmpty($competency['evidence']); // Export data for user 5. $this->setUser($u5); writer::reset(); provider::export_user_data(new approved_contextlist($u5, 'core_competency', [$u0ctx->id])); $data = writer::with_context($u0ctx)->get_data($makecomppath($comp3)); $competency = (array) $data; $this->assertEquals($comp3->get('shortname'), $competency['name']); $data = writer::with_context($u0ctx)->get_data(array_merge($makecomppath($comp3), [get_string('commentsubcontext', 'core_comment')])); $this->assert_exported_comments(['Hello!', 'It\'s me...'], $data->comments); } public function test_export_data_for_user_with_related_user_evidence(): void { $path = [ get_string('competencies', 'core_competency'), get_string('privacy:path:relatedtome', 'core_competency'), get_string('privacy:path:userevidence', 'core_competency') ]; $yes = transform::yesno(true); $no = transform::yesno(false); $dg = $this->getDataGenerator(); $ccg = $dg->get_plugin_generator('core_competency'); $u0 = $dg->create_user(); $u0b = $dg->create_user(); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $u3 = $dg->create_user(); $u4 = $dg->create_user(); $u0ctx = \context_user::instance($u0->id); $f = $ccg->create_framework(); $comp1 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $comp2 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $comp3 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $this->setUser($u0); $ue0 = $ccg->create_user_evidence(['userid' => $u0->id]); $this->setUser($u1); $ue1 = $ccg->create_user_evidence(['userid' => $u0->id]); $ue1b = $ccg->create_user_evidence(['userid' => $u0b->id]); $this->setUser($u2); $ccg->create_user_evidence_competency(['userevidenceid' => $ue1->get('id'), 'competencyid' => $comp1->get('id')]); $ccg->create_user_evidence_competency(['userevidenceid' => $ue1b->get('id'), 'competencyid' => $comp1->get('id')]); $ue2 = $ccg->create_user_evidence(['userid' => $u0->id]); $ue2b = $ccg->create_user_evidence(['userid' => $u0b->id]); $this->setUser($u3); $ccg->create_user_evidence_competency(['userevidenceid' => $ue2->get('id'), 'competencyid' => $comp2->get('id')]); $ccg->create_user_evidence_competency(['userevidenceid' => $ue2->get('id'), 'competencyid' => $comp3->get('id')]); // Export for user 1. provider::export_user_data(new approved_contextlist($u1, 'core_competency', [$u0ctx->id])); $uepath = array_merge($path, ["{$ue1->get('name')} ({$ue1->get('id')})"]); $data = writer::with_context($u0ctx)->get_data($uepath); $this->assertEquals($ue1->get('name'), $data->name); $this->assertEquals($yes, $data->created_or_modified_by_you); $this->assertEmpty($data->competencies); // Export for user 2. provider::export_user_data(new approved_contextlist($u2, 'core_competency', [$u0ctx->id])); $uepath = array_merge($path, ["{$ue1->get('name')} ({$ue1->get('id')})"]); $data = writer::with_context($u0ctx)->get_data($uepath); $this->assertEquals($ue1->get('name'), $data->name); $this->assertEquals($no, $data->created_or_modified_by_you); $this->assertCount(1, $data->competencies); $competency = $data->competencies[0]; $this->assertEquals($comp1->get('shortname'), $competency['name']); $this->assertEquals($yes, $competency['created_or_modified_by_you']); $uepath = array_merge($path, ["{$ue2->get('name')} ({$ue2->get('id')})"]); $data = writer::with_context($u0ctx)->get_data($uepath); $this->assertEquals($ue2->get('name'), $data->name); $this->assertEquals($yes, $data->created_or_modified_by_you); $this->assertEmpty($data->competencies); // Export for user 3. provider::export_user_data(new approved_contextlist($u3, 'core_competency', [$u0ctx->id])); $uepath = array_merge($path, ["{$ue2->get('name')} ({$ue2->get('id')})"]); $evidence = writer::with_context($u0ctx)->get_data($uepath); $this->assertEquals($ue2->get('name'), $evidence->name); $this->assertEquals($no, $evidence->created_or_modified_by_you); $this->assertCount(2, $evidence->competencies); $competency = $evidence->competencies[0]; $this->assertEquals($comp2->get('shortname'), $competency['name']); $this->assertEquals($yes, $competency['created_or_modified_by_you']); $competency = $evidence->competencies[1]; $this->assertEquals($comp3->get('shortname'), $competency['name']); $this->assertEquals($yes, $competency['created_or_modified_by_you']); } public function test_export_data_for_user_about_their_learning_plans(): void { $this->setAdminUser(); $dg = $this->getDataGenerator(); $ccg = $dg->get_plugin_generator('core_competency'); $path = [get_string('competencies', 'core_competency'), get_string('privacy:path:plans', 'core_competency')]; $yes = transform::yesno(true); $no = transform::yesno(false); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $u3 = $dg->create_user(); $u1ctx = \context_user::instance($u1->id); $u2ctx = \context_user::instance($u2->id); $f = $ccg->create_framework(); $comp1 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $comp2 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $comp3 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $comp4 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $t = $ccg->create_template(); $tc2 = $ccg->create_template_competency(['competencyid' => $comp2->get('id'), 'templateid' => $t->get('id')]); $tc3 = $ccg->create_template_competency(['competencyid' => $comp3->get('id'), 'templateid' => $t->get('id')]); $tc4 = $ccg->create_template_competency(['competencyid' => $comp4->get('id'), 'templateid' => $t->get('id')]); $p1a = $ccg->create_plan(['userid' => $u1->id, 'templateid' => $t->get('id'), 'status' => \core_competency\plan::STATUS_WAITING_FOR_REVIEW]); $p1b = $ccg->create_plan(['userid' => $u1->id]); $ccg->create_plan_competency(['planid' => $p1b->get('id'), 'competencyid' => $comp1->get('id')]); $ccg->create_plan_competency(['planid' => $p1b->get('id'), 'competencyid' => $comp2->get('id')]); $ccg->create_plan_competency(['planid' => $p1b->get('id'), 'competencyid' => $comp4->get('id')]); $p1c = $ccg->create_plan(['userid' => $u1->id]); $ccg->create_plan_competency(['planid' => $p1c->get('id'), 'competencyid' => $comp1->get('id')]); $ccg->create_plan_competency(['planid' => $p1c->get('id'), 'competencyid' => $comp3->get('id')]); $ccg->create_plan_competency(['planid' => $p1c->get('id'), 'competencyid' => $comp4->get('id')]); $p1d = $ccg->create_plan(['userid' => $u1->id]); $p2a = $ccg->create_plan(['userid' => $u2->id]); $ccg->create_plan_competency(['planid' => $p2a->get('id'), 'competencyid' => $comp1->get('id')]); $ccg->create_plan_competency(['planid' => $p2a->get('id'), 'competencyid' => $comp2->get('id')]); $uc1a = $ccg->create_user_competency(['competencyid' => $comp1->get('id'), 'userid' => $u1->id, 'grade' => 2, 'proficiency' => false]); $uc1b = $ccg->create_user_competency(['competencyid' => $comp2->get('id'), 'userid' => $u1->id, 'grade' => 3, 'proficiency' => false]); $uc1c = $ccg->create_user_competency(['competencyid' => $comp3->get('id'), 'userid' => $u1->id]); // Add comments on plan. $this->allow_anyone_to_comment_anywhere(); $this->setUser($u1); $p1a->get_comment_object()->add('Hello.'); $p1a->get_comment_object()->add('It\'s me.'); $this->setUser($u3); $p1a->get_comment_object()->add('After all these years...'); // Complete the plan to create archiving, and modify the user competency again. api::complete_plan($p1c); $uc1a->set('grade', 1); $uc1a->set('proficiency', true); $uc1a->update(); // Export user data in both contexts. provider::export_user_data(new approved_contextlist($u1, 'core_competency', [$u1ctx->id, $u2ctx->id])); // This plan is based off a template. $data = writer::with_context($u1ctx)->get_data(array_merge($path, ["{$p1a->get('name')} ({$p1a->get('id')})"])); $this->assertNotEmpty($data); $this->assertEquals($p1a->get('name'), $data->name); $this->assertEquals($p1a->get_statusname(), $data->status); $this->assertCount(3, $data->competencies); $comp = $data->competencies[0]; $this->assertEquals($comp2->get('shortname'), $comp['name']); $this->assertEquals('C', $comp['rating']['rating']); $comp = $data->competencies[1]; $this->assertEquals($comp3->get('shortname'), $comp['name']); $this->assertEquals('-', $comp['rating']['rating']); $comp = $data->competencies[2]; $this->assertEquals($comp4->get('shortname'), $comp['name']); $this->assertNull($comp['rating']); $data = writer::with_context($u1ctx)->get_data(array_merge($path, ["{$p1a->get('name')} ({$p1a->get('id')})", get_string('commentsubcontext', 'core_comment')])); $this->assert_exported_comments(['Hello.', 'It\'s me.', 'After all these years...'], $data->comments); // This plan is manually created. $data = writer::with_context($u1ctx)->get_data(array_merge($path, ["{$p1b->get('name')} ({$p1b->get('id')})"])); $this->assertNotEmpty($data); $this->assertEquals($p1b->get('name'), $data->name); $this->assertCount(3, $data->competencies); $comp = $data->competencies[0]; $this->assertEquals($comp1->get('shortname'), $comp['name']); $this->assertEquals('A', $comp['rating']['rating']); $comp = $data->competencies[1]; $this->assertEquals($comp2->get('shortname'), $comp['name']); $this->assertEquals('C', $comp['rating']['rating']); $comp = $data->competencies[2]; $this->assertEquals($comp4->get('shortname'), $comp['name']); $this->assertNull($comp['rating']); // This plan is complete. $data = writer::with_context($u1ctx)->get_data(array_merge($path, ["{$p1c->get('name')} ({$p1c->get('id')})"])); $this->assertNotEmpty($data); $this->assertEquals($p1c->get('name'), $data->name); $this->assertCount(3, $data->competencies); $comp = $data->competencies[0]; $this->assertEquals($comp1->get('shortname'), $comp['name']); $this->assertEquals('B', $comp['rating']['rating']); $comp = $data->competencies[1]; $this->assertEquals($comp3->get('shortname'), $comp['name']); $this->assertEquals('-', $comp['rating']['rating']); $comp = $data->competencies[2]; $this->assertEquals($comp4->get('shortname'), $comp['name']); $this->assertEquals('-', $comp['rating']['rating']); // This plan is empty. $data = writer::with_context($u1ctx)->get_data(array_merge($path, ["{$p1d->get('name')} ({$p1d->get('id')})"])); $this->assertNotEmpty($data); $this->assertEquals($p1d->get('name'), $data->name); $this->assertEquals($p1d->get_statusname(), $data->status); $this->assertEmpty($data->competencies); // Confirm that we do not get export what we shouldn't. $data = writer::with_context($u1ctx)->get_data(array_merge($path, ["{$p2a->get('name')} ({$p2a->get('id')})"])); $this->assertEmpty($data); $data = writer::with_context($u2ctx)->get_data(array_merge($path, ["{$p1a->get('name')} ({$p1a->get('id')})"])); $this->assertEmpty($data); $data = writer::with_context($u2ctx)->get_data(array_merge($path, ["{$p1b->get('name')} ({$p1b->get('id')})"])); $this->assertEmpty($data); $data = writer::with_context($u2ctx)->get_data(array_merge($path, ["{$p1c->get('name')} ({$p1c->get('id')})"])); $this->assertEmpty($data); $data = writer::with_context($u2ctx)->get_data(array_merge($path, ["{$p2a->get('name')} ({$p2a->get('id')})"])); $this->assertEmpty($data); // Export for user 2. writer::reset(); provider::export_user_data(new approved_contextlist($u2, 'core_competency', [$u1ctx->id, $u2ctx->id])); // Validate the basic plan. $data = writer::with_context($u2ctx)->get_data(array_merge($path, ["{$p2a->get('name')} ({$p2a->get('id')})"])); $this->assertNotEmpty($data); $this->assertEquals($p2a->get('name'), $data->name); $this->assertCount(2, $data->competencies); $comp = $data->competencies[0]; $this->assertEquals($comp1->get('shortname'), $comp['name']); $this->assertNull($comp['rating']); $comp = $data->competencies[1]; $this->assertEquals($comp2->get('shortname'), $comp['name']); $this->assertNull($comp['rating']); // Confirm that we do not get export what we shouldn't. $data = writer::with_context($u2ctx)->get_data(array_merge($path, ["{$p1a->get('name')} ({$p1a->get('id')})"])); $this->assertEmpty($data); $data = writer::with_context($u2ctx)->get_data(array_merge($path, ["{$p1b->get('name')} ({$p1b->get('id')})"])); $this->assertEmpty($data); $data = writer::with_context($u2ctx)->get_data(array_merge($path, ["{$p1c->get('name')} ({$p1c->get('id')})"])); $this->assertEmpty($data); $data = writer::with_context($u1ctx)->get_data(array_merge($path, ["{$p1a->get('name')} ({$p1a->get('id')})"])); $this->assertEmpty($data); $data = writer::with_context($u1ctx)->get_data(array_merge($path, ["{$p1b->get('name')} ({$p1b->get('id')})"])); $this->assertEmpty($data); $data = writer::with_context($u1ctx)->get_data(array_merge($path, ["{$p1c->get('name')} ({$p1c->get('id')})"])); $this->assertEmpty($data); } public function test_export_data_for_user_about_their_competencies(): void { $dg = $this->getDataGenerator(); $ccg = $dg->get_plugin_generator('core_competency'); $path = [get_string('competencies', 'core_competency'), get_string('competencies', 'core_competency')]; $no = transform::yesno(false); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $u3 = $dg->create_user(); $u1ctx = \context_user::instance($u1->id); $u2ctx = \context_user::instance($u2->id); $f = $ccg->create_framework(); $comp1 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $comp2 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $comp3 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $uc1a = $ccg->create_user_competency(['userid' => $u1->id, 'competencyid' => $comp1->get('id')]); $uc1b = $ccg->create_user_competency(['userid' => $u1->id, 'competencyid' => $comp2->get('id'), 'grade' => 2, 'proficiency' => false]); $uc1c = $ccg->create_user_competency(['userid' => $u1->id, 'competencyid' => $comp3->get('id')]); $e1a1 = $ccg->create_evidence(['usercompetencyid' => $uc1a->get('id'), 'action' => \core_competency\evidence::ACTION_COMPLETE, 'grade' => 1]); $e1a2 = $ccg->create_evidence(['usercompetencyid' => $uc1a->get('id'), 'note' => 'Not too bad']); $e1b1 = $ccg->create_evidence(['usercompetencyid' => $uc1b->get('id'), 'url' => 'https://example.com']); $uc2a = $ccg->create_user_competency(['userid' => $u2->id, 'competencyid' => $comp1->get('id')]); $uc2b = $ccg->create_user_competency(['userid' => $u2->id, 'competencyid' => $comp2->get('id')]); $e2a1 = $ccg->create_evidence(['usercompetencyid' => $uc2b->get('id'), 'note' => 'A']); $e2a2 = $ccg->create_evidence(['usercompetencyid' => $uc2b->get('id'), 'note' => 'B']); $e2a3 = $ccg->create_evidence(['usercompetencyid' => $uc2b->get('id'), 'note' => 'C']); // Add comments on competency. $this->allow_anyone_to_comment_anywhere(); $this->setUser($u1); $uc1a->get_comment_object()->add('Hello.'); $uc1a->get_comment_object()->add('It\'s me.'); $this->setUser($u3); $uc1a->get_comment_object()->add('After all these years...'); // Export for user 1 in both contexts. provider::export_user_data(new approved_contextlist($u1, 'core_competency', [$u1ctx->id, $u2ctx->id])); $data = writer::with_context($u1ctx)->get_data(array_merge($path, ["{$comp1->get('shortname')} ({$comp1->get('id')})"])); $this->assertNotEmpty($data); $this->assertEquals($comp1->get('shortname'), $data->name); $this->assertEquals('-', $data->rating['rating']); $this->assertCount(2, $data->evidence); $this->assertEquals(get_string('privacy:evidence:action:complete', 'core_competency'), $data->evidence[1]['action']); $this->assertEquals('Not too bad', $data->evidence[0]['note']); $data = writer::with_context($u1ctx)->get_data(array_merge($path, ["{$comp1->get('shortname')} ({$comp1->get('id')})", get_string('commentsubcontext', 'core_comment')])); $this->assert_exported_comments(['Hello.', 'It\'s me.', 'After all these years...'], $data->comments); $data = writer::with_context($u1ctx)->get_data(array_merge($path, ["{$comp2->get('shortname')} ({$comp2->get('id')})"])); $this->assertNotEmpty($data); $this->assertEquals($comp2->get('shortname'), $data->name); $this->assertEquals('B', $data->rating['rating']); $this->assertEquals($no, $data->rating['proficient']); $this->assertCount(1, $data->evidence); $this->assertEquals('https://example.com', $data->evidence[0]['url']); $data = writer::with_context($u1ctx)->get_data(array_merge($path, ["{$comp3->get('shortname')} ({$comp3->get('id')})"])); $this->assertNotEmpty($data); $this->assertEquals($comp3->get('shortname'), $data->name); $this->assertEquals('-', $data->rating['rating']); $this->assertEquals('-', $data->rating['proficient']); $this->assertEmpty($data->evidence); // We don't know anything about user 2. $data = writer::with_context($u2ctx)->get_data(array_merge($path, ["{$comp1->get('shortname')} ({$comp1->get('id')})"])); $this->assertEmpty($data); $data = writer::with_context($u2ctx)->get_data(array_merge($path, ["{$comp2->get('shortname')} ({$comp2->get('id')})"])); $this->assertEmpty($data); $data = writer::with_context($u2ctx)->get_data(array_merge($path, ["{$comp3->get('shortname')} ({$comp3->get('id')})"])); $this->assertEmpty($data); // Export for user 2 in both contexts. writer::reset(); provider::export_user_data(new approved_contextlist($u2, 'core_competency', [$u1ctx->id, $u2ctx->id])); $data = writer::with_context($u2ctx)->get_data(array_merge($path, ["{$comp1->get('shortname')} ({$comp1->get('id')})"])); $this->assertNotEmpty($data); $this->assertEquals($comp1->get('shortname'), $data->name); $this->assertEquals('-', $data->rating['rating']); $this->assertCount(0, $data->evidence); $data = writer::with_context($u2ctx)->get_data(array_merge($path, ["{$comp2->get('shortname')} ({$comp2->get('id')})"])); $this->assertNotEmpty($data); $this->assertEquals($comp2->get('shortname'), $data->name); $this->assertEquals('-', $data->rating['rating']); $this->assertCount(3, $data->evidence); $this->assertEquals('C', $data->evidence[0]['note']); $this->assertEquals('B', $data->evidence[1]['note']); $this->assertEquals('A', $data->evidence[2]['note']); $data = writer::with_context($u2ctx)->get_data(array_merge($path, ["{$comp3->get('shortname')} ({$comp3->get('id')})"])); $this->assertEmpty($data); // We don't know anything about user 1. $data = writer::with_context($u1ctx)->get_data(array_merge($path, ["{$comp1->get('shortname')} ({$comp1->get('id')})"])); $this->assertEmpty($data); $data = writer::with_context($u1ctx)->get_data(array_merge($path, ["{$comp2->get('shortname')} ({$comp2->get('id')})"])); $this->assertEmpty($data); $data = writer::with_context($u1ctx)->get_data(array_merge($path, ["{$comp3->get('shortname')} ({$comp3->get('id')})"])); $this->assertEmpty($data); } public function test_export_data_for_user_about_their_user_evidence(): void { $dg = $this->getDataGenerator(); $ccg = $dg->get_plugin_generator('core_competency'); $path = [get_string('competencies', 'core_competency'), get_string('privacy:path:userevidence', 'core_competency')]; $u1 = $dg->create_user(); $u2 = $dg->create_user(); $u3 = $dg->create_user(); $u1ctx = \context_user::instance($u1->id); $u2ctx = \context_user::instance($u2->id); $u3ctx = \context_user::instance($u3->id); $f = $ccg->create_framework(); $comp1 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $comp2 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $comp3 = $ccg->create_competency(['competencyframeworkid' => $f->get('id')]); $ue1a = $ccg->create_user_evidence(['userid' => $u1->id]); $ue1b = $ccg->create_user_evidence(['userid' => $u1->id]); $ue2a = $ccg->create_user_evidence(['userid' => $u2->id]); $ue3a = $ccg->create_user_evidence(['userid' => $u3->id]); $ccg->create_user_evidence_competency(['userevidenceid' => $ue1a->get('id'), 'competencyid' => $comp1->get('id')]); $ccg->create_user_evidence_competency(['userevidenceid' => $ue1a->get('id'), 'competencyid' => $comp2->get('id')]); $ccg->create_user_evidence_competency(['userevidenceid' => $ue1b->get('id'), 'competencyid' => $comp2->get('id')]); $ccg->create_user_evidence_competency(['userevidenceid' => $ue2a->get('id'), 'competencyid' => $comp2->get('id')]); // Export for user 1 in two contexts to make sure. provider::export_user_data(new approved_contextlist($u1, 'core_competency', [$u1ctx->id, $u2ctx->id])); $data = writer::with_context($u1ctx)->get_data(array_merge($path, ["{$ue1a->get('name')} ({$ue1a->get('id')})"])); $this->assertNotEmpty($data); $this->assertEquals($ue1a->get('name'), $data->name); $this->assertCount(2, $data->competencies); $this->assertEquals($comp1->get('shortname'), $data->competencies[0]['name']); $this->assertEquals($comp2->get('shortname'), $data->competencies[1]['name']); $data = writer::with_context($u1ctx)->get_data(array_merge($path, ["{$ue1b->get('name')} ({$ue1b->get('id')})"])); $this->assertNotEmpty($data); $this->assertEquals($ue1b->get('name'), $data->name); $this->assertCount(1, $data->competencies); $this->assertEquals($comp2->get('shortname'), $data->competencies[0]['name']); // We should not have access to other's info. $data = writer::with_context($u1ctx)->get_data(array_merge($path, ["{$ue2a->get('name')} ({$ue2a->get('id')})"])); $this->assertEmpty($data); $data = writer::with_context($u2ctx)->get_data(array_merge($path, ["{$ue2a->get('name')} ({$ue2a->get('id')})"])); $this->assertEmpty($data); // Export for user 2 in two contexts to make sure. writer::reset(); provider::export_user_data(new approved_contextlist($u2, 'core_competency', [$u2ctx->id, $u1ctx->id])); $data = writer::with_context($u2ctx)->get_data(array_merge($path, ["{$ue2a->get('name')} ({$ue2a->get('id')})"])); $this->assertNotEmpty($data); $this->assertEquals($ue2a->get('name'), $data->name); $this->assertCount(1, $data->competencies); $this->assertEquals($comp2->get('shortname'), $data->competencies[0]['name']); // We should not have access to other's info. $data = writer::with_context($u1ctx)->get_data(array_merge($path, ["{$ue1a->get('name')} ({$ue1a->get('id')})"])); $this->assertEmpty($data); $data = writer::with_context($u2ctx)->get_data(array_merge($path, ["{$ue1a->get('name')} ({$ue1a->get('id')})"])); $this->assertEmpty($data); $data = writer::with_context($u1ctx)->get_data(array_merge($path, ["{$ue1b->get('name')} ({$ue1b->get('id')})"])); $this->assertEmpty($data); $data = writer::with_context($u2ctx)->get_data(array_merge($path, ["{$ue1b->get('name')} ({$ue1b->get('id')})"])); $this->assertEmpty($data); // Export for user 3. writer::reset(); provider::export_user_data(new approved_contextlist($u3, 'core_competency', [$u3ctx->id])); $data = writer::with_context($u3ctx)->get_data(array_merge($path, ["{$ue3a->get('name')} ({$ue3a->get('id')})"])); $this->assertNotEmpty($data); $this->assertEquals($ue3a->get('name'), $data->name); $this->assertCount(0, $data->competencies); } /** * Helps testing comments on plans. * * @return void */ protected function allow_anyone_to_comment_anywhere() { global $DB; $roleid = $DB->get_field('role', 'id', ['shortname' => 'user'], MUST_EXIST); assign_capability('moodle/competency:plancomment', CAP_ALLOW, $roleid, SYSCONTEXTID, true); assign_capability('moodle/competency:planmanage', CAP_ALLOW, $roleid, SYSCONTEXTID, true); assign_capability('moodle/competency:planmanagedraft', CAP_ALLOW, $roleid, SYSCONTEXTID, true); assign_capability('moodle/competency:usercompetencycomment', CAP_ALLOW, $roleid, SYSCONTEXTID, true); assign_capability('moodle/competency:usercompetencyview', CAP_ALLOW, $roleid, SYSCONTEXTID, true); accesslib_clear_all_caches_for_unit_testing(); } /** * Assert the content of a contextlist. * * @param contextlist $contextlist The list. * @param array $expectedcontextsorids The expected content. * @return void */ protected function assert_contextlist(contextlist $contextlist, $expectedcontextsorids) { $contextids = array_unique($contextlist->get_contextids()); $expectedids = array_unique(array_map(function($item) { return $item instanceof \context ? $item->id : $id; }, $expectedcontextsorids)); $this->assert_array_match($expectedids, $contextids); } /** * Assert that array match. * * @param array $array1 The first one. * @param array $array2 The second one. * @return void */ protected function assert_array_match($array1, $array2) { $array1 = (array) (object) $array1; $array2 = (array) (object) $array2; sort($array1); sort($array2); $this->assertEquals($array1, $array2); } /** * Assert the content of exported comments. * * @param array $expected The content of the comments. * @param array $comments The exported comments. * @return void */ protected function assert_exported_comments($expected, $comments) { $this->assertCount(count($expected), $comments); $contents = array_map(function($comment) { return strip_tags($comment->content); }, $comments); $this->assert_array_match($expected, $contents); } /** * Assert that a comment object has comments. * * @param \comment $comment The comment object. * @return void */ protected function assert_has_comments(\comment $comment) { global $DB; $this->assertTrue($DB->record_exists('comments', [ 'contextid' => $comment->get_context()->id, 'component' => $comment->get_component(), 'commentarea' => $comment->get_commentarea(), 'itemid' => $comment->get_itemid() ])); } /** * Assert that a comment object does not have any comments. * * @param \comment $comment The comment object. * @return void */ protected function assert_has_no_comments(\comment $comment) { global $DB; $this->assertFalse($DB->record_exists('comments', [ 'contextid' => $comment->get_context()->id, 'component' => $comment->get_component(), 'commentarea' => $comment->get_commentarea(), 'itemid' => $comment->get_itemid() ])); } /** * Get the count of comments. * * @param \comment $comment The comment object. * @param int $userid The user ID. * @return int */ protected function get_comments_count(\comment $comment, $userid = null) { global $DB; $params = [ 'contextid' => $comment->get_context()->id, 'component' => $comment->get_component(), 'commentarea' => $comment->get_commentarea(), 'itemid' => $comment->get_itemid(), ]; if ($userid) { $params['userid'] = $userid; } return $DB->count_records('comments', $params); } } tests/plan_test.php000064400000061543152265256010010425 0ustar00. namespace core_competency; /** * Plan persistent testcase. * * @package core_competency * @copyright 2015 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class plan_test extends \advanced_testcase { public function test_can_manage_user(): void { $this->resetAfterTest(true); $manage = create_role('Manage', 'manage', 'Plan manager'); $manageown = create_role('Manageown', 'manageown', 'Own plan manager'); $u1 = $this->getDataGenerator()->create_user(); $u2 = $this->getDataGenerator()->create_user(); $u3 = $this->getDataGenerator()->create_user(); $syscontext = \context_system::instance(); $u1context = \context_user::instance($u1->id); $u2context = \context_user::instance($u2->id); $u3context = \context_user::instance($u3->id); assign_capability('moodle/competency:planmanage', CAP_ALLOW, $manage, $syscontext->id); assign_capability('moodle/competency:planmanageown', CAP_ALLOW, $manageown, $u2context->id); role_assign($manage, $u1->id, $syscontext->id); role_assign($manageown, $u2->id, $syscontext->id); role_assign($manage, $u3->id, $u2context->id); accesslib_clear_all_caches_for_unit_testing(); $this->setUser($u1); $this->assertTrue(plan::can_manage_user($u1->id)); $this->assertTrue(plan::can_manage_user($u2->id)); $this->assertTrue(plan::can_manage_user($u3->id)); $this->setUser($u2); $this->assertFalse(plan::can_manage_user($u1->id)); $this->assertTrue(plan::can_manage_user($u2->id)); $this->assertFalse(plan::can_manage_user($u3->id)); $this->setUser($u3); $this->assertFalse(plan::can_manage_user($u1->id)); $this->assertTrue(plan::can_manage_user($u2->id)); $this->assertFalse(plan::can_manage_user($u3->id)); } public function test_can_manage_user_draft(): void { $this->resetAfterTest(true); $manage = create_role('Manage', 'manage', 'Plan manager'); $manageown = create_role('Manageown', 'manageown', 'Own plan manager'); $managedraft = create_role('Managedraft', 'managedraft', 'Draft plan manager'); $manageowndraft = create_role('Manageowndraft', 'manageowndraft', 'Own draft plan manager'); $u1 = $this->getDataGenerator()->create_user(); $u2 = $this->getDataGenerator()->create_user(); $u3 = $this->getDataGenerator()->create_user(); $u4 = $this->getDataGenerator()->create_user(); $u5 = $this->getDataGenerator()->create_user(); $syscontext = \context_system::instance(); $u1context = \context_user::instance($u1->id); $u2context = \context_user::instance($u2->id); $u3context = \context_user::instance($u3->id); $u4context = \context_user::instance($u4->id); $u5context = \context_user::instance($u5->id); assign_capability('moodle/competency:planmanage', CAP_ALLOW, $manage, $syscontext->id); assign_capability('moodle/competency:planmanageown', CAP_ALLOW, $manageown, $syscontext->id); assign_capability('moodle/competency:planmanagedraft', CAP_ALLOW, $managedraft, $syscontext->id); assign_capability('moodle/competency:planmanageowndraft', CAP_ALLOW, $manageowndraft, $syscontext->id); role_assign($manage, $u1->id, $syscontext->id); role_assign($manageown, $u2->id, $syscontext->id); role_assign($managedraft, $u3->id, $syscontext->id); role_assign($managedraft, $u4->id, $u2context->id); role_assign($manageowndraft, $u5->id, $syscontext->id); accesslib_clear_all_caches_for_unit_testing(); $this->setUser($u1); $this->assertFalse(plan::can_manage_user_draft($u1->id)); $this->assertFalse(plan::can_manage_user_draft($u2->id)); $this->assertFalse(plan::can_manage_user_draft($u3->id)); $this->assertFalse(plan::can_manage_user_draft($u4->id)); $this->assertFalse(plan::can_manage_user_draft($u5->id)); $this->setUser($u2); $this->assertFalse(plan::can_manage_user_draft($u1->id)); $this->assertFalse(plan::can_manage_user_draft($u2->id)); $this->assertFalse(plan::can_manage_user_draft($u3->id)); $this->assertFalse(plan::can_manage_user_draft($u4->id)); $this->assertFalse(plan::can_manage_user_draft($u5->id)); $this->setUser($u3); $this->assertTrue(plan::can_manage_user_draft($u1->id)); $this->assertTrue(plan::can_manage_user_draft($u2->id)); $this->assertTrue(plan::can_manage_user_draft($u3->id)); $this->assertTrue(plan::can_manage_user_draft($u4->id)); $this->assertTrue(plan::can_manage_user_draft($u5->id)); $this->setUser($u4); $this->assertFalse(plan::can_manage_user_draft($u1->id)); $this->assertTrue(plan::can_manage_user_draft($u2->id)); $this->assertFalse(plan::can_manage_user_draft($u3->id)); $this->assertFalse(plan::can_manage_user_draft($u4->id)); $this->assertFalse(plan::can_manage_user_draft($u5->id)); $this->setUser($u5); $this->assertFalse(plan::can_manage_user_draft($u1->id)); $this->assertFalse(plan::can_manage_user_draft($u2->id)); $this->assertFalse(plan::can_manage_user_draft($u3->id)); $this->assertFalse(plan::can_manage_user_draft($u4->id)); $this->assertTrue(plan::can_manage_user_draft($u5->id)); } public function test_can_read_user(): void { $this->resetAfterTest(true); $read = create_role('Read', 'read', 'Plan reader'); $readown = create_role('Readown', 'readown', 'Own plan reader'); $u1 = $this->getDataGenerator()->create_user(); $u2 = $this->getDataGenerator()->create_user(); $u3 = $this->getDataGenerator()->create_user(); $syscontext = \context_system::instance(); $u1context = \context_user::instance($u1->id); $u2context = \context_user::instance($u2->id); $u3context = \context_user::instance($u3->id); assign_capability('moodle/competency:planview', CAP_ALLOW, $read, $syscontext->id); assign_capability('moodle/competency:planviewown', CAP_ALLOW, $readown, $u2context->id); role_assign($read, $u1->id, $syscontext->id); role_assign($readown, $u2->id, $syscontext->id); role_assign($read, $u3->id, $u2context->id); accesslib_clear_all_caches_for_unit_testing(); $this->setUser($u1); $this->assertTrue(plan::can_read_user($u1->id)); $this->assertTrue(plan::can_read_user($u2->id)); $this->assertTrue(plan::can_read_user($u3->id)); $this->setUser($u2); $this->assertFalse(plan::can_read_user($u1->id)); $this->assertTrue(plan::can_read_user($u2->id)); $this->assertFalse(plan::can_read_user($u3->id)); $this->setUser($u3); $this->assertFalse(plan::can_read_user($u1->id)); $this->assertTrue(plan::can_read_user($u2->id)); $this->assertTrue(plan::can_read_user($u3->id)); // Due to the default capability. } public function test_can_read_user_draft(): void { $this->resetAfterTest(true); $read = create_role('Read', 'read', 'Plan readr'); $readown = create_role('Readown', 'readown', 'Own plan readr'); $readdraft = create_role('Readdraft', 'readdraft', 'Draft plan readr'); $readowndraft = create_role('Readowndraft', 'readowndraft', 'Own draft plan readr'); $u1 = $this->getDataGenerator()->create_user(); $u2 = $this->getDataGenerator()->create_user(); $u3 = $this->getDataGenerator()->create_user(); $u4 = $this->getDataGenerator()->create_user(); $u5 = $this->getDataGenerator()->create_user(); $syscontext = \context_system::instance(); $u1context = \context_user::instance($u1->id); $u2context = \context_user::instance($u2->id); $u3context = \context_user::instance($u3->id); $u4context = \context_user::instance($u4->id); $u5context = \context_user::instance($u5->id); assign_capability('moodle/competency:planview', CAP_ALLOW, $read, $syscontext->id); assign_capability('moodle/competency:planviewown', CAP_ALLOW, $readown, $syscontext->id); assign_capability('moodle/competency:planviewdraft', CAP_ALLOW, $readdraft, $syscontext->id); assign_capability('moodle/competency:planviewowndraft', CAP_ALLOW, $readowndraft, $syscontext->id); assign_capability('moodle/competency:planviewown', CAP_PROHIBIT, $readowndraft, $syscontext->id); role_assign($read, $u1->id, $syscontext->id); role_assign($readown, $u2->id, $syscontext->id); role_assign($readdraft, $u3->id, $syscontext->id); role_assign($readdraft, $u4->id, $u2context->id); role_assign($readowndraft, $u5->id, $syscontext->id); accesslib_clear_all_caches_for_unit_testing(); $this->setUser($u1); $this->assertFalse(plan::can_read_user_draft($u1->id)); $this->assertFalse(plan::can_read_user_draft($u2->id)); $this->assertFalse(plan::can_read_user_draft($u3->id)); $this->assertFalse(plan::can_read_user_draft($u4->id)); $this->assertFalse(plan::can_read_user_draft($u5->id)); $this->setUser($u2); $this->assertFalse(plan::can_read_user_draft($u1->id)); $this->assertFalse(plan::can_read_user_draft($u2->id)); $this->assertFalse(plan::can_read_user_draft($u3->id)); $this->assertFalse(plan::can_read_user_draft($u4->id)); $this->assertFalse(plan::can_read_user_draft($u5->id)); $this->setUser($u3); $this->assertTrue(plan::can_read_user_draft($u1->id)); $this->assertTrue(plan::can_read_user_draft($u2->id)); $this->assertTrue(plan::can_read_user_draft($u3->id)); $this->assertTrue(plan::can_read_user_draft($u4->id)); $this->assertTrue(plan::can_read_user_draft($u5->id)); $this->setUser($u4); $this->assertFalse(plan::can_read_user_draft($u1->id)); $this->assertTrue(plan::can_read_user_draft($u2->id)); $this->assertFalse(plan::can_read_user_draft($u3->id)); $this->assertFalse(plan::can_read_user_draft($u4->id)); $this->assertFalse(plan::can_read_user_draft($u5->id)); $this->setUser($u5); $this->assertFalse(plan::can_read_user_draft($u1->id)); $this->assertFalse(plan::can_read_user_draft($u2->id)); $this->assertFalse(plan::can_read_user_draft($u3->id)); $this->assertFalse(plan::can_read_user_draft($u4->id)); $this->assertTrue(plan::can_read_user_draft($u5->id)); } public function test_validate_duedate(): void { global $DB; $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $user = $dg->create_user(); $record = array('userid' => $user->id, 'status' => plan::STATUS_DRAFT, 'duedate' => time() - 8000); // Ignore duedate validation on create/update draft plan. $plan = $lpg->create_plan($record); $this->assertInstanceOf(plan::class, $plan); // Passing from draft to active. $plan->set('status', plan::STATUS_ACTIVE); // Draft to active with duedate in the past. $expected = array( 'duedate' => new \lang_string('errorcannotsetduedateinthepast', 'core_competency'), ); $this->assertEquals($expected, $plan->validate()); // Draft to active: past date => past date(fail). $plan->set('duedate', time() - 100); $expected = array( 'duedate' => new \lang_string('errorcannotsetduedateinthepast', 'core_competency'), ); $this->assertEquals($expected, $plan->validate()); // Draft to active: past date => too soon (fail). $plan->set('duedate', time() + 100); $expected = array( 'duedate' => new \lang_string('errorcannotsetduedatetoosoon', 'core_competency'), ); $this->assertEquals($expected, $plan->validate()); // Draft to active: past date => future date (pass). $plan->set('duedate', time() + plan::DUEDATE_THRESHOLD + 10); $this->assertEquals(true, $plan->validate()); // Draft to active: past date => unset date (pass). $plan->set('duedate', 0); $this->assertEquals(true, $plan->validate()); // Updating active plan. $plan->update(); // Active to active: past => same past (pass). $record = $plan->to_record(); $record->duedate = 1; $DB->update_record(plan::TABLE, $record); $plan->read(); $plan->set('description', uniqid()); // Force revalidation. $this->assertTrue($plan->is_valid()); // Active to active: past => unset (pass). $plan->set('duedate', 0); $this->assertTrue($plan->is_valid()); $plan->update(); // Active to active: unset => unset (pass). $plan->set('description', uniqid()); // Force revalidation. $this->assertTrue($plan->is_valid()); // Active to active: unset date => past date(fail). $plan->set('duedate', time() - 100); $expected = array( 'duedate' => new \lang_string('errorcannotsetduedateinthepast', 'core_competency'), ); $this->assertEquals($expected, $plan->validate()); // Active to active: unset date => too soon (fail). $plan->set('duedate', time() + 100); $expected = array( 'duedate' => new \lang_string('errorcannotsetduedatetoosoon', 'core_competency'), ); $this->assertEquals($expected, $plan->validate()); // Active to active: unset date => future date (pass). $plan->set('duedate', time() + plan::DUEDATE_THRESHOLD + 10); $this->assertEquals(true, $plan->validate()); // Updating active plan with future date. $plan->update(); // Active to active: future => same future (pass). $plan->set('description', uniqid()); // Force revalidation. $this->assertTrue($plan->is_valid()); // Active to active: future date => unset date (pass). $plan->set('duedate', 0); $this->assertEquals(true, $plan->validate()); // Active to active: future date => past date(fail). $plan->set('duedate', time() - 100); $expected = array( 'duedate' => new \lang_string('errorcannotsetduedateinthepast', 'core_competency'), ); $this->assertEquals($expected, $plan->validate()); // Active to active: future date => too soon (fail). $plan->set('duedate', time() + 100); $expected = array( 'duedate' => new \lang_string('errorcannotsetduedatetoosoon', 'core_competency'), ); $this->assertEquals($expected, $plan->validate()); // Active to active: future date => future date (pass). $plan->set('duedate', time() + plan::DUEDATE_THRESHOLD + 10); $this->assertEquals(true, $plan->validate()); // Completing plan: with due date in the past. $record = $plan->to_record(); $record->status = plan::STATUS_ACTIVE; $record->duedate = time() - 200; $DB->update_record(plan::TABLE, $record); $success = api::complete_plan($plan->get('id')); $this->assertTrue($success); // Completing plan: with due date too soon (pass). $record = $plan->to_record(); $record->status = plan::STATUS_ACTIVE; $record->duedate = time() + 200; $DB->update_record(plan::TABLE, $record); $success = api::complete_plan($plan->get('id')); $this->assertTrue($success); // Completing plan: with due date in the future (pass). $record = $plan->to_record(); $record->status = plan::STATUS_ACTIVE; $record->duedate = time() + plan::DUEDATE_THRESHOLD + 10; $DB->update_record(plan::TABLE, $record); $success = api::complete_plan($plan->get('id')); $this->assertTrue($success); // Completing plan: with due date unset (pass). $record = $plan->to_record(); $record->status = plan::STATUS_ACTIVE; $record->duedate = 0; $DB->update_record(plan::TABLE, $record); $success = api::complete_plan($plan->get('id')); $this->assertTrue($success); // Reopening plan: with due date in the past => duedate unset. $record = $plan->to_record(); $record->status = plan::STATUS_COMPLETE; $record->duedate = time() - 200; $DB->update_record(plan::TABLE, $record); $success = api::reopen_plan($plan->get('id')); $this->assertTrue($success); $plan->read(); $this->assertEquals(0, $plan->get('duedate')); // Reopening plan: with due date too soon => duedate unset. $record = $plan->to_record(); $record->status = plan::STATUS_COMPLETE; $record->duedate = time() + 100; $DB->update_record(plan::TABLE, $record); $success = api::reopen_plan($plan->get('id')); $this->assertTrue($success); $plan->read(); $this->assertEquals(0, $plan->get('duedate')); // Reopening plan: with due date in the future => duedate unchanged. $record = $plan->to_record(); $record->status = plan::STATUS_COMPLETE; $duedate = time() + plan::DUEDATE_THRESHOLD + 10; $record->duedate = $duedate; $DB->update_record(plan::TABLE, $record); $success = api::reopen_plan($plan->get('id')); $this->assertTrue($success); $plan->read(); // Check that the due date has not changed. $this->assertNotEquals(0, $plan->get('duedate')); $this->assertEquals($duedate, $plan->get('duedate')); } public function test_get_by_user_and_competency(): void { $this->resetAfterTest(); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $u3 = $dg->create_user(); $u4 = $dg->create_user(); $f1 = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $tpl1 = $lpg->create_template(); $lpg->create_template_competency(array('competencyid' => $c1->get('id'), 'templateid' => $tpl1->get('id'))); $p1 = $lpg->create_plan(array('userid' => $u1->id)); $lpg->create_plan_competency(array('planid' => $p1->get('id'), 'competencyid' => $c1->get('id'))); $p2 = $lpg->create_plan(array('userid' => $u2->id)); $lpg->create_plan_competency(array('planid' => $p2->get('id'), 'competencyid' => $c1->get('id'))); $p3 = $lpg->create_plan(array('userid' => $u3->id, 'templateid' => $tpl1->get('id'))); $p4 = $lpg->create_plan(array('userid' => $u4->id, 'templateid' => $tpl1->get('id'))); api::complete_plan($p2); api::complete_plan($p4); // Finding a plan, not completed. $plans = plan::get_by_user_and_competency($u1->id, $c1->get('id')); $this->assertCount(1, $plans); $plan = array_shift($plans); $this->assertEquals($p1->get('id'), $plan->get('id')); $this->assertNotEquals(plan::STATUS_COMPLETE, $plan->get('status')); // Finding a completed plan. $plans = plan::get_by_user_and_competency($u2->id, $c1->get('id')); $this->assertCount(1, $plans); $plan = array_shift($plans); $this->assertEquals($p2->get('id'), $plan->get('id')); $this->assertEquals(plan::STATUS_COMPLETE, $plan->get('status')); // Finding a plan based on a template, not completed. $plans = plan::get_by_user_and_competency($u3->id, $c1->get('id')); $this->assertCount(1, $plans); $plan = array_shift($plans); $this->assertEquals($p3->get('id'), $plan->get('id')); $this->assertTrue($plan->is_based_on_template()); $this->assertNotEquals(plan::STATUS_COMPLETE, $plan->get('status')); // Finding a plan based on a template. $plans = plan::get_by_user_and_competency($u4->id, $c1->get('id')); $this->assertCount(1, $plans); $plan = array_shift($plans); $this->assertEquals($p4->get('id'), $plan->get('id')); $this->assertTrue($plan->is_based_on_template()); $this->assertEquals(plan::STATUS_COMPLETE, $plan->get('status')); // Finding more than one plan, no template. $p5 = $lpg->create_plan(array('userid' => $u1->id)); $lpg->create_plan_competency(array('planid' => $p5->get('id'), 'competencyid' => $c1->get('id'))); $plans = plan::get_by_user_and_competency($u1->id, $c1->get('id')); $this->assertCount(2, $plans); $plan = array_shift($plans); $this->assertEquals($p1->get('id'), $plan->get('id')); $plan = array_shift($plans); $this->assertEquals($p5->get('id'), $plan->get('id')); // Finding more than one plan, with template. $p6 = $lpg->create_plan(array('userid' => $u1->id, 'templateid' => $tpl1->get('id'))); $plans = plan::get_by_user_and_competency($u1->id, $c1->get('id')); $this->assertCount(3, $plans); $plan = array_shift($plans); $this->assertEquals($p1->get('id'), $plan->get('id')); $plan = array_shift($plans); $this->assertEquals($p5->get('id'), $plan->get('id')); $plan = array_shift($plans); $this->assertEquals($p6->get('id'), $plan->get('id')); // Finding no plans. $plans = plan::get_by_user_and_competency($u1->id, $c2->get('id')); $this->assertCount(0, $plans); } public function test_get_competency(): void { $this->resetAfterTest(); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $u3 = $dg->create_user(); $u4 = $dg->create_user(); $f1 = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c3 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c4 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $tpl1 = $lpg->create_template(); $p1 = $lpg->create_plan(array('userid' => $u1->id)); $p2 = $lpg->create_plan(array('userid' => $u2->id)); $p3 = $lpg->create_plan(array('userid' => $u3->id, 'templateid' => $tpl1->get('id'))); $p4 = $lpg->create_plan(array('userid' => $u4->id, 'templateid' => $tpl1->get('id'))); $lpg->create_plan_competency(array('planid' => $p1->get('id'), 'competencyid' => $c1->get('id'))); $lpg->create_plan_competency(array('planid' => $p2->get('id'), 'competencyid' => $c2->get('id'))); $lpg->create_template_competency(array('templateid' => $tpl1->get('id'), 'competencyid' => $c3->get('id'))); $lpg->create_template_competency(array('templateid' => $tpl1->get('id'), 'competencyid' => $c4->get('id'))); // Completing the plans and removing a competency from the template. api::complete_plan($p2); api::complete_plan($p4); api::remove_competency_from_template($tpl1->get('id'), $c4->get('id')); // We can find all competencies. $this->assertEquals($c1->to_record(), $p1->get_competency($c1->get('id'))->to_record()); $this->assertEquals($c2->to_record(), $p2->get_competency($c2->get('id'))->to_record()); $this->assertEquals($c3->to_record(), $p3->get_competency($c3->get('id'))->to_record()); $this->assertEquals($c4->to_record(), $p4->get_competency($c4->get('id'))->to_record()); // Getting the competency 4 from the non-completed plan based on a template p4, will throw an exception. $this->expectException('coding_exception'); $this->expectExceptionMessage('The competency does not belong to this template:'); $p3->get_competency($c4->get('id')); } } tests/lib_test.php000064400000036143152265256010010237 0ustar00. /** * Lib tests. * * @package core_competency * @copyright 2015 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace core_competency; defined('MOODLE_INTERNAL') || die(); use core_competency\plan; use core_competency\url; use core_competency\user_competency; global $CFG; /** * Lib testcase. * * @package core_competency * @copyright 2015 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class lib_test extends \advanced_testcase { public function test_comment_add_user_competency(): void { global $DB, $PAGE; $this->resetAfterTest(); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $u1 = $dg->create_user(['picture' => 1]); $u2 = $dg->create_user(); $u3 = $dg->create_user(); $reviewerroleid = $dg->create_role(); assign_capability('moodle/competency:planview', CAP_ALLOW, $reviewerroleid, \context_system::instance()->id, true); assign_capability('moodle/competency:usercompetencycomment', CAP_ALLOW, $reviewerroleid, \context_system::instance()->id, true); $dg->role_assign($reviewerroleid, $u2->id, \context_user::instance($u1->id)); $dg->role_assign($reviewerroleid, $u3->id, \context_user::instance($u1->id)); accesslib_clear_all_caches_for_unit_testing(); $f1 = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); // In 1 plan. $c2 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); // In 2 plans. $c3 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); // Orphan. $p1 = $lpg->create_plan(array('userid' => $u1->id)); $lpg->create_plan_competency(array('planid' => $p1->get('id'), 'competencyid' => $c1->get('id'))); $lpg->create_plan_competency(array('planid' => $p1->get('id'), 'competencyid' => $c2->get('id'))); $p2 = $lpg->create_plan(array('userid' => $u1->id)); $lpg->create_plan_competency(array('planid' => $p2->get('id'), 'competencyid' => $c2->get('id'))); $DB->set_field(plan::TABLE, 'timemodified', 1, array('id' => $p1->get('id'))); // Make plan 1 appear as old. $p1->read(); $uc1 = $lpg->create_user_competency(array('userid' => $u1->id, 'competencyid' => $c1->get('id'), 'status' => user_competency::STATUS_IN_REVIEW, 'reviewerid' => $u2->id)); $uc2 = $lpg->create_user_competency(array('userid' => $u1->id, 'competencyid' => $c2->get('id'), 'status' => user_competency::STATUS_IN_REVIEW, 'reviewerid' => $u2->id)); $uc3 = $lpg->create_user_competency(array('userid' => $u1->id, 'competencyid' => $c3->get('id'), 'status' => user_competency::STATUS_IN_REVIEW, 'reviewerid' => $u2->id)); // Post a comment for the user competency being in one plan. The reviewer is messaged. $this->setUser($u1); $comment = $uc1->get_comment_object(); $sink = $this->redirectMessages(); $comment->add('Hello world!'); $messages = $sink->get_messages(); $sink->close(); $this->assertCount(1, $messages); $message = array_pop($messages); $expectedurlname = $c1->get('shortname'); $expectedurl = url::user_competency_in_plan($u1->id, $c1->get('id'), $p1->get('id')); $this->assertEquals(\core_user::get_noreply_user()->id, $message->useridfrom); $this->assertEquals($u2->id, $message->useridto); $this->assertTrue(strpos($message->fullmessage, 'Hello world!') !== false); $this->assertTrue(strpos($message->fullmessagehtml, 'Hello world!') !== false); $this->assertEquals(FORMAT_MOODLE, $message->fullmessageformat); $this->assertEquals($expectedurl->out(false), $message->contexturl); $this->assertEquals($expectedurlname, $message->contexturlname); // Test customdata. $customdata = json_decode($message->customdata); $this->assertObjectHasProperty('notificationiconurl', $customdata); $this->assertStringContainsString('tokenpluginfile.php', $customdata->notificationiconurl); $userpicture = new \user_picture($u1); $userpicture->size = 1; // Use f1 size. $userpicture->includetoken = $u2->id; $this->assertEquals($userpicture->get_url($PAGE)->out(false), $customdata->notificationiconurl); // Reviewer posts a comment for the user competency being in two plans. Owner is messaged. $this->setUser($u2); $comment = $uc2->get_comment_object(); $sink = $this->redirectMessages(); $comment->add('Hello world!'); $messages = $sink->get_messages(); $sink->close(); $this->assertCount(1, $messages); $message = array_pop($messages); $expectedurlname = $c2->get('shortname'); $expectedurl = url::user_competency_in_plan($u1->id, $c2->get('id'), $p2->get('id')); $this->assertEquals(\core_user::get_noreply_user()->id, $message->useridfrom); $this->assertEquals($u1->id, $message->useridto); $this->assertTrue(strpos($message->fullmessage, 'Hello world!') !== false); $this->assertTrue(strpos($message->fullmessagehtml, 'Hello world!') !== false); $this->assertEquals(FORMAT_MOODLE, $message->fullmessageformat); $this->assertEquals($expectedurl->out(false), $message->contexturl); $this->assertEquals($expectedurlname, $message->contexturlname); // Reviewer posts a comment for the user competency being in no plans. User is messaged. $this->setUser($u2); $comment = $uc3->get_comment_object(); $sink = $this->redirectMessages(); $comment->add('Hello world!'); $messages = $sink->get_messages(); $sink->close(); $this->assertCount(1, $messages); $message = array_pop($messages); $expectedurlname = get_string('userplans', 'core_competency'); $expectedurl = url::plans($u1->id); $this->assertEquals(\core_user::get_noreply_user()->id, $message->useridfrom); $this->assertEquals($u1->id, $message->useridto); $this->assertTrue(strpos($message->fullmessage, 'Hello world!') !== false); $this->assertTrue(strpos($message->fullmessagehtml, 'Hello world!') !== false); $this->assertEquals(FORMAT_MOODLE, $message->fullmessageformat); $this->assertEquals($expectedurl->out(false), $message->contexturl); $this->assertEquals($expectedurlname, $message->contexturlname); // A comment is posted by another user, reviewer and owner are messaged. $this->setUser($u3); $comment = $uc3->get_comment_object(); $sink = $this->redirectMessages(); $comment->add('Hello world!'); $messages = $sink->get_messages(); $sink->close(); $this->assertCount(2, $messages); $message1 = array_shift($messages); $message2 = array_shift($messages); $this->assertEquals(\core_user::get_noreply_user()->id, $message->useridfrom); $this->assertEquals($u1->id, $message1->useridto); $this->assertEquals(\core_user::get_noreply_user()->id, $message->useridfrom); $this->assertEquals($u2->id, $message2->useridto); // A comment is posted in HTML. $this->setUser($u2); $comment = $uc3->get_comment_object(); $sink = $this->redirectMessages(); $comment->add('Hello world!', FORMAT_HTML); $messages = $sink->get_messages(); $sink->close(); $this->assertCount(1, $messages); $message = array_pop($messages); $expectedurlname = get_string('userplans', 'core_competency'); $expectedurl = url::plans($u1->id); $this->assertEquals(\core_user::get_noreply_user()->id, $message->useridfrom); $this->assertEquals($u1->id, $message->useridto); $this->assertTrue(strpos($message->fullmessage, 'Hello world!') !== false); $this->assertTrue(strpos($message->fullmessagehtml, 'Hello world!') !== false); $this->assertEquals(FORMAT_HTML, $message->fullmessageformat); $this->assertEquals($expectedurl->out(false), $message->contexturl); $this->assertEquals($expectedurlname, $message->contexturlname); } /** * Commenting on a plan. */ public function test_comment_add_plan(): void { $this->resetAfterTest(); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $u3 = $dg->create_user(); $userroleid = $dg->create_role(); $reviewerroleid = $dg->create_role(); assign_capability('moodle/competency:planviewowndraft', CAP_ALLOW, $userroleid, \context_system::instance()->id, true); assign_capability('moodle/competency:planviewown', CAP_ALLOW, $userroleid, \context_system::instance()->id, true); assign_capability('moodle/competency:planviewdraft', CAP_ALLOW, $reviewerroleid, \context_system::instance()->id, true); assign_capability('moodle/competency:planmanage', CAP_ALLOW, $reviewerroleid, \context_system::instance()->id, true); assign_capability('moodle/competency:plancomment', CAP_ALLOW, $reviewerroleid, \context_system::instance()->id, true); $dg->role_assign($userroleid, $u1->id, \context_user::instance($u1->id)); $dg->role_assign($reviewerroleid, $u2->id, \context_user::instance($u1->id)); $dg->role_assign($reviewerroleid, $u3->id, \context_system::instance()); accesslib_clear_all_caches_for_unit_testing(); $p1 = $lpg->create_plan(array('userid' => $u1->id)); // Post a comment in own plan, no reviewer. Nobody is messaged. $this->setUser($u1); $comment = $p1->get_comment_object(); $sink = $this->redirectMessages(); $comment->add('Hello world!'); $messages = $sink->get_messages(); $sink->close(); $this->assertCount(0, $messages); // Post a comment in plan as someone else, no reviewer. The owner is messages. $this->setUser($u3); $comment = $p1->get_comment_object(); $sink = $this->redirectMessages(); $comment->add('Hello world!'); $messages = $sink->get_messages(); $sink->close(); $this->assertCount(1, $messages); $message = array_pop($messages); $this->assertEquals(\core_user::get_noreply_user()->id, $message->useridfrom); $this->assertEquals($u1->id, $message->useridto); // Test customdata. $customdata = json_decode($message->customdata); $this->assertObjectHasProperty('notificationiconurl', $customdata); // Post a comment in a plan with reviewer. The reviewer is messaged. $p1->set('reviewerid', $u2->id); $p1->update(); $this->setUser($u1); $comment = $p1->get_comment_object(); $sink = $this->redirectMessages(); $comment->add('Hello world!'); $messages = $sink->get_messages(); $sink->close(); $this->assertCount(1, $messages); $message = array_pop($messages); $this->assertEquals(\core_user::get_noreply_user()->id, $message->useridfrom); $this->assertEquals($u2->id, $message->useridto); // Post a comment as reviewer in a plan being reviewed. The owner is messaged. $p1->set('reviewerid', $u2->id); $p1->update(); $this->setUser($u2); $comment = $p1->get_comment_object(); $sink = $this->redirectMessages(); $comment->add('Hello world!'); $messages = $sink->get_messages(); $sink->close(); $this->assertCount(1, $messages); $message = array_pop($messages); $this->assertEquals(\core_user::get_noreply_user()->id, $message->useridfrom); $this->assertEquals($u1->id, $message->useridto); // Post a comment as someone else in a plan being reviewed. The owner and reviewer are messaged. $p1->set('reviewerid', $u2->id); $p1->update(); $this->setUser($u3); $comment = $p1->get_comment_object(); $sink = $this->redirectMessages(); $comment->add('Hello world!'); $messages = $sink->get_messages(); $sink->close(); $this->assertCount(2, $messages); $message1 = array_shift($messages); $message2 = array_shift($messages); $this->assertEquals(\core_user::get_noreply_user()->id, $message1->useridfrom); $this->assertEquals($u1->id, $message1->useridto); $this->assertEquals(\core_user::get_noreply_user()->id, $message2->useridfrom); $this->assertEquals($u2->id, $message2->useridto); $p1->set('reviewerid', null); $p1->update(); // Test message content. $this->setUser($u3); $comment = $p1->get_comment_object(); $sink = $this->redirectMessages(); $comment->add('Hello world!'); $messages = $sink->get_messages(); $sink->close(); $this->assertCount(1, $messages); $message = array_pop($messages); $expectedurlname = $p1->get('name'); $expectedurl = url::plan($p1->get('id')); $this->assertTrue(strpos($message->fullmessage, 'Hello world!') !== false); $this->assertTrue(strpos($message->fullmessagehtml, 'Hello world!') !== false); $this->assertEquals(FORMAT_MOODLE, $message->fullmessageformat); $this->assertEquals($expectedurl->out(false), $message->contexturl); $this->assertEquals($expectedurlname, $message->contexturlname); // Test message content as HTML. $this->setUser($u3); $comment = $p1->get_comment_object(); $sink = $this->redirectMessages(); $comment->add('Hello world!', FORMAT_HTML); $messages = $sink->get_messages(); $sink->close(); $this->assertCount(1, $messages); $message = array_pop($messages); $expectedurlname = $p1->get('name'); $expectedurl = url::plan($p1->get('id')); $this->assertTrue(strpos($message->fullmessage, 'Hello world!') !== false); $this->assertTrue(strpos($message->fullmessagehtml, 'Hello world!') !== false); $this->assertEquals(FORMAT_HTML, $message->fullmessageformat); $this->assertEquals($expectedurl->out(false), $message->contexturl); $this->assertEquals($expectedurlname, $message->contexturlname); } } tests/performance_helper_test.php000064400000015424152265256010013330 0ustar00. namespace core_competency; use core_competency\external\performance_helper; /** * Performance helper testcase. * * @package core_competency * @copyright 2016 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class performance_helper_test extends \advanced_testcase { public function test_get_context_from_competency(): void { global $DB; $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $cat1 = $dg->create_category(); $framework = $lpg->create_framework(); $competency = $lpg->create_competency(['competencyframeworkid' => $framework->get('id')]); $competency2 = $lpg->create_competency(['competencyframeworkid' => $framework->get('id')]); $context = $competency->get_context(); $helper = new performance_helper(); $initdbqueries = $DB->perf_get_queries(); // Confirm that subsequent calls return a cached object. // Note that here we check that the framework is not loaded more than once. // The context objects are already cached in the context layer. $firstruncontext = $helper->get_context_from_competency($competency); $dbqueries = $DB->perf_get_queries(); $this->assertSame($context, $firstruncontext); $this->assertNotEquals($initdbqueries, $dbqueries); $secondruncontext = $helper->get_context_from_competency($competency); $this->assertSame($context, $secondruncontext); $this->assertSame($firstruncontext, $secondruncontext); $this->assertEquals($DB->perf_get_queries(), $dbqueries); $thirdruncontext = $helper->get_context_from_competency($competency2); $this->assertSame($context, $thirdruncontext); $this->assertSame($secondruncontext, $thirdruncontext); $this->assertEquals($DB->perf_get_queries(), $dbqueries); } public function test_get_framework_from_competency(): void { global $DB; $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $cat1 = $dg->create_category(); $framework1 = $lpg->create_framework(); $comp1a = $lpg->create_competency(['competencyframeworkid' => $framework1->get('id')]); $comp1b = $lpg->create_competency(['competencyframeworkid' => $framework1->get('id')]); $framework2 = $lpg->create_framework(); $comp2a = $lpg->create_competency(['competencyframeworkid' => $framework2->get('id')]); $helper = new performance_helper(); $initdbqueries = $DB->perf_get_queries(); // Confirm that we get the right framework, and that subsequent calls // do not trigger DB queries, even for other competencies. $firstrunframework = $helper->get_framework_from_competency($comp1a); $firstrundbqueries = $DB->perf_get_queries(); $this->assertNotEquals($initdbqueries, $firstrundbqueries); $this->assertEquals($framework1, $firstrunframework); $this->assertNotSame($framework1, $firstrunframework); $secondrunframework = $helper->get_framework_from_competency($comp1b); $this->assertEquals($firstrundbqueries, $DB->perf_get_queries()); $this->assertEquals($framework1, $secondrunframework); $this->assertSame($firstrunframework, $secondrunframework); $thirdrunframework = $helper->get_framework_from_competency($comp1a); $this->assertEquals($firstrundbqueries, $DB->perf_get_queries()); $this->assertEquals($framework1, $thirdrunframework); $this->assertSame($firstrunframework, $thirdrunframework); // Fetch another framework. $fourthrunframework = $helper->get_framework_from_competency($comp2a); $fourthrundbqueries = $DB->perf_get_queries(); $this->assertNotEquals($firstrundbqueries, $fourthrundbqueries); $this->assertEquals($framework2, $fourthrunframework); $this->assertNotSame($framework2, $fourthrunframework); $fifthrunframework = $helper->get_framework_from_competency($comp2a); $this->assertEquals($fourthrundbqueries, $DB->perf_get_queries()); $this->assertEquals($framework2, $fifthrunframework); $this->assertSame($fourthrunframework, $fifthrunframework); } public function test_get_scale_from_competency(): void { global $DB; $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $scale1 = $dg->create_scale(); $scale2 = $dg->create_scale(); $cat1 = $dg->create_category(); $framework1 = $lpg->create_framework(['scaleid' => $scale1->id]); $comp1 = $lpg->create_competency(['competencyframeworkid' => $framework1->get('id')]); $comp2 = $lpg->create_competency(['competencyframeworkid' => $framework1->get('id'), 'scaleid' => $scale2->id]); $comp3 = $lpg->create_competency(['competencyframeworkid' => $framework1->get('id')]); $helper = new performance_helper(); $initdbqueries = $DB->perf_get_queries(); // Get the first scale. $firstrunscale = $helper->get_scale_from_competency($comp1); $firstrundbqueries = $DB->perf_get_queries(); $this->assertNotEquals($initdbqueries, $firstrundbqueries); $this->assertEquals($scale1, $firstrunscale->get_record_data()); $secondrunscale = $helper->get_scale_from_competency($comp3); $this->assertEquals($firstrundbqueries, $DB->perf_get_queries()); $this->assertSame($firstrunscale, $secondrunscale); // Another scale, and its subsequent calls. $thirdrunscale = $helper->get_scale_from_competency($comp2); $thirddbqueries = $DB->perf_get_queries(); $this->assertNotEquals($firstrundbqueries, $thirddbqueries); $this->assertEquals($scale2, $thirdrunscale->get_record_data()); $this->assertSame($thirdrunscale, $helper->get_scale_from_competency($comp2)); $this->assertEquals($thirddbqueries, $DB->perf_get_queries()); } } tests/competency_rule_test.php000064400000035556152265256010012675 0ustar00. namespace core_competency; defined('MOODLE_INTERNAL') || die(); global $CFG; require_once($CFG->dirroot . '/webservice/tests/helpers.php'); /** * Competency rule testcase. * * @package core_competency * @copyright 2015 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class competency_rule_test extends \externallib_advanced_testcase { public function test_rule_all_matching(): void { $this->resetAfterTest(true); $this->setAdminUser(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $u1 = $this->getDataGenerator()->create_user(); // Set up the framework and competencies. $framework = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'), 'ruletype' => 'core_competency\competency_rule_all')); $c11 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'), 'parentid' => $c1->get('id'), 'ruletype' => 'core_competency\competency_rule_all')); $c111 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'), 'parentid' => $c11->get('id'), 'ruletype' => 'core_competency\competency_rule_all')); $c112 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'), 'parentid' => $c11->get('id'), 'ruletype' => 'core_competency\competency_rule_all')); $c12 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'), 'parentid' => $c1->get('id'), 'ruletype' => 'core_competency\competency_rule_all')); $c13 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'), 'parentid' => $c1->get('id'), 'ruletype' => 'core_competency\competency_rule_all')); $c131 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'), 'parentid' => $c13->get('id'), 'ruletype' => 'core_competency\competency_rule_all')); // Create some user competency records. $uc1 = $lpg->create_user_competency(array('competencyid' => $c1->get('id'), 'userid' => $u1->id)); $uc11 = $lpg->create_user_competency(array('competencyid' => $c11->get('id'), 'userid' => $u1->id, 'grade' => 1, 'proficiency' => 1)); $uc111 = $lpg->create_user_competency(array('competencyid' => $c111->get('id'), 'userid' => $u1->id, 'grade' => 1, 'proficiency' => 1)); $uc112 = $lpg->create_user_competency(array('competencyid' => $c112->get('id'), 'userid' => $u1->id, 'grade' => 1, 'proficiency' => 1)); $uc12 = $lpg->create_user_competency(array('competencyid' => $c12->get('id'), 'userid' => $u1->id)); $uc13 = new user_competency(0, (object) array('userid' => $u1->id, 'competencyid' => $c13->get('id'))); // Not all children are met. $cr = new competency_rule_all($c1); $this->assertFalse($cr->matches($uc1)); // All children are met. $cr = new competency_rule_all($c11); $this->assertTrue($cr->matches($uc11)); // The competency doesn't have any children. $cr = new competency_rule_all($c12); $this->assertFalse($cr->matches($uc12)); // The competency doesn't have saved user competency records. $cr = new competency_rule_all($c13); $this->assertFalse($cr->matches($uc13)); } public function test_rule_points_validation(): void { $this->resetAfterTest(true); $this->setAdminUser(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $framework = $lpg->create_framework(); $framework2 = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'), 'parentid' => $c1->get('id'))); $c3 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'), 'parentid' => $c1->get('id'))); $c4 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $cx = $lpg->create_competency(array('competencyframeworkid' => $framework2->get('id'))); $c1->set('ruletype', 'core_competency\competency_rule_points'); $rule = new competency_rule_points($c1); // Invalid config. $config = json_encode(array()); $this->assertFalse($rule->validate_config($config)); // Missing required points. $config = json_encode(array( 'base' => array(), 'competencies' => array( array('id' => $c2->get('id'), 'points' => 1, 'required' => 0), array('id' => $c3->get('id'), 'points' => 1, 'required' => 0), ) )); $this->assertFalse($rule->validate_config($config)); // Invalid required points. $config = json_encode(array( 'base' => array('points' => 'abc'), 'competencies' => array( array('id' => $c2->get('id'), 'points' => 1, 'required' => 0), array('id' => $c3->get('id'), 'points' => 1, 'required' => 0), ) )); $this->assertFalse($rule->validate_config($config)); // Less than 1 required points. $config = json_encode(array( 'base' => array('points' => 0), 'competencies' => array( array('id' => $c2->get('id'), 'points' => 1, 'required' => 0), array('id' => $c3->get('id'), 'points' => 1, 'required' => 0), ) )); $this->assertFalse($rule->validate_config($config)); // Not enough required points. $config = json_encode(array( 'base' => array('points' => 3), 'competencies' => array( array('id' => $c2->get('id'), 'points' => 1, 'required' => 0), array('id' => $c3->get('id'), 'points' => 1, 'required' => 0), ) )); $this->assertFalse($rule->validate_config($config)); // Duplicate competency. $config = json_encode(array( 'base' => array('points' => 1), 'competencies' => array( array('id' => $c2->get('id'), 'points' => 1, 'required' => 0), array('id' => $c2->get('id'), 'points' => 1, 'required' => 0), ) )); $this->assertFalse($rule->validate_config($config)); // Competency includes itself. $config = json_encode(array( 'base' => array('points' => 1), 'competencies' => array( array('id' => $c1->get('id'), 'points' => 1, 'required' => 0), array('id' => $c2->get('id'), 'points' => 1, 'required' => 0), ) )); $this->assertFalse($rule->validate_config($config)); // Cannot use negative points. $config = json_encode(array( 'base' => array('points' => 1), 'competencies' => array( array('id' => $c2->get('id'), 'points' => -1, 'required' => 0), array('id' => $c3->get('id'), 'points' => 1, 'required' => 0), ) )); $this->assertFalse($rule->validate_config($config)); // Not competencies set. $config = json_encode(array( 'base' => array('points' => 1), 'competencies' => array( ) )); $this->assertFalse($rule->validate_config($config)); // There is a competency that is not a child. $config = json_encode(array( 'base' => array('points' => 1), 'competencies' => array( array('id' => $c1->get('id'), 'points' => 1, 'required' => 0), array('id' => $c2->get('id'), 'points' => 1, 'required' => 0), array('id' => $c3->get('id'), 'points' => 1, 'required' => 0), ) )); $this->assertFalse($rule->validate_config($config)); // There is a competency from another framework in there. $config = json_encode(array( 'base' => array('points' => 1), 'competencies' => array( array('id' => $cx->get('id'), 'points' => 1, 'required' => 0), array('id' => $c3->get('id'), 'points' => 1, 'required' => 0), ) )); $this->assertFalse($rule->validate_config($config)); // A normal config. $config = json_encode(array( 'base' => array('points' => 4), 'competencies' => array( array('id' => $c2->get('id'), 'points' => 3, 'required' => 0), array('id' => $c3->get('id'), 'points' => 2, 'required' => 1), ) )); $this->assertTrue($rule->validate_config($config)); } public function test_rule_points_matching(): void { $this->resetAfterTest(true); $this->setAdminUser(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $u1 = $this->getDataGenerator()->create_user(); // Set up the framework and competencies. $framework = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c1->set('ruletype', 'core_competency\competency_rule_points'); $comprule = new competency_rule_points($c1); $c11 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'), 'parentid' => $c1->get('id'))); $c12 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'), 'parentid' => $c1->get('id'))); $c13 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'), 'parentid' => $c1->get('id'))); $c14 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'), 'parentid' => $c1->get('id'))); // Create some user competency records. $uc1 = $lpg->create_user_competency(array('competencyid' => $c1->get('id'), 'userid' => $u1->id)); $uc11 = $lpg->create_user_competency(array('competencyid' => $c11->get('id'), 'userid' => $u1->id, 'grade' => 1, 'proficiency' => 1)); $uc12 = $lpg->create_user_competency(array('competencyid' => $c12->get('id'), 'userid' => $u1->id, 'grade' => 1, 'proficiency' => 1)); $uc13 = $lpg->create_user_competency(array('competencyid' => $c13->get('id'), 'userid' => $u1->id)); // Enough points. $rule = array( 'base' => array('points' => 8), 'competencies' => array( array( 'id' => $c11->get('id'), 'points' => 4, 'required' => 0 ), array( 'id' => $c12->get('id'), 'points' => 4, 'required' => 0 ), ) ); $c1->set('ruleconfig', json_encode($rule)); $c1->update(); $this->assertTrue($comprule->matches($uc1)); // Not enough points. $rule = array( 'base' => array('points' => 8), 'competencies' => array( array( 'id' => $c11->get('id'), 'points' => 4, 'required' => 0 ), array( 'id' => $c13->get('id'), 'points' => 4, 'required' => 0 ), ) ); $c1->set('ruleconfig', json_encode($rule)); $c1->update(); $this->assertFalse($comprule->matches($uc1)); // One required that is not met but points were OK. $rule = array( 'base' => array('points' => 8), 'competencies' => array( array( 'id' => $c11->get('id'), 'points' => 4, 'required' => 0 ), array( 'id' => $c12->get('id'), 'points' => 4, 'required' => 0 ), array( 'id' => $c13->get('id'), 'points' => 4, 'required' => 1 ), ) ); $c1->set('ruleconfig', json_encode($rule)); $c1->update(); $this->assertFalse($comprule->matches($uc1)); // One required, one not, should match. $rule = array( 'base' => array('points' => 8), 'competencies' => array( array( 'id' => $c11->get('id'), 'points' => 4, 'required' => 0 ), array( 'id' => $c12->get('id'), 'points' => 4, 'required' => 1 ), ) ); $c1->set('ruleconfig', json_encode($rule)); $c1->update(); $this->assertTrue($comprule->matches($uc1)); // All required and should match. $rule = array( 'base' => array('points' => 8), 'competencies' => array( array( 'id' => $c11->get('id'), 'points' => 4, 'required' => 1 ), array( 'id' => $c12->get('id'), 'points' => 4, 'required' => 1 ), ) ); $c1->set('ruleconfig', json_encode($rule)); $c1->update(); $this->assertTrue($comprule->matches($uc1)); // All required, but one doesn't have a user record. $rule = array( 'base' => array('points' => 4), 'competencies' => array( array( 'id' => $c12->get('id'), 'points' => 4, 'required' => 1 ), array( 'id' => $c14->get('id'), 'points' => 4, 'required' => 1 ), ) ); $c1->set('ruleconfig', json_encode($rule)); $c1->update(); $this->assertFalse($comprule->matches($uc1)); } } tests/competency_override_test.php000064400000036425152265256010013541 0ustar00. namespace core_competency; /** * Competency ruleoutcome override grade tests * * @package core_competency * @copyright 2022 Matthew Hilton * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class competency_override_test extends \advanced_testcase { /** @var \stdClass course record. */ protected $course; /** @var \stdClass user record. */ protected $user; /** @var \stdClass block instance record. */ protected $scale; /** @var competency_framework loading competency frameworks from the DB. */ protected $framework; /** @var plan loading competency plans from the DB. */ protected $plan; /** @var competency loading competency from the DB. */ protected $comp1; /** @var competency loading competency from the DB. */ protected $comp2; /** @var \stdClass course module. */ protected $cm; /** @var \completion_info completion information. */ protected $completion; /** @var \context_course context course. */ protected $context; public function setUp(): void { parent::setUp(); $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); // Create user in course. $c1 = $dg->create_course((object) ['enablecompletion' => true]); $u1 = $dg->create_user(); $dg->enrol_user($u1->id, $c1->id); // Create framework with three values. $scale = $dg->create_scale(["scale" => "not,partially,fully"]); $scaleconfiguration = json_encode([ ['scaleid' => $scale->id], ['id' => 1, 'scaledefault' => 1, 'proficient' => 1] ]); $framework = $lpg->create_framework([ 'scaleid' => $scale->id, 'scaleconfiguration' => $scaleconfiguration ]); $plan = $lpg->create_plan(['userid' => $u1->id]); $comp1 = $lpg->create_competency([ 'competencyframeworkid' => $framework->get('id'), 'scaleid' => $scale->id, 'scaleconfiguration' => $scaleconfiguration ]); $comp2 = $lpg->create_competency([ 'competencyframeworkid' => $framework->get('id'), 'scaleid' => $scale->id, 'scaleconfiguration' => $scaleconfiguration ]); api::add_competency_to_plan($plan->get('id'), $comp1->get('id')); api::add_competency_to_plan($plan->get('id'), $comp2->get('id')); $lpg->create_course_competency([ 'courseid' => $c1->id, 'competencyid' => $comp1->get('id'), 'ruleoutcome' => \core_competency\course_competency::OUTCOME_COMPLETE, ]); $lpg->create_course_competency([ 'courseid' => $c1->id, 'competencyid' => $comp2->get('id'), 'ruleoutcome' => \core_competency\course_competency::OUTCOME_COMPLETE, ]); $label = $dg->create_module('label', ['course' => $c1, 'completion' => COMPLETION_VIEWED, 'completionview' => 1]); $cm = get_coursemodule_from_instance('label', $label->id); $completion = new \completion_info($c1); $this->assertEquals(COMPLETION_ENABLED, $completion->is_enabled($cm)); // Link course module with the competency and setup a rule to complete the competency when the module is completed. api::add_competency_to_course_module($cm, $comp1->get('id')); api::add_competency_to_course_module($cm, $comp2->get('id')); $coursemodulecomps = api::list_course_module_competencies_in_course_module($cm); $this->assertCount(2, $coursemodulecomps); api::set_course_module_competency_ruleoutcome($coursemodulecomps[0], \core_competency\course_competency::OUTCOME_COMPLETE); api::set_course_module_competency_ruleoutcome($coursemodulecomps[1], \core_competency\course_competency::OUTCOME_COMPLETE); $this->course = $c1; $this->user = $u1; $this->scale = $scale; $this->framework = $framework; $this->plan = $plan; $this->comp1 = $comp1; $this->comp2 = $comp2; $this->cm = $cm; $this->completion = new \completion_info($c1); $this->context = \context_course::instance($this->course->id); } /** * Test ruleoutcome overridegrade is correctly applied when coursemodule completion is processed. * * @covers \core_competency\api::set_course_module_competency_ruleoutcome */ public function test_ruleoutcome_overridegrade(): void { // Initially the competency (and hence all the child competencies) should not be complete for the user. [$coursecomp, $plancomp, $usercomp] = $this->get_related_competencies($this->comp1->get('id')); $this->assertEquals(0, $plancomp->usercompetency->get('grade')); $this->assertEquals(0, $usercomp->get('grade')); $this->assertEquals(0, $coursecomp->get('grade')); [$coursecomp2, $plancomp2, $usercomp2] = $this->get_related_competencies($this->comp2->get('id')); $this->assertEquals(0, $plancomp2->usercompetency->get('grade')); $this->assertEquals(0, $usercomp2->get('grade')); $this->assertEquals(0, $coursecomp2->get('grade')); // Update the course module completion state to complete and trigger a competency update. $data = $this->completion->get_data($this->cm, false, $this->user->id); $data->completionstate = COMPLETION_COMPLETE; $data->timemodified = time(); $this->completion->internal_set_data($this->cm, $data); // Comptency should now be complete for user, plan, and course now that the course module is completed. [$coursecomp, $plancomp, $usercomp] = $this->get_related_competencies($this->comp1->get('id')); $this->assertEquals(1, $plancomp->usercompetency->get('grade')); $this->assertEquals(1, $usercomp->get('grade')); $this->assertEquals(1, $coursecomp->get('grade')); [$coursecomp2, $plancomp2, $usercomp2] = $this->get_related_competencies($this->comp2->get('id')); $this->assertEquals(1, $plancomp2->usercompetency->get('grade')); $this->assertEquals(1, $usercomp2->get('grade')); $this->assertEquals(1, $coursecomp2->get('grade')); // Change the competency completion for the user by adding evidence. api::add_evidence($this->user->id, $this->comp1, $this->context, evidence::ACTION_OVERRIDE, 'commentincontext', 'core', null, false, null, 2); api::add_evidence($this->user->id, $this->comp2, $this->context, evidence::ACTION_OVERRIDE, 'commentincontext', 'core', null, false, null, 2); // After adding evidence, the competencies should now reflect the new grade value. [$coursecomp, $plancomp, $usercomp] = $this->get_related_competencies($this->comp1->get('id')); $this->assertEquals(2, $plancomp->usercompetency->get('grade')); $this->assertEquals(2, $usercomp->get('grade')); $this->assertEquals(2, $coursecomp->get('grade')); [$coursecomp2, $plancomp2, $usercomp2] = $this->get_related_competencies($this->comp2->get('id')); $this->assertEquals(2, $plancomp2->usercompetency->get('grade')); $this->assertEquals(2, $usercomp2->get('grade')); $this->assertEquals(2, $coursecomp2->get('grade')); // Update the course module competency to incomplete. This will not change the competency status. $data = $this->completion->get_data($this->cm, false, $this->user->id); $data->completionstate = COMPLETION_INCOMPLETE; $data->timemodified = time(); $this->completion->internal_set_data($this->cm, $data); [$coursecomp, $plancomp, $usercomp] = $this->get_related_competencies($this->comp1->get('id')); $this->assertEquals(2, $plancomp->usercompetency->get('grade')); $this->assertEquals(2, $usercomp->get('grade')); $this->assertEquals(2, $coursecomp->get('grade')); [$coursecomp2, $plancomp2, $usercomp2] = $this->get_related_competencies($this->comp2->get('id')); $this->assertEquals(2, $plancomp2->usercompetency->get('grade')); $this->assertEquals(2, $usercomp2->get('grade')); $this->assertEquals(2, $coursecomp2->get('grade')); // Re-complete the course module, so that it attempts to re-complete the competencies. $data = $this->completion->get_data($this->cm, false, $this->user->id); $data->completionstate = COMPLETION_COMPLETE; $data->timemodified = time(); $this->completion->internal_set_data($this->cm, $data); // By default, this will not override the existing grade, so it should remain the same as before. [$coursecomp, $plancomp, $usercomp] = $this->get_related_competencies($this->comp1->get('id')); $this->assertEquals(2, $plancomp->usercompetency->get('grade')); $this->assertEquals(2, $usercomp->get('grade')); $this->assertEquals(2, $coursecomp->get('grade')); [$coursecomp2, $plancomp2, $usercomp2] = $this->get_related_competencies($this->comp2->get('id')); $this->assertEquals(2, $plancomp2->usercompetency->get('grade')); $this->assertEquals(2, $usercomp2->get('grade')); $this->assertEquals(2, $coursecomp2->get('grade')); // Update the completion rule for only competency 1 to $overridegrade = true. $coursemodulecomps = api::list_course_module_competencies_in_course_module($this->cm); api::set_course_module_competency_ruleoutcome($coursemodulecomps[0], \core_competency\course_competency::OUTCOME_COMPLETE, true); // Mark as incomplete then re-complete the course module. $data = $this->completion->get_data($this->cm, false, $this->user->id); $data->completionstate = COMPLETION_INCOMPLETE; $data->timemodified = time(); $this->completion->internal_set_data($this->cm, $data); $data = $this->completion->get_data($this->cm, false, $this->user->id); $data->completionstate = COMPLETION_COMPLETE; $data->timemodified = time(); $this->completion->internal_set_data($this->cm, $data); // Because the rule is now set to override existing grades, the grade should have now updated as per the ruleoutcome. // However the second competency didn't have this rule set, so it will not be overriden. [$coursecomp, $plancomp, $usercomp] = $this->get_related_competencies($this->comp1->get('id')); $this->assertEquals(1, $plancomp->usercompetency->get('grade')); $this->assertEquals(1, $usercomp->get('grade')); $this->assertEquals(1, $coursecomp->get('grade')); [$coursecomp2, $plancomp2, $usercomp2] = $this->get_related_competencies($this->comp2->get('id')); $this->assertEquals(2, $plancomp2->usercompetency->get('grade')); $this->assertEquals(2, $usercomp2->get('grade')); $this->assertEquals(2, $coursecomp2->get('grade')); // If competency 2 is changed now to override and re-completed, it will update the same as competency 1. api::set_course_module_competency_ruleoutcome($coursemodulecomps[1], \core_competency\course_competency::OUTCOME_COMPLETE, true); $data = $this->completion->get_data($this->cm, false, $this->user->id); $data->completionstate = COMPLETION_INCOMPLETE; $data->timemodified = time(); $this->completion->internal_set_data($this->cm, $data); $data = $this->completion->get_data($this->cm, false, $this->user->id); $data->completionstate = COMPLETION_COMPLETE; $data->timemodified = time(); $this->completion->internal_set_data($this->cm, $data); // Now both the competencies have $overridegrade = true, // they should both reflect the ruleoutcome after the completion above was processed. [$coursecomp, $plancomp, $usercomp] = $this->get_related_competencies($this->comp1->get('id')); $this->assertEquals(1, $plancomp->usercompetency->get('grade')); $this->assertEquals(1, $usercomp->get('grade')); $this->assertEquals(1, $coursecomp->get('grade')); [$coursecomp2, $plancomp2, $usercomp2] = $this->get_related_competencies($this->comp2->get('id')); $this->assertEquals(1, $plancomp2->usercompetency->get('grade')); $this->assertEquals(1, $usercomp2->get('grade')); $this->assertEquals(1, $coursecomp2->get('grade')); } /** * Test competency backup and restore correctly restores the ruleoutcome overridegrade value. * * @covers \core_competency\api::set_course_module_competency_ruleoutcome */ public function test_override_backup_restore(): void { global $CFG; require_once($CFG->dirroot . '/course/externallib.php'); // Set one to override grade and another to not override grade. $coursemodulecomps = api::list_course_module_competencies_in_course_module($this->cm); api::set_course_module_competency_ruleoutcome($coursemodulecomps[0], \core_competency\course_competency::OUTCOME_COMPLETE, false); api::set_course_module_competency_ruleoutcome($coursemodulecomps[1], \core_competency\course_competency::OUTCOME_COMPLETE, true); // Duplicate the course (backup and restore). $duplicated = \core_course_external::duplicate_course($this->course->id, 'test', 'test', $this->course->category); // Get the new course modules. $newcoursemodules = get_coursemodules_in_course('label', $duplicated['id']); $this->assertCount(1, $newcoursemodules); $cm = array_pop($newcoursemodules); // Get the comeptencies for this cm. $newcoursemodulecomps = api::list_course_module_competencies_in_course_module($cm); $this->assertCount(2, $newcoursemodulecomps); // Ensure the override grade settings are restored properly. $this->assertEquals($coursemodulecomps[0]->get('overridegrade'), $newcoursemodulecomps[0]->get('overridegrade')); $this->assertEquals($coursemodulecomps[1]->get('overridegrade'), $newcoursemodulecomps[1]->get('overridegrade')); } /** * Gets the course, user and plan competency for the given competency ID * * @param int $compid ID of the competency. * @return array array containing the three related competencies */ private function get_related_competencies(int $compid): array { $coursecomp = api::get_user_competency_in_course($this->course->id, $this->user->id, $compid); $usercomp = api::get_user_competency($this->user->id, $compid); $plancomp = api::get_plan_competency($this->plan, $compid); return [$coursecomp, $plancomp, $usercomp]; } } tests/template_test.php000064400000007033152265256010011300 0ustar00. namespace core_competency; /** * Template persistent testcase. * * @package core_competency * @copyright 2016 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class template_test extends \advanced_testcase { public function test_validate_duedate(): void { global $DB; $this->resetAfterTest(); $tpl = $this->getDataGenerator()->get_plugin_generator('core_competency')->create_template(); // No due date -> pass. $tpl->set('duedate', 0); $this->assertTrue($tpl->is_valid()); // Setting new due date in the past -> fail. $tpl->set('duedate', 1); $errors = $tpl->get_errors(); $this->assertCount(1, $errors); $this->assertArrayHasKey('duedate', $errors); // Setting new due date in very close past -> pass. $tpl->set('duedate', time() - 10); $this->assertTrue($tpl->is_valid()); // Setting new due date in future -> pass. $tpl->set('duedate', time() + 600); $this->assertTrue($tpl->is_valid()); // Save due date in the future. $tpl->update(); // Going from future date to past -> fail. $tpl->set('duedate', 1); $errors = $tpl->get_errors(); $this->assertCount(1, $errors); $this->assertArrayHasKey('duedate', $errors); // Going from future date to none -> pass. $tpl->set('duedate', 0); $this->assertTrue($tpl->is_valid()); // Going from future date to other future -> pass. $tpl->set('duedate', time() + 6000); $this->assertTrue($tpl->is_valid()); // Going from future date to close past -> pass. $tpl->set('duedate', time() - 10); $this->assertTrue($tpl->is_valid()); // Mocking past due date. $record = $tpl->to_record(); $record->duedate = 1; $DB->update_record(template::TABLE, $record); $tpl->read(); $this->assertEquals(1, $tpl->get('duedate')); // Not changing the past due date -> pass. // Note: changing visibility to force validation. $tpl->set('visible', 0); $tpl->set('visible', 1); $this->assertTrue($tpl->is_valid()); // Changing past due date to other past -> fail. $tpl->set('duedate', 10); $errors = $tpl->get_errors(); $this->assertCount(1, $errors); $this->assertArrayHasKey('duedate', $errors); // Changing past due date close past -> pass. $tpl->set('duedate', time() + 10); $this->assertTrue($tpl->is_valid()); // Changing past due date to future -> pass. $tpl->set('duedate', time() + 1000); $this->assertTrue($tpl->is_valid()); // Changing past due date to none -> pass. $tpl->set('duedate', 0); $this->assertTrue($tpl->is_valid()); } } tests/user_evidence_competency_test.php000064400000010424152265256010014531 0ustar00. namespace core_competency; /** * User evidence competency persistent testcase. * * @package core_competency * @copyright 2016 Serge Gauthier - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class user_evidence_competency_test extends \advanced_testcase { public function test_get_user_competencies_by_userevidenceid(): void { global $CFG, $DB; $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $u1 = $dg->create_user(); // Create framework with competencies. $fw = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $fw->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $fw->get('id'))); $c3 = $lpg->create_competency(array('competencyframeworkid' => $fw->get('id'))); $c4 = $lpg->create_competency(array('competencyframeworkid' => $fw->get('id'))); // Create a plan with competencies. $p1 = $lpg->create_plan(array('userid' => $u1->id)); $lpg->create_plan_competency(array('planid' => $p1->get('id'), 'competencyid' => $c1->get('id'))); $lpg->create_plan_competency(array('planid' => $p1->get('id'), 'competencyid' => $c2->get('id'))); $lpg->create_plan_competency(array('planid' => $p1->get('id'), 'competencyid' => $c3->get('id'))); $lpg->create_plan_competency(array('planid' => $p1->get('id'), 'competencyid' => $c4->get('id'))); // Create a prior learning evidence and link competencies. $ue1 = $lpg->create_user_evidence(array('userid' => $u1->id)); $uec11 = $lpg->create_user_evidence_competency(array('userevidenceid' => $ue1->get('id'), 'competencyid' => $c1->get('id'))); $uec12 = $lpg->create_user_evidence_competency(array('userevidenceid' => $ue1->get('id'), 'competencyid' => $c2->get('id'))); $uec13 = $lpg->create_user_evidence_competency(array('userevidenceid' => $ue1->get('id'), 'competencyid' => $c3->get('id'))); $uc11 = $lpg->create_user_competency(array('userid' => $u1->id, 'competencyid' => $c1->get('id'))); $uc12 = $lpg->create_user_competency(array('userid' => $u1->id, 'competencyid' => $c2->get('id'))); $uc13 = $lpg->create_user_competency(array('userid' => $u1->id, 'competencyid' => $c3->get('id'))); // Create an other prior learning evidence and link competencies. $ue2 = $lpg->create_user_evidence(array('userid' => $u1->id)); $uec22 = $lpg->create_user_evidence_competency(array('userevidenceid' => $ue2->get('id'), 'competencyid' => $c4->get('id'))); $uc22 = $lpg->create_user_competency(array('userid' => $u1->id, 'competencyid' => $c4->get('id'))); // Check the user competencies associated to the first prior learning evidence. $ucs = user_evidence_competency::get_user_competencies_by_userevidenceid($ue1->get('id')); $this->assertCount(3, $ucs); $uc = array_shift($ucs); $this->assertEquals($uc->get('id'), $uc11->get('id')); $uc = array_shift($ucs); $this->assertEquals($uc->get('id'), $uc12->get('id')); $uc = array_shift($ucs); $this->assertEquals($uc->get('id'), $uc13->get('id')); // Check the user competencies associated to the second prior learning evidence. $ucs = user_evidence_competency::get_user_competencies_by_userevidenceid($ue2->get('id')); $this->assertCount(1, $ucs); $uc = array_shift($ucs); $this->assertEquals($uc->get('id'), $uc22->get('id')); } } tests/hooks_test.php000064400000034356152265256010010620 0ustar00. namespace core_competency; /** * Hook tests. * * @package core_competency * @copyright 2016 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class hooks_test extends \advanced_testcase { public function test_hook_course_deleted(): void { $this->resetAfterTest(); $dg = $this->getDataGenerator(); $ccg = $dg->get_plugin_generator('core_competency'); $u1 = $dg->create_user(); $framework = $ccg->create_framework(); $comp1 = $ccg->create_competency(['competencyframeworkid' => $framework->get('id')]); $comp2 = $ccg->create_competency(['competencyframeworkid' => $framework->get('id')]); $c1 = $dg->create_course(); $cc1a = $ccg->create_course_competency(['competencyid' => $comp1->get('id'), 'courseid' => $c1->id]); $cc1b = $ccg->create_course_competency(['competencyid' => $comp2->get('id'), 'courseid' => $c1->id]); $assign1a = $dg->create_module('assign', ['course' => $c1]); $assign1b = $dg->create_module('assign', ['course' => $c1]); $cmc1a = $ccg->create_course_module_competency(['competencyid' => $comp1->get('id'), 'cmid' => $assign1a->cmid]); $cmc1b = $ccg->create_course_module_competency(['competencyid' => $comp1->get('id'), 'cmid' => $assign1b->cmid]); $ucc1a = $ccg->create_user_competency_course(['competencyid' => $comp1->get('id'), 'courseid' => $c1->id, 'userid' => $u1->id]); $ucc1b = $ccg->create_user_competency_course(['competencyid' => $comp2->get('id'), 'courseid' => $c1->id, 'userid' => $u1->id]); $c2 = $dg->create_course(); $cc2a = $ccg->create_course_competency(['competencyid' => $comp1->get('id'), 'courseid' => $c2->id]); $cc2b = $ccg->create_course_competency(['competencyid' => $comp2->get('id'), 'courseid' => $c2->id]); $assign2a = $dg->create_module('assign', ['course' => $c2]); $assign2b = $dg->create_module('assign', ['course' => $c2]); $cmc2a = $ccg->create_course_module_competency(['competencyid' => $comp1->get('id'), 'cmid' => $assign2a->cmid]); $cmc2b = $ccg->create_course_module_competency(['competencyid' => $comp1->get('id'), 'cmid' => $assign2b->cmid]); $ucc2a = $ccg->create_user_competency_course(['competencyid' => $comp1->get('id'), 'courseid' => $c2->id, 'userid' => $u1->id]); $ucc2b = $ccg->create_user_competency_course(['competencyid' => $comp2->get('id'), 'courseid' => $c2->id, 'userid' => $u1->id]); delete_course($c1, false); $this->assertEquals(0, course_competency::count_records(['courseid' => $c1->id])); $this->assertEquals(2, course_competency::count_records(['courseid' => $c2->id])); $this->assertEquals(0, course_module_competency::count_records(['cmid' => $assign1a->cmid])); $this->assertEquals(0, course_module_competency::count_records(['cmid' => $assign1b->cmid])); $this->assertEquals(1, course_module_competency::count_records(['cmid' => $assign2a->cmid])); $this->assertEquals(1, course_module_competency::count_records(['cmid' => $assign2b->cmid])); $this->assertEquals(0, user_competency_course::count_records(['courseid' => $c1->id, 'userid' => $u1->id])); $this->assertEquals(2, user_competency_course::count_records(['courseid' => $c2->id, 'userid' => $u1->id])); } public function test_hook_course_module_deleted(): void { $this->resetAfterTest(); $dg = $this->getDataGenerator(); $ccg = $dg->get_plugin_generator('core_competency'); $u1 = $dg->create_user(); $framework = $ccg->create_framework(); $comp1 = $ccg->create_competency(['competencyframeworkid' => $framework->get('id')]); $comp2 = $ccg->create_competency(['competencyframeworkid' => $framework->get('id')]); $c1 = $dg->create_course(); $cc1a = $ccg->create_course_competency(['competencyid' => $comp1->get('id'), 'courseid' => $c1->id]); $cc1b = $ccg->create_course_competency(['competencyid' => $comp2->get('id'), 'courseid' => $c1->id]); $assign1a = $dg->create_module('assign', ['course' => $c1]); $assign1b = $dg->create_module('assign', ['course' => $c1]); $cmc1a = $ccg->create_course_module_competency(['competencyid' => $comp1->get('id'), 'cmid' => $assign1a->cmid]); $cmc1b = $ccg->create_course_module_competency(['competencyid' => $comp1->get('id'), 'cmid' => $assign1b->cmid]); $ucc1a = $ccg->create_user_competency_course(['competencyid' => $comp1->get('id'), 'courseid' => $c1->id, 'userid' => $u1->id]); $ucc1b = $ccg->create_user_competency_course(['competencyid' => $comp2->get('id'), 'courseid' => $c1->id, 'userid' => $u1->id]); $c2 = $dg->create_course(); $cc2a = $ccg->create_course_competency(['competencyid' => $comp1->get('id'), 'courseid' => $c2->id]); $cc2b = $ccg->create_course_competency(['competencyid' => $comp2->get('id'), 'courseid' => $c2->id]); $assign2a = $dg->create_module('assign', ['course' => $c2]); $assign2b = $dg->create_module('assign', ['course' => $c2]); $cmc2a = $ccg->create_course_module_competency(['competencyid' => $comp1->get('id'), 'cmid' => $assign2a->cmid]); $cmc2b = $ccg->create_course_module_competency(['competencyid' => $comp1->get('id'), 'cmid' => $assign2b->cmid]); $ucc2a = $ccg->create_user_competency_course(['competencyid' => $comp1->get('id'), 'courseid' => $c2->id, 'userid' => $u1->id]); $ucc2b = $ccg->create_user_competency_course(['competencyid' => $comp2->get('id'), 'courseid' => $c2->id, 'userid' => $u1->id]); course_delete_module($assign1b->cmid); $this->assertEquals(2, course_competency::count_records(['courseid' => $c1->id])); $this->assertEquals(1, course_module_competency::count_records(['cmid' => $assign1a->cmid])); $this->assertEquals(0, course_module_competency::count_records(['cmid' => $assign1b->cmid])); $this->assertEquals(2, user_competency_course::count_records(['courseid' => $c1->id])); $this->assertEquals(2, course_competency::count_records(['courseid' => $c2->id])); $this->assertEquals(1, course_module_competency::count_records(['cmid' => $assign2a->cmid])); $this->assertEquals(1, course_module_competency::count_records(['cmid' => $assign2b->cmid])); $this->assertEquals(2, user_competency_course::count_records(['courseid' => $c2->id, 'userid' => $u1->id])); } public function test_hook_course_reset_competency_ratings(): void { $this->resetAfterTest(); $dg = $this->getDataGenerator(); $ccg = $dg->get_plugin_generator('core_competency'); $u1 = $dg->create_user(); $framework = $ccg->create_framework(); $comp1 = $ccg->create_competency(['competencyframeworkid' => $framework->get('id')]); $comp2 = $ccg->create_competency(['competencyframeworkid' => $framework->get('id')]); $c1 = $dg->create_course(); $cc1a = $ccg->create_course_competency(['competencyid' => $comp1->get('id'), 'courseid' => $c1->id]); $cc1b = $ccg->create_course_competency(['competencyid' => $comp2->get('id'), 'courseid' => $c1->id]); $assign1a = $dg->create_module('assign', ['course' => $c1]); $assign1b = $dg->create_module('assign', ['course' => $c1]); $cmc1a = $ccg->create_course_module_competency(['competencyid' => $comp1->get('id'), 'cmid' => $assign1a->cmid]); $cmc1b = $ccg->create_course_module_competency(['competencyid' => $comp1->get('id'), 'cmid' => $assign1b->cmid]); $ucc1a = $ccg->create_user_competency_course(['competencyid' => $comp1->get('id'), 'courseid' => $c1->id, 'userid' => $u1->id]); $ucc1b = $ccg->create_user_competency_course(['competencyid' => $comp2->get('id'), 'courseid' => $c1->id, 'userid' => $u1->id]); $c2 = $dg->create_course(); $cc2a = $ccg->create_course_competency(['competencyid' => $comp1->get('id'), 'courseid' => $c2->id]); $cc2b = $ccg->create_course_competency(['competencyid' => $comp2->get('id'), 'courseid' => $c2->id]); $assign2a = $dg->create_module('assign', ['course' => $c2]); $assign2b = $dg->create_module('assign', ['course' => $c2]); $cmc2a = $ccg->create_course_module_competency(['competencyid' => $comp1->get('id'), 'cmid' => $assign2a->cmid]); $cmc2b = $ccg->create_course_module_competency(['competencyid' => $comp1->get('id'), 'cmid' => $assign2b->cmid]); $ucc2a = $ccg->create_user_competency_course(['competencyid' => $comp1->get('id'), 'courseid' => $c2->id, 'userid' => $u1->id]); $ucc2b = $ccg->create_user_competency_course(['competencyid' => $comp2->get('id'), 'courseid' => $c2->id, 'userid' => $u1->id]); reset_course_userdata((object) ['id' => $c1->id, 'reset_competency_ratings' => true]); $this->assertEquals(2, course_competency::count_records(['courseid' => $c1->id])); $this->assertEquals(2, course_competency::count_records(['courseid' => $c2->id])); $this->assertEquals(1, course_module_competency::count_records(['cmid' => $assign1a->cmid])); $this->assertEquals(1, course_module_competency::count_records(['cmid' => $assign1b->cmid])); $this->assertEquals(1, course_module_competency::count_records(['cmid' => $assign2a->cmid])); $this->assertEquals(1, course_module_competency::count_records(['cmid' => $assign2b->cmid])); $this->assertEquals(0, user_competency_course::count_records(['courseid' => $c1->id, 'userid' => $u1->id])); $this->assertEquals(2, user_competency_course::count_records(['courseid' => $c2->id, 'userid' => $u1->id])); } public function test_hook_cohort_deleted(): void { $this->resetAfterTest(); $this->setAdminUser(); $datagen = $this->getDataGenerator(); $corecompgen = $datagen->get_plugin_generator('core_competency'); $c1 = $datagen->create_cohort(); $c2 = $datagen->create_cohort(); $t1 = $corecompgen->create_template(); $t2 = $corecompgen->create_template(); // Create the template cohorts. api::create_template_cohort($t1->get('id'), $c1->id); api::create_template_cohort($t1->get('id'), $c2->id); api::create_template_cohort($t2->get('id'), $c1->id); // Check that the association was made. $this->assertEquals(2, \core_competency\template_cohort::count_records(array('templateid' => $t1->get('id')))); $this->assertEquals(1, \core_competency\template_cohort::count_records(array('templateid' => $t2->get('id')))); // Delete the first cohort. cohort_delete_cohort($c1); // Check that the association was removed. $this->assertEquals(1, \core_competency\template_cohort::count_records(array('templateid' => $t1->get('id')))); $this->assertEquals(0, \core_competency\template_cohort::count_records(array('templateid' => $t2->get('id')))); } public function test_hook_user_deleted(): void { $this->resetAfterTest(); $dg = $this->getDataGenerator(); $ccg = $dg->get_plugin_generator('core_competency'); $u1 = $dg->create_user(); $framework = $ccg->create_framework(); $comp1 = $ccg->create_competency(['competencyframeworkid' => $framework->get('id')]); $comp2 = $ccg->create_competency(['competencyframeworkid' => $framework->get('id')]); $c1 = $dg->create_course(); $cc1a = $ccg->create_course_competency(['competencyid' => $comp1->get('id'), 'courseid' => $c1->id]); $cc1b = $ccg->create_course_competency(['competencyid' => $comp2->get('id'), 'courseid' => $c1->id]); $assign1a = $dg->create_module('assign', ['course' => $c1]); $assign1b = $dg->create_module('assign', ['course' => $c1]); $cmc1a = $ccg->create_course_module_competency(['competencyid' => $comp1->get('id'), 'cmid' => $assign1a->cmid]); $cmc1b = $ccg->create_course_module_competency(['competencyid' => $comp1->get('id'), 'cmid' => $assign1b->cmid]); $ucc1a = $ccg->create_user_competency_course(['competencyid' => $comp1->get('id'), 'courseid' => $c1->id, 'userid' => $u1->id]); $ucc1b = $ccg->create_user_competency_course(['competencyid' => $comp2->get('id'), 'courseid' => $c1->id, 'userid' => $u1->id]); $c2 = $dg->create_course(); $cc2a = $ccg->create_course_competency(['competencyid' => $comp1->get('id'), 'courseid' => $c2->id]); $cc2b = $ccg->create_course_competency(['competencyid' => $comp2->get('id'), 'courseid' => $c2->id]); $assign2a = $dg->create_module('assign', ['course' => $c2]); $assign2b = $dg->create_module('assign', ['course' => $c2]); $cmc2a = $ccg->create_course_module_competency(['competencyid' => $comp1->get('id'), 'cmid' => $assign2a->cmid]); $cmc2b = $ccg->create_course_module_competency(['competencyid' => $comp1->get('id'), 'cmid' => $assign2b->cmid]); $ucc2a = $ccg->create_user_competency_course(['competencyid' => $comp1->get('id'), 'courseid' => $c2->id, 'userid' => $u1->id]); $ucc2b = $ccg->create_user_competency_course(['competencyid' => $comp2->get('id'), 'courseid' => $c2->id, 'userid' => $u1->id]); reset_course_userdata((object) ['id' => $c1->id, 'reset_competency_ratings' => true]); delete_user($u1); // Assert the records don't exist anymore. $this->assertEquals(0, user_competency_course::count_records(['courseid' => $c1->id, 'userid' => $u1->id])); } } tests/course_module_competency_test.php000064400000005346152265256010014565 0ustar00. namespace core_competency; /** * Course module competency persistent testcase. * * @package core_competency * @copyright 2019 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class course_module_competency_test extends \advanced_testcase { public function test_count_competencies(): void { global $CFG, $DB; $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $c1 = $dg->create_course(); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $framework = $lpg->create_framework(); $comp1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); // In C1, and C2. $comp2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); // In C2. $lpg->create_course_competency(array('competencyid' => $comp1->get('id'), 'courseid' => $c1->id)); $lpg->create_course_competency(array('competencyid' => $comp2->get('id'), 'courseid' => $c1->id)); $assign1a = $dg->create_module('assign', ['course' => $c1]); $assign1b = $dg->create_module('assign', ['course' => $c1]); $cmc1a = $lpg->create_course_module_competency(['competencyid' => $comp1->get('id'), 'cmid' => $assign1a->cmid]); $cmc1b = $lpg->create_course_module_competency(['competencyid' => $comp1->get('id'), 'cmid' => $assign1b->cmid]); $cmc2b = $lpg->create_course_module_competency(['competencyid' => $comp2->get('id'), 'cmid' => $assign1b->cmid]); // Enrol the user 1 in C1. $dg->enrol_user($u1->id, $c1->id); $all = course_module_competency::list_course_module_competencies($assign1a->cmid); $this->assertEquals(course_module_competency::count_competencies($assign1a->cmid), count($all)); $all = course_module_competency::list_course_module_competencies($assign1b->cmid); $this->assertEquals(course_module_competency::count_competencies($assign1b->cmid), count($all)); } } tests/api_test.php000064400000710326152265256010010244 0ustar00. namespace core_competency; /** * API tests. * * @package core_competency * @copyright 2015 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class api_test extends \advanced_testcase { public function test_get_framework_related_contexts(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $cat1 = $dg->create_category(); $cat2 = $dg->create_category(array('parent' => $cat1->id)); $cat3 = $dg->create_category(array('parent' => $cat2->id)); $c1 = $dg->create_course(array('category' => $cat2->id)); // This context should not be returned. $cat1ctx = \context_coursecat::instance($cat1->id); $cat2ctx = \context_coursecat::instance($cat2->id); $cat3ctx = \context_coursecat::instance($cat3->id); $sysctx = \context_system::instance(); $expected = array($cat1ctx->id => $cat1ctx); $this->assertEquals($expected, api::get_related_contexts($cat1ctx, 'self')); $expected = array($cat1ctx->id => $cat1ctx, $cat2ctx->id => $cat2ctx, $cat3ctx->id => $cat3ctx); $this->assertEquals($expected, api::get_related_contexts($cat1ctx, 'children')); $expected = array($sysctx->id => $sysctx, $cat1ctx->id => $cat1ctx, $cat2ctx->id => $cat2ctx); $this->assertEquals($expected, api::get_related_contexts($cat2ctx, 'parents')); } public function test_get_framework_related_contexts_with_capabilities(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $user = $dg->create_user(); $cat1 = $dg->create_category(); $cat2 = $dg->create_category(array('parent' => $cat1->id)); $cat3 = $dg->create_category(array('parent' => $cat2->id)); $c1 = $dg->create_course(array('category' => $cat2->id)); // This context should not be returned. $cat1ctx = \context_coursecat::instance($cat1->id); $cat2ctx = \context_coursecat::instance($cat2->id); $cat3ctx = \context_coursecat::instance($cat3->id); $sysctx = \context_system::instance(); $roleallow = create_role('Allow', 'allow', 'Allow read'); assign_capability('moodle/competency:competencyview', CAP_ALLOW, $roleallow, $sysctx->id); role_assign($roleallow, $user->id, $sysctx->id); $roleprevent = create_role('Prevent', 'prevent', 'Prevent read'); assign_capability('moodle/competency:competencyview', CAP_PROHIBIT, $roleprevent, $sysctx->id); role_assign($roleprevent, $user->id, $cat2ctx->id); accesslib_clear_all_caches_for_unit_testing(); $this->setUser($user); $this->assertFalse(has_capability('moodle/competency:competencyview', $cat2ctx)); $requiredcap = array('moodle/competency:competencyview'); $expected = array(); $this->assertEquals($expected, api::get_related_contexts($cat2ctx, 'self', $requiredcap)); $expected = array($cat1ctx->id => $cat1ctx); $this->assertEquals($expected, api::get_related_contexts($cat1ctx, 'children', $requiredcap)); $expected = array($sysctx->id => $sysctx, $cat1ctx->id => $cat1ctx); $this->assertEquals($expected, api::get_related_contexts($cat2ctx, 'parents', $requiredcap)); } public function test_get_template_related_contexts(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $cat1 = $dg->create_category(); $cat2 = $dg->create_category(array('parent' => $cat1->id)); $cat3 = $dg->create_category(array('parent' => $cat2->id)); $c1 = $dg->create_course(array('category' => $cat2->id)); // This context should not be returned. $cat1ctx = \context_coursecat::instance($cat1->id); $cat2ctx = \context_coursecat::instance($cat2->id); $cat3ctx = \context_coursecat::instance($cat3->id); $sysctx = \context_system::instance(); $expected = array($cat1ctx->id => $cat1ctx); $this->assertEquals($expected, api::get_related_contexts($cat1ctx, 'self')); $expected = array($cat1ctx->id => $cat1ctx, $cat2ctx->id => $cat2ctx, $cat3ctx->id => $cat3ctx); $this->assertEquals($expected, api::get_related_contexts($cat1ctx, 'children')); $expected = array($sysctx->id => $sysctx, $cat1ctx->id => $cat1ctx, $cat2ctx->id => $cat2ctx); $this->assertEquals($expected, api::get_related_contexts($cat2ctx, 'parents')); } public function test_get_template_related_contexts_with_capabilities(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $user = $dg->create_user(); $cat1 = $dg->create_category(); $cat2 = $dg->create_category(array('parent' => $cat1->id)); $cat3 = $dg->create_category(array('parent' => $cat2->id)); $c1 = $dg->create_course(array('category' => $cat2->id)); // This context should not be returned. $cat1ctx = \context_coursecat::instance($cat1->id); $cat2ctx = \context_coursecat::instance($cat2->id); $cat3ctx = \context_coursecat::instance($cat3->id); $sysctx = \context_system::instance(); $roleallow = create_role('Allow', 'allow', 'Allow read'); assign_capability('moodle/competency:templateview', CAP_ALLOW, $roleallow, $sysctx->id); role_assign($roleallow, $user->id, $sysctx->id); $roleprevent = create_role('Prevent', 'prevent', 'Prevent read'); assign_capability('moodle/competency:templateview', CAP_PROHIBIT, $roleprevent, $sysctx->id); role_assign($roleprevent, $user->id, $cat2ctx->id); accesslib_clear_all_caches_for_unit_testing(); $this->setUser($user); $this->assertFalse(has_capability('moodle/competency:templateview', $cat2ctx)); $requiredcap = array('moodle/competency:templateview'); $expected = array(); $this->assertEquals($expected, api::get_related_contexts($cat2ctx, 'self', $requiredcap)); $expected = array($cat1ctx->id => $cat1ctx); $this->assertEquals($expected, api::get_related_contexts($cat1ctx, 'children', $requiredcap)); $expected = array($sysctx->id => $sysctx, $cat1ctx->id => $cat1ctx); $this->assertEquals($expected, api::get_related_contexts($cat2ctx, 'parents', $requiredcap)); } /** * Test updating a template. */ public function test_update_template(): void { $cat = $this->getDataGenerator()->create_category(); $this->resetAfterTest(true); $this->setAdminUser(); $syscontext = \context_system::instance(); $template = api::create_template((object) array('shortname' => 'testing', 'contextid' => $syscontext->id)); $this->assertEquals('testing', $template->get('shortname')); $this->assertEquals($syscontext->id, $template->get('contextid')); // Simple update. api::update_template((object) array('id' => $template->get('id'), 'shortname' => 'success')); $template = api::read_template($template->get('id')); $this->assertEquals('success', $template->get('shortname')); // Trying to change the context. $this->expectException(\coding_exception::class); api::update_template((object) ['id' => $template->get('id'), 'contextid' => \context_coursecat::instance($cat->id)->id]); } /** * Test listing framework with order param. */ public function test_list_frameworks(): void { $this->resetAfterTest(true); $this->setAdminUser(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); // Create a list of frameworks. $framework1 = $lpg->create_framework(array( 'shortname' => 'shortname_alpha', 'idnumber' => 'idnumber_cinnamon', 'description' => 'description', 'descriptionformat' => FORMAT_HTML, 'visible' => true, 'contextid' => \context_system::instance()->id )); $framework2 = $lpg->create_framework(array( 'shortname' => 'shortname_beetroot', 'idnumber' => 'idnumber_apple', 'description' => 'description', 'descriptionformat' => FORMAT_HTML, 'visible' => true, 'contextid' => \context_system::instance()->id )); $framework3 = $lpg->create_framework(array( 'shortname' => 'shortname_crisps', 'idnumber' => 'idnumber_beer', 'description' => 'description', 'descriptionformat' => FORMAT_HTML, 'visible' => false, 'contextid' => \context_system::instance()->id )); // Get frameworks list order by shortname desc. $result = api::list_frameworks('shortname', 'DESC', null, 3, \context_system::instance()); $f = (object) array_shift($result); $this->assertEquals($framework3->get('id'), $f->get('id')); $f = (object) array_shift($result); $this->assertEquals($framework2->get('id'), $f->get('id')); $f = (object) array_shift($result); $this->assertEquals($framework1->get('id'), $f->get('id')); // Get frameworks list order by idnumber asc. $result = api::list_frameworks('idnumber', 'ASC', null, 3, \context_system::instance()); $f = (object) array_shift($result); $this->assertEquals($framework2->get('id'), $f->get('id')); $f = (object) array_shift($result); $this->assertEquals($framework3->get('id'), $f->get('id')); $f = (object) array_shift($result); $this->assertEquals($framework1->get('id'), $f->get('id')); // Repeat excluding the non-visible ones. $result = api::list_frameworks('idnumber', 'ASC', null, 3, \context_system::instance(), 'self', true); $this->assertCount(2, $result); $f = (object) array_shift($result); $this->assertEquals($framework2->get('id'), $f->get('id')); $f = (object) array_shift($result); $this->assertEquals($framework1->get('id'), $f->get('id')); // Search by query string, trying match on shortname. $result = api::list_frameworks('idnumber', 'ASC', null, 3, \context_system::instance(), 'self', false, 'crisp'); $this->assertCount(1, $result); $f = (object) array_shift($result); $this->assertEquals($framework3->get('id'), $f->get('id')); // Search by query string, trying match on shortname, but hidden. $result = api::list_frameworks('idnumber', 'ASC', null, 3, \context_system::instance(), 'self', true, 'crisp'); $this->assertCount(0, $result); // Search by query string, trying match on ID number. $result = api::list_frameworks('idnumber', 'ASC', null, 3, \context_system::instance(), 'self', false, 'apple'); $this->assertCount(1, $result); $f = (object) array_shift($result); $this->assertEquals($framework2->get('id'), $f->get('id')); // Search by query string, trying match on both. $result = api::list_frameworks('idnumber', 'ASC', null, 3, \context_system::instance(), 'self', false, 'bee'); $this->assertCount(2, $result); $f = (object) array_shift($result); $this->assertEquals($framework2->get('id'), $f->get('id')); $f = (object) array_shift($result); $this->assertEquals($framework3->get('id'), $f->get('id')); } /** * Test duplicate a framework. */ public function test_duplicate_framework(): void { $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $this->resetAfterTest(true); $this->setAdminUser(); $syscontext = \context_system::instance(); $params = array( 'shortname' => 'shortname_a', 'idnumber' => 'idnumber_c', 'description' => 'description', 'descriptionformat' => FORMAT_HTML, 'visible' => true, 'contextid' => $syscontext->id ); $framework = $lpg->create_framework($params); $competency1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $competency2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $competency3 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $competency4 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $competency41 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'), 'parentid' => $competency4->get('id')) ); $competency42 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'), 'parentid' => $competency4->get('id')) ); $competencyidnumbers = array($competency1->get('idnumber'), $competency2->get('idnumber'), $competency3->get('idnumber'), $competency4->get('idnumber'), $competency41->get('idnumber'), $competency42->get('idnumber') ); $config = json_encode(array( 'base' => array('points' => 4), 'competencies' => array( array('id' => $competency41->get('id'), 'points' => 3, 'required' => 0), array('id' => $competency42->get('id'), 'points' => 2, 'required' => 1), ) )); $competency4->set('ruletype', 'core_competency\competency_rule_points'); $competency4->set('ruleoutcome', \core_competency\competency::OUTCOME_EVIDENCE); $competency4->set('ruleconfig', $config); $competency4->update(); api::add_related_competency($competency1->get('id'), $competency2->get('id')); api::add_related_competency($competency3->get('id'), $competency4->get('id')); $frameworkduplicated1 = api::duplicate_framework($framework->get('id')); $frameworkduplicated2 = api::duplicate_framework($framework->get('id')); $this->assertEquals($framework->get('idnumber').'_1', $frameworkduplicated1->get('idnumber')); $this->assertEquals($framework->get('idnumber').'_2', $frameworkduplicated2->get('idnumber')); $competenciesfr1 = api::list_competencies(array('competencyframeworkid' => $frameworkduplicated1->get('id'))); $competenciesfr2 = api::list_competencies(array('competencyframeworkid' => $frameworkduplicated2->get('id'))); $competencyidsfr1 = array(); $competencyidsfr2 = array(); foreach ($competenciesfr1 as $cmp) { $competencyidsfr1[] = $cmp->get('idnumber'); } foreach ($competenciesfr2 as $cmp) { $competencyidsfr2[] = $cmp->get('idnumber'); } $this->assertEmpty(array_diff($competencyidsfr1, $competencyidnumbers)); $this->assertEmpty(array_diff($competencyidsfr2, $competencyidnumbers)); $this->assertCount(6, $competenciesfr1); $this->assertCount(6, $competenciesfr2); // Test the related competencies. reset($competenciesfr1); $compduplicated1 = current($competenciesfr1); $relatedcompetencies = $compduplicated1->get_related_competencies(); $comprelated = current($relatedcompetencies); $this->assertEquals($comprelated->get('idnumber'), $competency2->get('idnumber')); // Check if config rule have been ported correctly. $competency4duplicated = competency::get_record(array( 'idnumber' => $competency4->get('idnumber'), 'competencyframeworkid' => $frameworkduplicated2->get('id') )); $configduplicated = json_decode($competency4duplicated->get('ruleconfig'), true); $configorigin = json_decode($config, true); // Check that the 2 config have the same base. $this->assertEquals($configorigin['base'], $configduplicated['base']); $this->assertEquals(count($configorigin['competencies']), count($configduplicated['competencies'])); $competencyidsrules = array(); foreach ($configduplicated['competencies'] as $key => $value) { // Check that the only difference between the 2 config is id competency. $this->assertEquals(1, count(array_diff($value, $configorigin['competencies'][$key]))); $competencyidsrules[] = $value['id']; } $this->assertTrue($competency4duplicated->is_parent_of($competencyidsrules)); // Test duplicate an empty framework. $emptyfrm = $lpg->create_framework(); $emptyfrmduplicated = api::duplicate_framework($emptyfrm->get('id')); $this->assertEquals($emptyfrm->get('idnumber').'_1', $emptyfrmduplicated->get('idnumber')); $nbcomp = api::count_competencies(array('competencyframeworkid' => $emptyfrmduplicated->get('id'))); $this->assertEquals(0, $nbcomp); } /** * Test update plan. */ public function test_update_plan(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $usermanageowndraft = $dg->create_user(); $usermanageown = $dg->create_user(); $usermanagedraft = $dg->create_user(); $usermanage = $dg->create_user(); $syscontext = \context_system::instance(); // Creating specific roles. $manageowndraftrole = $dg->create_role(array( 'name' => 'User manage own draft', 'shortname' => 'manage-own-draft' )); $manageownrole = $dg->create_role(array( 'name' => 'User manage own', 'shortname' => 'manage-own' )); $managedraftrole = $dg->create_role(array( 'name' => 'User manage draft', 'shortname' => 'manage-draft' )); $managerole = $dg->create_role(array( 'name' => 'User manage', 'shortname' => 'manage' )); assign_capability('moodle/competency:planmanageowndraft', CAP_ALLOW, $manageowndraftrole, $syscontext->id); assign_capability('moodle/competency:planviewowndraft', CAP_ALLOW, $manageowndraftrole, $syscontext->id); assign_capability('moodle/competency:planmanageown', CAP_ALLOW, $manageownrole, $syscontext->id); assign_capability('moodle/competency:planviewown', CAP_ALLOW, $manageownrole, $syscontext->id); assign_capability('moodle/competency:planmanagedraft', CAP_ALLOW, $managedraftrole, $syscontext->id); assign_capability('moodle/competency:planviewdraft', CAP_ALLOW, $managedraftrole, $syscontext->id); assign_capability('moodle/competency:planmanage', CAP_ALLOW, $managerole, $syscontext->id); assign_capability('moodle/competency:planview', CAP_ALLOW, $managerole, $syscontext->id); $dg->role_assign($manageowndraftrole, $usermanageowndraft->id, $syscontext->id); $dg->role_assign($manageownrole, $usermanageown->id, $syscontext->id); $dg->role_assign($managedraftrole, $usermanagedraft->id, $syscontext->id); $dg->role_assign($managerole, $usermanage->id, $syscontext->id); // Create first learning plan with user create draft. $this->setUser($usermanageowndraft); $plan = array ( 'name' => 'plan own draft', 'description' => 'plan own draft', 'userid' => $usermanageowndraft->id ); $plan = api::create_plan((object)$plan); $record = $plan->to_record(); $record->name = 'plan own draft modified'; // Check if user create draft can edit the plan name. $plan = api::update_plan($record); $this->assertInstanceOf('\core_competency\plan', $plan); // The status cannot be changed in this method. $record->status = \core_competency\plan::STATUS_ACTIVE; try { $plan = api::update_plan($record); $this->fail('Updating the status is not allowed.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/To change the status of a plan use the appropriate methods./', $e->getMessage()); } // Test when user with manage own plan capability try to edit other user plan. $record->status = \core_competency\plan::STATUS_DRAFT; $record->name = 'plan create draft modified 2'; $this->setUser($usermanageown); try { $plan = api::update_plan($record); $this->fail('User with manage own plan capability can only edit his own plan.'); } catch (\required_capability_exception $e) { $this->assertTrue(true); } // User with manage plan capability cannot edit the other user plans with status draft. $this->setUser($usermanage); $record->name = 'plan create draft modified 3'; try { $plan = api::update_plan($record); $this->fail('User with manage plan capability cannot edit the other user plans with status draft'); } catch (\required_capability_exception $e) { $this->assertTrue(true); } // User with manage draft capability can edit other user's learning plan if the status is draft. $this->setUser($usermanagedraft); $record->status = \core_competency\plan::STATUS_DRAFT; $record->name = 'plan manage draft modified 3'; $plan = api::update_plan($record); $this->assertInstanceOf('\core_competency\plan', $plan); // User with manage plan capability can create/edit learning plan if status is active/complete. $this->setUser($usermanage); $plan = array ( 'name' => 'plan create', 'description' => 'plan create', 'userid' => $usermanage->id, 'status' => \core_competency\plan::STATUS_ACTIVE ); $plan = api::create_plan((object)$plan); // Silently transition to complete status to avoid errors about transitioning to complete. $plan->set('status', \core_competency\plan::STATUS_COMPLETE); $plan->update(); $record = $plan->to_record(); $record->name = 'plan create own modified'; try { api::update_plan($record); $this->fail('Completed plan can not be edited'); } catch (\coding_exception $e) { $this->assertTrue(true); } } public function test_create_plan_from_template(): void { $this->resetAfterTest(true); $this->setAdminUser(); $u1 = $this->getDataGenerator()->create_user(); $tpl = $this->getDataGenerator()->get_plugin_generator('core_competency')->create_template(); // Creating a new plan. $plan = api::create_plan_from_template($tpl, $u1->id); $record = $plan->to_record(); $this->assertInstanceOf('\core_competency\plan', $plan); $this->assertTrue(\core_competency\plan::record_exists($plan->get('id'))); $this->assertEquals($tpl->get('id'), $plan->get('templateid')); $this->assertEquals($u1->id, $plan->get('userid')); $this->assertTrue($plan->is_based_on_template()); // Creating a plan that already exists. $plan = api::create_plan_from_template($tpl, $u1->id); $this->assertFalse($plan); // Check that api::create_plan cannot be used. unset($record->id); $this->expectException(\coding_exception::class); $plan = api::create_plan($record); } public function test_update_plan_based_on_template(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $this->setAdminUser(); $tpl1 = $lpg->create_template(); $tpl2 = $lpg->create_template(); $up1 = $lpg->create_plan(array('userid' => $u1->id, 'templateid' => $tpl1->get('id'))); $up2 = $lpg->create_plan(array('userid' => $u2->id, 'templateid' => null)); try { // Trying to remove the template dependency. $record = $up1->to_record(); $record->templateid = null; api::update_plan($record); $this->fail('A plan cannot be unlinked using api::update_plan()'); } catch (\coding_exception $e) { // All good. } try { // Trying to switch to another template. $record = $up1->to_record(); $record->templateid = $tpl2->get('id'); api::update_plan($record); $this->fail('A plan cannot be moved to another template.'); } catch (\coding_exception $e) { // All good. } try { // Trying to switch to using a template. $record = $up2->to_record(); $record->templateid = $tpl1->get('id'); api::update_plan($record); $this->fail('A plan cannot be update to use a template.'); } catch (\coding_exception $e) { // All good. } } public function test_unlink_plan_from_template(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $this->setAdminUser(); $f1 = $lpg->create_framework(); $f2 = $lpg->create_framework(); $c1a = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2a = $lpg->create_competency(array('competencyframeworkid' => $f2->get('id'))); $c1b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $tpl1 = $lpg->create_template(); $tpl2 = $lpg->create_template(); $tplc1a = $lpg->create_template_competency(array('templateid' => $tpl1->get('id'), 'competencyid' => $c1a->get('id'), 'sortorder' => 9)); $tplc1b = $lpg->create_template_competency(array('templateid' => $tpl1->get('id'), 'competencyid' => $c1b->get('id'), 'sortorder' => 8)); $tplc2a = $lpg->create_template_competency(array('templateid' => $tpl2->get('id'), 'competencyid' => $c2a->get('id'))); $plan1 = $lpg->create_plan(array('userid' => $u1->id, 'templateid' => $tpl1->get('id'), 'status' => plan::STATUS_ACTIVE)); $plan2 = $lpg->create_plan(array('userid' => $u2->id, 'templateid' => $tpl2->get('id'))); $plan3 = $lpg->create_plan(array('userid' => $u1->id, 'templateid' => $tpl1->get('id'), 'status' => plan::STATUS_COMPLETE)); // Check that we have what we expect at this stage. $this->assertEquals(2, \core_competency\template_competency::count_records(array('templateid' => $tpl1->get('id')))); $this->assertEquals(1, \core_competency\template_competency::count_records(array('templateid' => $tpl2->get('id')))); $this->assertEquals(0, \core_competency\plan_competency::count_records(array('planid' => $plan1->get('id')))); $this->assertEquals(0, \core_competency\plan_competency::count_records(array('planid' => $plan2->get('id')))); $this->assertTrue($plan1->is_based_on_template()); $this->assertTrue($plan2->is_based_on_template()); // Let's do this! $tpl1comps = \core_competency\template_competency::list_competencies($tpl1->get('id'), true); $tpl2comps = \core_competency\template_competency::list_competencies($tpl2->get('id'), true); api::unlink_plan_from_template($plan1); $plan1->read(); $plan2->read(); $this->assertCount(2, $tpl1comps); $this->assertCount(1, $tpl2comps); $this->assertEquals(2, \core_competency\template_competency::count_records(array('templateid' => $tpl1->get('id')))); $this->assertEquals(1, \core_competency\template_competency::count_records(array('templateid' => $tpl2->get('id')))); $this->assertEquals(2, \core_competency\plan_competency::count_records(array('planid' => $plan1->get('id')))); $this->assertEquals(0, \core_competency\plan_competency::count_records(array('planid' => $plan2->get('id')))); $this->assertFalse($plan1->is_based_on_template()); $this->assertEquals($tpl1->get('id'), $plan1->get('origtemplateid')); $this->assertTrue($plan2->is_based_on_template()); $this->assertEquals(null, $plan2->get('origtemplateid')); // Check we can unlink draft plan. try { api::unlink_plan_from_template($plan2); } catch (\coding_exception $e) { $this->fail('Fail to unlink draft plan.'); } // Check we can not unlink completed plan. try { api::unlink_plan_from_template($plan3); $this->fail('We can not unlink completed plan.'); } catch (\coding_exception $e) { // All good. } // Even the order remains. $plan1comps = \core_competency\plan_competency::list_competencies($plan1->get('id')); $before = reset($tpl1comps); $after = reset($plan1comps); $this->assertEquals($before->get('id'), $after->get('id')); $this->assertEquals($before->get('sortorder'), $after->get('sortorder')); $before = next($tpl1comps); $after = next($plan1comps); $this->assertEquals($before->get('id'), $after->get('id')); $this->assertEquals($before->get('sortorder'), $after->get('sortorder')); } public function test_update_template_updates_plans(): void { $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $lpg = $dg->get_plugin_generator('core_competency'); $tpl1 = $lpg->create_template(); $tpl2 = $lpg->create_template(); // Create plans with data not matching templates. $time = time(); $plan1 = $lpg->create_plan(array('templateid' => $tpl1->get('id'), 'userid' => $u1->id, 'name' => 'Not good name', 'duedate' => $time + 3600, 'description' => 'Ahah', 'descriptionformat' => FORMAT_MARKDOWN)); $plan2 = $lpg->create_plan(array('templateid' => $tpl1->get('id'), 'userid' => $u2->id, 'name' => 'Not right name', 'duedate' => $time + 3601, 'description' => 'Ahah', 'descriptionformat' => FORMAT_PLAIN)); $plan3 = $lpg->create_plan(array('templateid' => $tpl2->get('id'), 'userid' => $u1->id, 'name' => 'Not sweet name', 'duedate' => $time + 3602, 'description' => 'Ahah', 'descriptionformat' => FORMAT_PLAIN)); // Prepare our expectations. $plan1->read(); $plan2->read(); $plan3->read(); $this->assertEquals($tpl1->get('id'), $plan1->get('templateid')); $this->assertEquals($tpl1->get('id'), $plan2->get('templateid')); $this->assertEquals($tpl2->get('id'), $plan3->get('templateid')); $this->assertNotEquals($tpl1->get('shortname'), $plan1->get('name')); $this->assertNotEquals($tpl1->get('shortname'), $plan2->get('name')); $this->assertNotEquals($tpl2->get('shortname'), $plan3->get('name')); $this->assertNotEquals($tpl1->get('description'), $plan1->get('description')); $this->assertNotEquals($tpl1->get('description'), $plan2->get('description')); $this->assertNotEquals($tpl2->get('description'), $plan3->get('description')); $this->assertNotEquals($tpl1->get('descriptionformat'), $plan1->get('descriptionformat')); $this->assertNotEquals($tpl1->get('descriptionformat'), $plan2->get('descriptionformat')); $this->assertNotEquals($tpl2->get('descriptionformat'), $plan3->get('descriptionformat')); $this->assertNotEquals($tpl1->get('duedate'), $plan1->get('duedate')); $this->assertNotEquals($tpl1->get('duedate'), $plan2->get('duedate')); $this->assertNotEquals($tpl2->get('duedate'), $plan3->get('duedate')); // Update the template without changing critical fields does not update the plans. $data = $tpl1->to_record(); $data->visible = 0; api::update_template($data); $this->assertNotEquals($tpl1->get('shortname'), $plan1->get('name')); $this->assertNotEquals($tpl1->get('shortname'), $plan2->get('name')); $this->assertNotEquals($tpl2->get('shortname'), $plan3->get('name')); $this->assertNotEquals($tpl1->get('description'), $plan1->get('description')); $this->assertNotEquals($tpl1->get('description'), $plan2->get('description')); $this->assertNotEquals($tpl2->get('description'), $plan3->get('description')); $this->assertNotEquals($tpl1->get('descriptionformat'), $plan1->get('descriptionformat')); $this->assertNotEquals($tpl1->get('descriptionformat'), $plan2->get('descriptionformat')); $this->assertNotEquals($tpl2->get('descriptionformat'), $plan3->get('descriptionformat')); $this->assertNotEquals($tpl1->get('duedate'), $plan1->get('duedate')); $this->assertNotEquals($tpl1->get('duedate'), $plan2->get('duedate')); $this->assertNotEquals($tpl2->get('duedate'), $plan3->get('duedate')); // Now really update the template. $data = $tpl1->to_record(); $data->shortname = 'Awesome!'; $data->description = 'This is too awesome!'; $data->descriptionformat = FORMAT_HTML; $data->duedate = $time + 200; api::update_template($data); $tpl1->read(); // Now confirm that the right plans were updated. $plan1->read(); $plan2->read(); $plan3->read(); $this->assertEquals($tpl1->get('id'), $plan1->get('templateid')); $this->assertEquals($tpl1->get('id'), $plan2->get('templateid')); $this->assertEquals($tpl2->get('id'), $plan3->get('templateid')); $this->assertEquals($tpl1->get('shortname'), $plan1->get('name')); $this->assertEquals($tpl1->get('shortname'), $plan2->get('name')); $this->assertNotEquals($tpl2->get('shortname'), $plan3->get('name')); $this->assertEquals($tpl1->get('description'), $plan1->get('description')); $this->assertEquals($tpl1->get('description'), $plan2->get('description')); $this->assertNotEquals($tpl2->get('description'), $plan3->get('description')); $this->assertEquals($tpl1->get('descriptionformat'), $plan1->get('descriptionformat')); $this->assertEquals($tpl1->get('descriptionformat'), $plan2->get('descriptionformat')); $this->assertNotEquals($tpl2->get('descriptionformat'), $plan3->get('descriptionformat')); $this->assertEquals($tpl1->get('duedate'), $plan1->get('duedate')); $this->assertEquals($tpl1->get('duedate'), $plan2->get('duedate')); $this->assertNotEquals($tpl2->get('duedate'), $plan3->get('duedate')); } /** * Test that the method to complete a plan. */ public function test_complete_plan(): void { global $DB; $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $user = $dg->create_user(); // Create a framework and assign competencies. $framework = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c3 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c4 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); // Create two plans and assign competencies. $plan = $lpg->create_plan(array('userid' => $user->id)); $otherplan = $lpg->create_plan(array('userid' => $user->id)); $lpg->create_plan_competency(array('planid' => $plan->get('id'), 'competencyid' => $c1->get('id'))); $lpg->create_plan_competency(array('planid' => $plan->get('id'), 'competencyid' => $c2->get('id'))); $lpg->create_plan_competency(array('planid' => $plan->get('id'), 'competencyid' => $c3->get('id'))); $lpg->create_plan_competency(array('planid' => $otherplan->get('id'), 'competencyid' => $c1->get('id'))); $uclist = array( $lpg->create_user_competency(array('userid' => $user->id, 'competencyid' => $c1->get('id'), 'proficiency' => true, 'grade' => 1 )), $lpg->create_user_competency(array('userid' => $user->id, 'competencyid' => $c2->get('id'), 'proficiency' => false, 'grade' => 2 )) ); $this->assertEquals(2, \core_competency\user_competency::count_records()); $this->assertEquals(0, \core_competency\user_competency_plan::count_records()); // Change status of the plan to complete. api::complete_plan($plan); // Check that user competencies are now in user_competency_plan objects and still in user_competency. $this->assertEquals(2, \core_competency\user_competency::count_records()); $this->assertEquals(3, \core_competency\user_competency_plan::count_records()); $usercompetenciesplan = \core_competency\user_competency_plan::get_records(); $this->assertEquals($uclist[0]->get('userid'), $usercompetenciesplan[0]->get('userid')); $this->assertEquals($uclist[0]->get('competencyid'), $usercompetenciesplan[0]->get('competencyid')); $this->assertEquals($uclist[0]->get('proficiency'), (bool) $usercompetenciesplan[0]->get('proficiency')); $this->assertEquals($uclist[0]->get('grade'), $usercompetenciesplan[0]->get('grade')); $this->assertEquals($plan->get('id'), $usercompetenciesplan[0]->get('planid')); $this->assertEquals($uclist[1]->get('userid'), $usercompetenciesplan[1]->get('userid')); $this->assertEquals($uclist[1]->get('competencyid'), $usercompetenciesplan[1]->get('competencyid')); $this->assertEquals($uclist[1]->get('proficiency'), (bool) $usercompetenciesplan[1]->get('proficiency')); $this->assertEquals($uclist[1]->get('grade'), $usercompetenciesplan[1]->get('grade')); $this->assertEquals($plan->get('id'), $usercompetenciesplan[1]->get('planid')); $this->assertEquals($user->id, $usercompetenciesplan[2]->get('userid')); $this->assertEquals($c3->get('id'), $usercompetenciesplan[2]->get('competencyid')); $this->assertNull($usercompetenciesplan[2]->get('proficiency')); $this->assertNull($usercompetenciesplan[2]->get('grade')); $this->assertEquals($plan->get('id'), $usercompetenciesplan[2]->get('planid')); // Check we can not add competency to completed plan. try { api::add_competency_to_plan($plan->get('id'), $c4->get('id')); $this->fail('We can not add competency to completed plan.'); } catch (\coding_exception $e) { // All good. } // Check we can not remove competency to completed plan. try { api::remove_competency_from_plan($plan->get('id'), $c3->get('id')); $this->fail('We can not remove competency to completed plan.'); } catch (\coding_exception $e) { // All good. } // Completing a plan that is completed throws an exception. $this->expectException(\coding_exception::class); api::complete_plan($plan); } /** * Set-up the workflow data (review, active, ...). * * @return array */ protected function setup_workflow_data() { $this->resetAfterTest(); $dg = $this->getDataGenerator(); $user = $dg->create_user(); $reviewer = $dg->create_user(); $otheruser = $dg->create_user(); $syscontext = \context_system::instance(); $userrole = $dg->create_role(); $reviewerrole = $dg->create_role(); $otheruserrole = $dg->create_role(); assign_capability('moodle/competency:planmanageowndraft', CAP_ALLOW, $userrole, $syscontext->id); assign_capability('moodle/competency:planmanage', CAP_ALLOW, $reviewerrole, $syscontext->id); assign_capability('moodle/competency:planviewdraft', CAP_ALLOW, $reviewerrole, $syscontext->id); $dg->role_assign($userrole, $user->id, $syscontext->id); $dg->role_assign($reviewerrole, $reviewer->id, $syscontext->id); accesslib_clear_all_caches_for_unit_testing(); $lpg = $dg->get_plugin_generator('core_competency'); $tpl = $lpg->create_template(); $plan = $lpg->create_plan(array('userid' => $user->id)); $tplplan = $lpg->create_plan(array('userid' => $user->id, 'templateid' => $tpl->get('id'))); return array( 'dg' => $dg, 'lpg' => $lpg, 'user' => $user, 'reviewer' => $reviewer, 'otheruser' => $otheruser, 'plan' => $plan, 'tplplan' => $tplplan, ); } /** * Testing requesting the review of a plan. */ public function test_plan_request_review(): void { $data = $this->setup_workflow_data(); $dg = $data['dg']; $lpg = $data['lpg']; $user = $data['user']; $reviewer = $data['reviewer']; $otheruser = $data['otheruser']; $plan = $data['plan']; $tplplan = $data['tplplan']; $this->assertEquals(plan::STATUS_DRAFT, $plan->get('status')); $this->assertEquals(plan::STATUS_DRAFT, $tplplan->get('status')); // Foreign user cannot do anything. $this->setUser($otheruser); try { api::plan_request_review($plan); $this->fail('The user can not read the plan.'); } catch (\required_capability_exception $e) { $this->assertEquals('nopermissions', $e->errorcode); } // Can not change a plan based on a template. $this->setUser($user); try { api::plan_request_review($tplplan); $this->fail('The plan is based on a template.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/Template plans cannot be reviewed./', $e->getMessage()); } // Can not send for review when not draft. $this->setUser($user); $plan->set('status', plan::STATUS_WAITING_FOR_REVIEW); try { api::plan_request_review($plan); $this->fail('The plan cannot be sent for review at this stage.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/The plan cannot be sent for review at this stage./', $e->getMessage()); } // Can not send for review when not draft. $this->setUser($user); $plan->set('status', plan::STATUS_ACTIVE); try { api::plan_request_review($plan); $this->fail('The plan cannot be sent for review at this stage.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/The plan cannot be sent for review at this stage./', $e->getMessage()); } // Can not send for review when not draft. $this->setUser($user); $plan->set('status', plan::STATUS_IN_REVIEW); try { api::plan_request_review($plan); $this->fail('The plan cannot be sent for review at this stage.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/The plan cannot be sent for review at this stage./', $e->getMessage()); } // Can not send for review when not draft. $this->setUser($user); $plan->set('status', plan::STATUS_COMPLETE); try { api::plan_request_review($plan); $this->fail('The plan cannot be sent for review at this stage.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/The plan cannot be sent for review at this stage./', $e->getMessage()); } // Sending for review as a reviewer. $this->setUser($reviewer); $plan->set('status', plan::STATUS_DRAFT); try { api::plan_request_review($plan); $this->fail('The user can not request a review.'); } catch (\required_capability_exception $e) { $this->assertEquals('nopermissions', $e->errorcode); } // Sending for review. $this->setUser($user); api::plan_request_review($plan); $plan->read(); $this->assertEquals(plan::STATUS_WAITING_FOR_REVIEW, $plan->get('status')); // Sending for review by ID. $plan->set('status', plan::STATUS_DRAFT); $plan->update(); api::plan_request_review($plan->get('id')); $plan->read(); $this->assertEquals(plan::STATUS_WAITING_FOR_REVIEW, $plan->get('status')); } /** * Testing cancelling the review request. */ public function test_plan_cancel_review_request(): void { $data = $this->setup_workflow_data(); $dg = $data['dg']; $lpg = $data['lpg']; $user = $data['user']; $reviewer = $data['reviewer']; $otheruser = $data['otheruser']; $plan = $data['plan']; $tplplan = $data['tplplan']; // Set waiting for review. $tplplan->set('status', plan::STATUS_WAITING_FOR_REVIEW); $tplplan->update(); $plan->set('status', plan::STATUS_WAITING_FOR_REVIEW); $plan->update(); // Foreign user cannot do anything. $this->setUser($otheruser); try { api::plan_cancel_review_request($plan); $this->fail('The user can not read the plan.'); } catch (\required_capability_exception $e) { $this->assertEquals('nopermissions', $e->errorcode); } // Can not change a plan based on a template. $this->setUser($user); try { api::plan_cancel_review_request($tplplan); $this->fail('The plan is based on a template.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/Template plans cannot be reviewed./', $e->getMessage()); } // Can not cancel review request when not waiting for review. $this->setUser($user); $plan->set('status', plan::STATUS_DRAFT); try { api::plan_cancel_review_request($plan); $this->fail('The plan cannot be sent for review at this stage.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/The plan review cannot be cancelled at this stage./', $e->getMessage()); } // Can not cancel review request when not waiting for review. $this->setUser($user); $plan->set('status', plan::STATUS_IN_REVIEW); try { api::plan_cancel_review_request($plan); $this->fail('The plan review cannot be cancelled at this stage.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/The plan review cannot be cancelled at this stage./', $e->getMessage()); } // Can not cancel review request when not waiting for review. $this->setUser($user); $plan->set('status', plan::STATUS_ACTIVE); try { api::plan_cancel_review_request($plan); $this->fail('The plan review cannot be cancelled at this stage.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/The plan review cannot be cancelled at this stage./', $e->getMessage()); } // Can not cancel review request when not waiting for review. $this->setUser($user); $plan->set('status', plan::STATUS_COMPLETE); try { api::plan_cancel_review_request($plan); $this->fail('The plan review cannot be cancelled at this stage.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/The plan review cannot be cancelled at this stage./', $e->getMessage()); } // Cancelling as a reviewer. $this->setUser($reviewer); $plan->set('status', plan::STATUS_WAITING_FOR_REVIEW); try { api::plan_cancel_review_request($plan); $this->fail('The user can not cancel a review request.'); } catch (\required_capability_exception $e) { $this->assertEquals('nopermissions', $e->errorcode); } // Cancelling review request. $this->setUser($user); api::plan_cancel_review_request($plan); $plan->read(); $this->assertEquals(plan::STATUS_DRAFT, $plan->get('status')); // Cancelling review request by ID. $plan->set('status', plan::STATUS_WAITING_FOR_REVIEW); $plan->update(); api::plan_cancel_review_request($plan->get('id')); $plan->read(); $this->assertEquals(plan::STATUS_DRAFT, $plan->get('status')); } /** * Testing starting the review. */ public function test_plan_start_review(): void { $data = $this->setup_workflow_data(); $dg = $data['dg']; $lpg = $data['lpg']; $user = $data['user']; $reviewer = $data['reviewer']; $otheruser = $data['otheruser']; $plan = $data['plan']; $tplplan = $data['tplplan']; // Set waiting for review. $tplplan->set('status', plan::STATUS_WAITING_FOR_REVIEW); $tplplan->update(); $plan->set('status', plan::STATUS_WAITING_FOR_REVIEW); $plan->update(); // Foreign user cannot do anything. $this->setUser($otheruser); try { api::plan_start_review($plan); $this->fail('The user can not read the plan.'); } catch (\required_capability_exception $e) { $this->assertEquals('nopermissions', $e->errorcode); } // Can not change a plan based on a template. $this->setUser($reviewer); try { api::plan_start_review($tplplan); $this->fail('The plan is based on a template.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/Template plans cannot be reviewed./', $e->getMessage()); } // Can not start a review when not waiting for review. $this->setUser($reviewer); $plan->set('status', plan::STATUS_DRAFT); try { api::plan_start_review($plan); $this->fail('The plan review cannot be started at this stage.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/The plan review cannot be started at this stage./', $e->getMessage()); } // Can not start a review when not waiting for review. $this->setUser($reviewer); $plan->set('status', plan::STATUS_IN_REVIEW); try { api::plan_start_review($plan); $this->fail('The plan review cannot be started at this stage.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/The plan review cannot be started at this stage./', $e->getMessage()); } // Can not start a review when not waiting for review. $this->setUser($reviewer); $plan->set('status', plan::STATUS_ACTIVE); try { api::plan_start_review($plan); $this->fail('The plan review cannot be started at this stage.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/The plan review cannot be started at this stage./', $e->getMessage()); } // Can not start a review when not waiting for review. $this->setUser($reviewer); $plan->set('status', plan::STATUS_COMPLETE); try { api::plan_start_review($plan); $this->fail('The plan review cannot be started at this stage.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/The plan review cannot be started at this stage./', $e->getMessage()); } // Starting as the owner. $this->setUser($user); $plan->set('status', plan::STATUS_WAITING_FOR_REVIEW); try { api::plan_start_review($plan); $this->fail('The user can not start a review.'); } catch (\required_capability_exception $e) { $this->assertEquals('nopermissions', $e->errorcode); } // Starting review. $this->setUser($reviewer); api::plan_start_review($plan); $plan->read(); $this->assertEquals(plan::STATUS_IN_REVIEW, $plan->get('status')); $this->assertEquals($reviewer->id, $plan->get('reviewerid')); // Starting review by ID. $plan->set('status', plan::STATUS_WAITING_FOR_REVIEW); $plan->set('reviewerid', null); $plan->update(); api::plan_start_review($plan->get('id')); $plan->read(); $this->assertEquals(plan::STATUS_IN_REVIEW, $plan->get('status')); $this->assertEquals($reviewer->id, $plan->get('reviewerid')); } /** * Testing stopping the review. */ public function test_plan_stop_review(): void { $data = $this->setup_workflow_data(); $dg = $data['dg']; $lpg = $data['lpg']; $user = $data['user']; $reviewer = $data['reviewer']; $otheruser = $data['otheruser']; $plan = $data['plan']; $tplplan = $data['tplplan']; // Set waiting for review. $tplplan->set('status', plan::STATUS_IN_REVIEW); $tplplan->update(); $plan->set('status', plan::STATUS_IN_REVIEW); $plan->update(); // Foreign user cannot do anything. $this->setUser($otheruser); try { api::plan_stop_review($plan); $this->fail('The user can not read the plan.'); } catch (\required_capability_exception $e) { $this->assertEquals('nopermissions', $e->errorcode); } // Can not change a plan based on a template. $this->setUser($reviewer); try { api::plan_stop_review($tplplan); $this->fail('The plan is based on a template.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/Template plans cannot be reviewed./', $e->getMessage()); } // Can not stop a review whe not in review. $this->setUser($reviewer); $plan->set('status', plan::STATUS_DRAFT); try { api::plan_stop_review($plan); $this->fail('The plan review cannot be stopped at this stage.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/The plan review cannot be stopped at this stage./', $e->getMessage()); } // Can not stop a review whe not in review. $this->setUser($reviewer); $plan->set('status', plan::STATUS_WAITING_FOR_REVIEW); try { api::plan_stop_review($plan); $this->fail('The plan review cannot be stopped at this stage.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/The plan review cannot be stopped at this stage./', $e->getMessage()); } // Can not stop a review whe not in review. $this->setUser($reviewer); $plan->set('status', plan::STATUS_ACTIVE); try { api::plan_stop_review($plan); $this->fail('The plan review cannot be stopped at this stage.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/The plan review cannot be stopped at this stage./', $e->getMessage()); } // Can not stop a review whe not in review. $this->setUser($reviewer); $plan->set('status', plan::STATUS_COMPLETE); try { api::plan_stop_review($plan); $this->fail('The plan review cannot be stopped at this stage.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/The plan review cannot be stopped at this stage./', $e->getMessage()); } // Stopping as the owner. $this->setUser($user); $plan->set('status', plan::STATUS_IN_REVIEW); try { api::plan_stop_review($plan); $this->fail('The user can not stop a review.'); } catch (\required_capability_exception $e) { $this->assertEquals('nopermissions', $e->errorcode); } // Stopping review. $this->setUser($reviewer); api::plan_stop_review($plan); $plan->read(); $this->assertEquals(plan::STATUS_DRAFT, $plan->get('status')); // Stopping review by ID. $plan->set('status', plan::STATUS_IN_REVIEW); $plan->update(); api::plan_stop_review($plan->get('id')); $plan->read(); $this->assertEquals(plan::STATUS_DRAFT, $plan->get('status')); } /** * Testing approving the plan. */ public function test_approve_plan(): void { $data = $this->setup_workflow_data(); $dg = $data['dg']; $lpg = $data['lpg']; $user = $data['user']; $reviewer = $data['reviewer']; $otheruser = $data['otheruser']; $plan = $data['plan']; $tplplan = $data['tplplan']; // Set waiting for review. $tplplan->set('status', plan::STATUS_IN_REVIEW); $tplplan->update(); $plan->set('status', plan::STATUS_IN_REVIEW); $plan->update(); // Foreign user cannot do anything. $this->setUser($otheruser); try { api::approve_plan($plan); $this->fail('The user can not read the plan.'); } catch (\required_capability_exception $e) { $this->assertEquals('nopermissions', $e->errorcode); } // Can not change a plan based on a template. $this->setUser($reviewer); try { api::approve_plan($tplplan); $this->fail('The plan is based on a template.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/Template plans are already approved./', $e->getMessage()); } // Can not approve a plan already approved. $this->setUser($reviewer); $plan->set('status', plan::STATUS_ACTIVE); try { api::approve_plan($plan); $this->fail('The plan cannot be approved at this stage.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/The plan cannot be approved at this stage./', $e->getMessage()); } // Can not approve a plan already approved. $this->setUser($reviewer); $plan->set('status', plan::STATUS_COMPLETE); try { api::approve_plan($plan); $this->fail('The plan cannot be approved at this stage.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/The plan cannot be approved at this stage./', $e->getMessage()); } // Approve as the owner. $this->setUser($user); $plan->set('status', plan::STATUS_IN_REVIEW); try { api::approve_plan($plan); $this->fail('The user can not approve the plan.'); } catch (\required_capability_exception $e) { $this->assertEquals('nopermissions', $e->errorcode); } // Approve plan from in review. $this->setUser($reviewer); api::approve_plan($plan); $plan->read(); $this->assertEquals(plan::STATUS_ACTIVE, $plan->get('status')); // Approve plan by ID. $plan->set('status', plan::STATUS_IN_REVIEW); $plan->update(); api::approve_plan($plan->get('id')); $plan->read(); $this->assertEquals(plan::STATUS_ACTIVE, $plan->get('status')); // Approve plan from draft. $plan->set('status', plan::STATUS_DRAFT); $plan->update(); api::approve_plan($plan); $plan->read(); $this->assertEquals(plan::STATUS_ACTIVE, $plan->get('status')); // Approve plan from waiting for review. $plan->set('status', plan::STATUS_WAITING_FOR_REVIEW); $plan->update(); api::approve_plan($plan); $plan->read(); $this->assertEquals(plan::STATUS_ACTIVE, $plan->get('status')); } /** * Testing stopping the review. */ public function test_unapprove_plan(): void { $data = $this->setup_workflow_data(); $dg = $data['dg']; $lpg = $data['lpg']; $user = $data['user']; $reviewer = $data['reviewer']; $otheruser = $data['otheruser']; $plan = $data['plan']; $tplplan = $data['tplplan']; // Set waiting for review. $tplplan->set('status', plan::STATUS_ACTIVE); $tplplan->update(); $plan->set('status', plan::STATUS_ACTIVE); $plan->update(); // Foreign user cannot do anything. $this->setUser($otheruser); try { api::unapprove_plan($plan); $this->fail('The user can not read the plan.'); } catch (\required_capability_exception $e) { $this->assertEquals('nopermissions', $e->errorcode); } // Can not change a plan based on a template. $this->setUser($reviewer); try { api::unapprove_plan($tplplan); $this->fail('The plan is based on a template.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/Template plans are always approved./', $e->getMessage()); } // Can not unapprove a non-draft plan. $this->setUser($reviewer); $plan->set('status', plan::STATUS_DRAFT); try { api::unapprove_plan($plan); $this->fail('The plan cannot be sent back to draft at this stage.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/The plan cannot be sent back to draft at this stage./', $e->getMessage()); } // Can not unapprove a non-draft plan. $this->setUser($reviewer); $plan->set('status', plan::STATUS_WAITING_FOR_REVIEW); try { api::unapprove_plan($plan); $this->fail('The plan cannot be sent back to draft at this stage.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/The plan cannot be sent back to draft at this stage./', $e->getMessage()); } // Can not unapprove a non-draft plan. $this->setUser($reviewer); $plan->set('status', plan::STATUS_IN_REVIEW); try { api::unapprove_plan($plan); $this->fail('The plan cannot be sent back to draft at this stage.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/The plan cannot be sent back to draft at this stage./', $e->getMessage()); } // Can not unapprove a non-draft plan. $this->setUser($reviewer); $plan->set('status', plan::STATUS_COMPLETE); try { api::unapprove_plan($plan); $this->fail('The plan cannot be sent back to draft at this stage.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/The plan cannot be sent back to draft at this stage./', $e->getMessage()); } // Unapprove as the owner. $this->setUser($user); $plan->set('status', plan::STATUS_ACTIVE); try { api::unapprove_plan($plan); $this->fail('The user can not unapprove the plan.'); } catch (\required_capability_exception $e) { $this->assertEquals('nopermissions', $e->errorcode); } // Unapprove plan. $this->setUser($reviewer); api::unapprove_plan($plan); $plan->read(); $this->assertEquals(plan::STATUS_DRAFT, $plan->get('status')); // Unapprove plan by ID. $plan->set('status', plan::STATUS_ACTIVE); $plan->update(); api::unapprove_plan($plan->get('id')); $plan->read(); $this->assertEquals(plan::STATUS_DRAFT, $plan->get('status')); } /** * Test update plan and the managing of archived user competencies. */ public function test_update_plan_manage_archived_competencies(): void { global $DB; $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $syscontext = \context_system::instance(); // Create users and roles for the test. $user = $dg->create_user(); $manageownrole = $dg->create_role(array( 'name' => 'User manage own', 'shortname' => 'manageown' )); assign_capability('moodle/competency:planmanageowndraft', CAP_ALLOW, $manageownrole, $syscontext->id); assign_capability('moodle/competency:planviewowndraft', CAP_ALLOW, $manageownrole, $syscontext->id); assign_capability('moodle/competency:planmanageown', CAP_ALLOW, $manageownrole, $syscontext->id); assign_capability('moodle/competency:planviewown', CAP_ALLOW, $manageownrole, $syscontext->id); $dg->role_assign($manageownrole, $user->id, $syscontext->id); $this->setUser($user); // Create a framework and assign competencies. $framework = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c3 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); // Create two plans and assign competencies. $plan = $lpg->create_plan(array('userid' => $user->id)); $otherplan = $lpg->create_plan(array('userid' => $user->id)); $lpg->create_plan_competency(array('planid' => $plan->get('id'), 'competencyid' => $c1->get('id'))); $lpg->create_plan_competency(array('planid' => $plan->get('id'), 'competencyid' => $c2->get('id'))); $lpg->create_plan_competency(array('planid' => $plan->get('id'), 'competencyid' => $c3->get('id'))); $lpg->create_plan_competency(array('planid' => $otherplan->get('id'), 'competencyid' => $c1->get('id'))); $uclist = array( $lpg->create_user_competency(array( 'userid' => $user->id, 'competencyid' => $c1->get('id'), 'proficiency' => true, 'grade' => 1 )), $lpg->create_user_competency(array( 'userid' => $user->id, 'competencyid' => $c2->get('id'), 'proficiency' => false, 'grade' => 2 )) ); // Change status of the plan to complete. $record = $plan->to_record(); $record->status = \core_competency\plan::STATUS_COMPLETE; try { $plan = api::update_plan($record); $this->fail('We cannot complete a plan using api::update_plan().'); } catch (\coding_exception $e) { // All good. } api::complete_plan($plan); // Check that user compretencies are now in user_competency_plan objects and still in user_competency. $this->assertEquals(2, \core_competency\user_competency::count_records()); $this->assertEquals(3, \core_competency\user_competency_plan::count_records()); $usercompetenciesplan = \core_competency\user_competency_plan::get_records(); $this->assertEquals($uclist[0]->get('userid'), $usercompetenciesplan[0]->get('userid')); $this->assertEquals($uclist[0]->get('competencyid'), $usercompetenciesplan[0]->get('competencyid')); $this->assertEquals($uclist[0]->get('proficiency'), (bool) $usercompetenciesplan[0]->get('proficiency')); $this->assertEquals($uclist[0]->get('grade'), $usercompetenciesplan[0]->get('grade')); $this->assertEquals($plan->get('id'), $usercompetenciesplan[0]->get('planid')); $this->assertEquals($uclist[1]->get('userid'), $usercompetenciesplan[1]->get('userid')); $this->assertEquals($uclist[1]->get('competencyid'), $usercompetenciesplan[1]->get('competencyid')); $this->assertEquals($uclist[1]->get('proficiency'), (bool) $usercompetenciesplan[1]->get('proficiency')); $this->assertEquals($uclist[1]->get('grade'), $usercompetenciesplan[1]->get('grade')); $this->assertEquals($plan->get('id'), $usercompetenciesplan[1]->get('planid')); $this->assertEquals($user->id, $usercompetenciesplan[2]->get('userid')); $this->assertEquals($c3->get('id'), $usercompetenciesplan[2]->get('competencyid')); $this->assertNull($usercompetenciesplan[2]->get('proficiency')); $this->assertNull($usercompetenciesplan[2]->get('grade')); $this->assertEquals($plan->get('id'), $usercompetenciesplan[2]->get('planid')); // Change status of the plan to active. $record = $plan->to_record(); $record->status = \core_competency\plan::STATUS_ACTIVE; try { api::update_plan($record); $this->fail('Completed plan can not be edited'); } catch (\coding_exception $e) { // All good. } api::reopen_plan($record->id); // Check that user_competency_plan objects are deleted if the plan status is changed to another status. $this->assertEquals(2, \core_competency\user_competency::count_records()); $this->assertEquals(0, \core_competency\user_competency_plan::count_records()); } /** * Test completing plan does not change the order of competencies. */ public function test_complete_plan_doesnot_change_order(): void { global $DB; $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $syscontext = \context_system::instance(); // Create users and roles for the test. $user = $dg->create_user(); // Create a framework and assign competencies. $framework = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c3 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); // Create two plans and assign competencies. $plan = $lpg->create_plan(array('userid' => $user->id)); $lpg->create_plan_competency(array('planid' => $plan->get('id'), 'competencyid' => $c1->get('id'))); $lpg->create_plan_competency(array('planid' => $plan->get('id'), 'competencyid' => $c2->get('id'))); $lpg->create_plan_competency(array('planid' => $plan->get('id'), 'competencyid' => $c3->get('id'))); // Changing competencies order in plan competency. api::reorder_plan_competency($plan->get('id'), $c1->get('id'), $c3->get('id')); $competencies = api::list_plan_competencies($plan); $this->assertEquals($c2->get('id'), $competencies[0]->competency->get('id')); $this->assertEquals($c3->get('id'), $competencies[1]->competency->get('id')); $this->assertEquals($c1->get('id'), $competencies[2]->competency->get('id')); // Completing plan. api::complete_plan($plan); $competencies = api::list_plan_competencies($plan); // Completing plan does not change order. $this->assertEquals($c2->get('id'), $competencies[0]->competency->get('id')); $this->assertEquals($c3->get('id'), $competencies[1]->competency->get('id')); $this->assertEquals($c1->get('id'), $competencies[2]->competency->get('id')); // Testing plan based on template. $template = $lpg->create_template(); $framework = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c3 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $lpg->create_template_competency(array( 'templateid' => $template->get('id'), 'competencyid' => $c1->get('id') )); $lpg->create_template_competency(array( 'templateid' => $template->get('id'), 'competencyid' => $c2->get('id') )); $lpg->create_template_competency(array( 'templateid' => $template->get('id'), 'competencyid' => $c3->get('id') )); // Reorder competencies in template. api::reorder_template_competency($template->get('id'), $c1->get('id'), $c3->get('id')); // Create plan from template. $plan = api::create_plan_from_template($template->get('id'), $user->id); $competencies = api::list_plan_competencies($plan); // Completing plan does not change order. $this->assertEquals($c2->get('id'), $competencies[0]->competency->get('id')); $this->assertEquals($c3->get('id'), $competencies[1]->competency->get('id')); $this->assertEquals($c1->get('id'), $competencies[2]->competency->get('id')); // Completing plan. api::complete_plan($plan); $competencies = api::list_plan_competencies($plan); // Completing plan does not change order. $this->assertEquals($c2->get('id'), $competencies[0]->competency->get('id')); $this->assertEquals($c3->get('id'), $competencies[1]->competency->get('id')); $this->assertEquals($c1->get('id'), $competencies[2]->competency->get('id')); } /** * Test remove plan and the managing of archived user competencies. */ public function test_delete_plan_manage_archived_competencies(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $syscontext = \context_system::instance(); // Create user and role for the test. $user = $dg->create_user(); $managerole = $dg->create_role(array( 'name' => 'User manage own', 'shortname' => 'manageown' )); assign_capability('moodle/competency:planmanageowndraft', CAP_ALLOW, $managerole, $syscontext->id); assign_capability('moodle/competency:planmanageown', CAP_ALLOW, $managerole, $syscontext->id); $dg->role_assign($managerole, $user->id, $syscontext->id); $this->setUser($user); // Create a framework and assign competencies. $framework = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c3 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); // Create completed plan with records in user_competency. $completedplan = $lpg->create_plan(array('userid' => $user->id, 'status' => \core_competency\plan::STATUS_COMPLETE)); $lpg->create_plan_competency(array('planid' => $completedplan->get('id'), 'competencyid' => $c1->get('id'))); $lpg->create_plan_competency(array('planid' => $completedplan->get('id'), 'competencyid' => $c2->get('id'))); $uc1 = $lpg->create_user_competency(array('userid' => $user->id, 'competencyid' => $c1->get('id'))); $uc2 = $lpg->create_user_competency(array('userid' => $user->id, 'competencyid' => $c2->get('id'))); $ucp1 = $lpg->create_user_competency_plan(array('userid' => $user->id, 'competencyid' => $c1->get('id'), 'planid' => $completedplan->get('id'))); $ucp2 = $lpg->create_user_competency_plan(array('userid' => $user->id, 'competencyid' => $c2->get('id'), 'planid' => $completedplan->get('id'))); api::delete_plan($completedplan->get('id')); // Check that achived user competencies are deleted. $this->assertEquals(0, \core_competency\plan::count_records()); $this->assertEquals(2, \core_competency\user_competency::count_records()); $this->assertEquals(0, \core_competency\user_competency_plan::count_records()); } /** * Test listing of plan competencies. */ public function test_list_plan_competencies_manage_archived_competencies(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $syscontext = \context_system::instance(); // Create user and role for the test. $user = $dg->create_user(); $viewrole = $dg->create_role(array( 'name' => 'User view', 'shortname' => 'view' )); assign_capability('moodle/competency:planviewdraft', CAP_ALLOW, $viewrole, $syscontext->id); assign_capability('moodle/competency:planview', CAP_ALLOW, $viewrole, $syscontext->id); $dg->role_assign($viewrole, $user->id, $syscontext->id); $this->setUser($user); // Create a framework and assign competencies. $framework = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c3 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); // Create draft plan with records in user_competency. $draftplan = $lpg->create_plan(array('userid' => $user->id)); $lpg->create_plan_competency(array('planid' => $draftplan->get('id'), 'competencyid' => $c1->get('id'))); $lpg->create_plan_competency(array('planid' => $draftplan->get('id'), 'competencyid' => $c2->get('id'))); $lpg->create_plan_competency(array('planid' => $draftplan->get('id'), 'competencyid' => $c3->get('id'))); $uc1 = $lpg->create_user_competency(array('userid' => $user->id, 'competencyid' => $c1->get('id'))); $uc2 = $lpg->create_user_competency(array('userid' => $user->id, 'competencyid' => $c2->get('id'))); // Check that user_competency objects are returned when plan status is not complete. $plancompetencies = api::list_plan_competencies($draftplan); $this->assertCount(3, $plancompetencies); $this->assertInstanceOf('\core_competency\user_competency', $plancompetencies[0]->usercompetency); $this->assertEquals($uc1->get('id'), $plancompetencies[0]->usercompetency->get('id')); $this->assertNull($plancompetencies[0]->usercompetencyplan); $this->assertInstanceOf('\core_competency\user_competency', $plancompetencies[1]->usercompetency); $this->assertEquals($uc2->get('id'), $plancompetencies[1]->usercompetency->get('id')); $this->assertNull($plancompetencies[1]->usercompetencyplan); $this->assertInstanceOf('\core_competency\user_competency', $plancompetencies[2]->usercompetency); $this->assertEquals(0, $plancompetencies[2]->usercompetency->get('id')); $this->assertNull($plancompetencies[2]->usercompetencyplan); // Create completed plan with records in user_competency_plan. $completedplan = $lpg->create_plan(array('userid' => $user->id, 'status' => \core_competency\plan::STATUS_COMPLETE)); $pc1 = $lpg->create_plan_competency(array('planid' => $completedplan->get('id'), 'competencyid' => $c1->get('id'))); $pc2 = $lpg->create_plan_competency(array('planid' => $completedplan->get('id'), 'competencyid' => $c2->get('id'))); $pc3 = $lpg->create_plan_competency(array('planid' => $completedplan->get('id'), 'competencyid' => $c3->get('id'))); $ucp1 = $lpg->create_user_competency_plan(array('userid' => $user->id, 'competencyid' => $c1->get('id'), 'planid' => $completedplan->get('id'))); $ucp2 = $lpg->create_user_competency_plan(array('userid' => $user->id, 'competencyid' => $c2->get('id'), 'planid' => $completedplan->get('id'))); $ucp3 = $lpg->create_user_competency_plan(array('userid' => $user->id, 'competencyid' => $c3->get('id'), 'planid' => $completedplan->get('id'))); // Check that user_competency_plan objects are returned when plan status is complete. $plancompetencies = api::list_plan_competencies($completedplan); $this->assertCount(3, $plancompetencies); $this->assertInstanceOf('\core_competency\user_competency_plan', $plancompetencies[0]->usercompetencyplan); $this->assertEquals($ucp1->get('id'), $plancompetencies[0]->usercompetencyplan->get('id')); $this->assertNull($plancompetencies[0]->usercompetency); $this->assertInstanceOf('\core_competency\user_competency_plan', $plancompetencies[1]->usercompetencyplan); $this->assertEquals($ucp2->get('id'), $plancompetencies[1]->usercompetencyplan->get('id')); $this->assertNull($plancompetencies[1]->usercompetency); $this->assertInstanceOf('\core_competency\user_competency_plan', $plancompetencies[2]->usercompetencyplan); $this->assertEquals($ucp3->get('id'), $plancompetencies[2]->usercompetencyplan->get('id')); $this->assertNull($plancompetencies[2]->usercompetency); } public function test_create_template_cohort(): void { $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $c1 = $dg->create_cohort(); $c2 = $dg->create_cohort(); $t1 = $lpg->create_template(); $t2 = $lpg->create_template(); $this->assertEquals(0, \core_competency\template_cohort::count_records()); // Create two relations with mixed parameters. $result = api::create_template_cohort($t1->get('id'), $c1->id); $result = api::create_template_cohort($t1, $c2); $this->assertEquals(2, \core_competency\template_cohort::count_records()); $this->assertInstanceOf('core_competency\template_cohort', $result); $this->assertEquals($c2->id, $result->get('cohortid')); $this->assertEquals($t1->get('id'), $result->get('templateid')); $this->assertEquals(2, \core_competency\template_cohort::count_records_select('templateid = :id', array('id' => $t1->get('id')))); $this->assertEquals(0, \core_competency\template_cohort::count_records_select('templateid = :id', array('id' => $t2->get('id')))); } public function test_create_template_cohort_permissions(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $cat = $dg->create_category(); $catcontext = \context_coursecat::instance($cat->id); $syscontext = \context_system::instance(); $user = $dg->create_user(); $role = $dg->create_role(); assign_capability('moodle/competency:templatemanage', CAP_ALLOW, $role, $syscontext->id, true); $dg->role_assign($role, $user->id, $syscontext->id); $cohortrole = $dg->create_role(); assign_capability('moodle/cohort:view', CAP_ALLOW, $cohortrole, $syscontext->id, true); accesslib_clear_all_caches_for_unit_testing(); $c1 = $dg->create_cohort(); $c2 = $dg->create_cohort(array('visible' => 0, 'contextid' => $catcontext->id)); $t1 = $lpg->create_template(); $this->assertEquals(0, \core_competency\template_cohort::count_records()); $this->setUser($user); $result = api::create_template_cohort($t1, $c1); $this->assertInstanceOf('core_competency\\template_cohort', $result); try { $result = api::create_template_cohort($t1, $c2); $this->fail('Permission required.'); } catch (\required_capability_exception $e) { // That's what should happen. } // Try again with the right permissions. $dg->role_assign($cohortrole, $user->id, $catcontext->id); accesslib_clear_all_caches_for_unit_testing(); $result = api::create_template_cohort($t1, $c2); $this->assertInstanceOf('core_competency\\template_cohort', $result); } public function test_reorder_template_competencies_permissions(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $cat = $dg->create_category(); $catcontext = \context_coursecat::instance($cat->id); $syscontext = \context_system::instance(); $user = $dg->create_user(); $role = $dg->create_role(); assign_capability('moodle/competency:templatemanage', CAP_ALLOW, $role, $syscontext->id, true); $dg->role_assign($role, $user->id, $syscontext->id); // Create a template. $template = $lpg->create_template(array('contextid' => $catcontext->id)); // Create a competency framework. $framework = $lpg->create_framework(array('contextid' => $catcontext->id)); // Create competencies. $competency1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $competency2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); // Add the competencies. $lpg->create_template_competency(array( 'templateid' => $template->get('id'), 'competencyid' => $competency1->get('id') )); $lpg->create_template_competency(array( 'templateid' => $template->get('id'), 'competencyid' => $competency2->get('id') )); $this->setUser($user); // Can reorder competencies with system context permissions in category context. $result = api::reorder_template_competency($template->get('id'), $competency2->get('id'), $competency1->get('id')); $this->assertTrue($result); unassign_capability('moodle/competency:templatemanage', $role, $syscontext->id); accesslib_clear_all_caches_for_unit_testing(); try { api::reorder_template_competency($template->get('id'), $competency2->get('id'), $competency1->get('id')); $this->fail('Exception expected due to not permissions to manage template competencies'); } catch (\required_capability_exception $e) { $this->assertEquals('nopermissions', $e->errorcode); } // Giving permissions in category context. assign_capability('moodle/competency:templatemanage', CAP_ALLOW, $role, $catcontext->id, true); $dg->role_assign($role, $user->id, $catcontext->id); // User with templatemanage capability in category context can reorder competencies in temple. $result = api::reorder_template_competency($template->get('id'), $competency1->get('id'), $competency2->get('id')); $this->assertTrue($result); // Removing templatemanage capability in category context. unassign_capability('moodle/competency:templatemanage', $role, $catcontext->id); accesslib_clear_all_caches_for_unit_testing(); try { api::reorder_template_competency($template->get('id'), $competency2->get('id'), $competency1->get('id')); $this->fail('Exception expected due to not permissions to manage template competencies'); } catch (\required_capability_exception $e) { $this->assertEquals('nopermissions', $e->errorcode); } } public function test_delete_template(): void { $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $c1 = $dg->create_cohort(); $c2 = $dg->create_cohort(); $template = $lpg->create_template(); $id = $template->get('id'); // Create 2 template cohorts. $tc1 = $lpg->create_template_cohort(array('templateid' => $template->get('id'), 'cohortid' => $c1->id)); $tc1 = $lpg->create_template_cohort(array('templateid' => $template->get('id'), 'cohortid' => $c2->id)); // Check pre-test. $this->assertTrue(\core_competency\template::record_exists($id)); $this->assertEquals(2, \core_competency\template_cohort::count_records(array('templateid' => $id))); $result = api::delete_template($template->get('id')); $this->assertTrue($result); // Check that the template deos not exist anymore. $this->assertFalse(\core_competency\template::record_exists($id)); // Test if associated cohorts are also deleted. $this->assertEquals(0, \core_competency\template_cohort::count_records(array('templateid' => $id))); } public function test_delete_template_cohort(): void { $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $c1 = $dg->create_cohort(); $c2 = $dg->create_cohort(); $t1 = $lpg->create_template(); $t2 = $lpg->create_template(); $tc1 = $lpg->create_template_cohort(array('templateid' => $t1->get('id'), 'cohortid' => $c1->id)); $tc1 = $lpg->create_template_cohort(array('templateid' => $t2->get('id'), 'cohortid' => $c2->id)); $this->assertEquals(2, \core_competency\template_cohort::count_records()); $this->assertEquals(1, \core_competency\template_cohort::count_records_select('templateid = :id', array('id' => $t1->get('id')))); $this->assertEquals(1, \core_competency\template_cohort::count_records_select('templateid = :id', array('id' => $t2->get('id')))); // Delete existing. $result = api::delete_template_cohort($t1->get('id'), $c1->id); $this->assertTrue($result); $this->assertEquals(1, \core_competency\template_cohort::count_records()); $this->assertEquals(0, \core_competency\template_cohort::count_records_select('templateid = :id', array('id' => $t1->get('id')))); $this->assertEquals(1, \core_competency\template_cohort::count_records_select('templateid = :id', array('id' => $t2->get('id')))); // Delete non-existant. $result = api::delete_template_cohort($t1->get('id'), $c1->id); $this->assertTrue($result); $this->assertEquals(1, \core_competency\template_cohort::count_records()); $this->assertEquals(0, \core_competency\template_cohort::count_records_select('templateid = :id', array('id' => $t1->get('id')))); $this->assertEquals(1, \core_competency\template_cohort::count_records_select('templateid = :id', array('id' => $t2->get('id')))); } public function test_add_evidence_log(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $u1 = $dg->create_user(); $u1ctx = \context_user::instance($u1->id); $f1 = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); // Creating a standard evidence with minimal information. $evidence = api::add_evidence($u1->id, $c1->get('id'), $u1ctx->id, \core_competency\evidence::ACTION_LOG, 'invaliddata', 'error'); $evidence->read(); $uc = \core_competency\user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c1->get('id'))); $this->assertEquals(\core_competency\user_competency::STATUS_IDLE, $uc->get('status')); $this->assertSame(null, $uc->get('grade')); $this->assertSame(null, $uc->get('proficiency')); $this->assertEquals($uc->get('id'), $evidence->get('usercompetencyid')); $this->assertEquals($u1ctx->id, $evidence->get('contextid')); $this->assertEquals(\core_competency\evidence::ACTION_LOG, $evidence->get('action')); $this->assertEquals('invaliddata', $evidence->get('descidentifier')); $this->assertEquals('error', $evidence->get('desccomponent')); $this->assertSame(null, $evidence->get('desca')); $this->assertSame(null, $evidence->get('url')); $this->assertSame(null, $evidence->get('grade')); $this->assertSame(null, $evidence->get('actionuserid')); // Creating a standard evidence with more information. $evidence = api::add_evidence($u1->id, $c1->get('id'), $u1ctx->id, \core_competency\evidence::ACTION_LOG, 'invaliddata', 'error', '$a', false, 'http://moodle.org', null, 2, 'The evidence of prior learning were reviewed.'); $evidence->read(); $uc = \core_competency\user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c1->get('id'))); $this->assertEquals(\core_competency\user_competency::STATUS_IDLE, $uc->get('status')); $this->assertSame(null, $uc->get('grade')); $this->assertSame(null, $uc->get('proficiency')); $this->assertEquals($uc->get('id'), $evidence->get('usercompetencyid')); $this->assertEquals($u1ctx->id, $evidence->get('contextid')); $this->assertEquals(\core_competency\evidence::ACTION_LOG, $evidence->get('action')); $this->assertEquals('invaliddata', $evidence->get('descidentifier')); $this->assertEquals('error', $evidence->get('desccomponent')); $this->assertEquals('$a', $evidence->get('desca')); $this->assertEquals('http://moodle.org', $evidence->get('url')); $this->assertSame(null, $evidence->get('grade')); $this->assertEquals(2, $evidence->get('actionuserid')); $this->assertSame('The evidence of prior learning were reviewed.', $evidence->get('note')); // Creating a standard evidence and send for review. $evidence = api::add_evidence($u1->id, $c2->get('id'), $u1ctx->id, \core_competency\evidence::ACTION_LOG, 'invaliddata', 'error', null, true); $evidence->read(); $uc = \core_competency\user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c2->get('id'))); $this->assertEquals(\core_competency\user_competency::STATUS_WAITING_FOR_REVIEW, $uc->get('status')); // Trying to pass a grade should fail. try { $evidence = api::add_evidence($u1->id, $c1->get('id'), $u1ctx->id, \core_competency\evidence::ACTION_LOG, 'invaliddata', 'error', null, false, null, 1); $this->fail('A grade can not be set'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/grade MUST NOT be set/', $e->getMessage()); } } public function test_add_evidence_complete(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $u1 = $dg->create_user(); $u1ctx = \context_user::instance($u1->id); $scale = $dg->create_scale(array('scale' => 'A,B,C,D')); $scaleconfig = array(array('scaleid' => $scale->id)); $scaleconfig[] = array('name' => 'B', 'id' => 2, 'scaledefault' => 1, 'proficient' => 0); $scaleconfig[] = array('name' => 'C', 'id' => 3, 'scaledefault' => 0, 'proficient' => 1); $scaleconfig[] = array('name' => 'D', 'id' => 4, 'scaledefault' => 0, 'proficient' => 1); $c2scaleconfig = array(array('scaleid' => $scale->id)); $c2scaleconfig[] = array('name' => 'B', 'id' => 2, 'scaledefault' => 0, 'proficient' => 1); $c2scaleconfig[] = array('name' => 'C', 'id' => 3, 'scaledefault' => 0, 'proficient' => 0); $c2scaleconfig[] = array('name' => 'D', 'id' => 4, 'scaledefault' => 1, 'proficient' => 1); $f1 = $lpg->create_framework(array('scaleid' => $scale->id, 'scaleconfiguration' => $scaleconfig)); $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'scaleid' => $scale->id, 'scaleconfiguration' => $c2scaleconfig)); $c3 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); // Creating an evidence with minimal information. $evidence = api::add_evidence($u1->id, $c1->get('id'), $u1ctx->id, \core_competency\evidence::ACTION_COMPLETE, 'invaliddata', 'error'); $evidence->read(); $uc = \core_competency\user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c1->get('id'))); $this->assertEquals(\core_competency\user_competency::STATUS_IDLE, $uc->get('status')); $this->assertEquals(2, $uc->get('grade')); // The grade has been set automatically to the framework default. $this->assertEquals(0, $uc->get('proficiency')); $this->assertEquals($uc->get('id'), $evidence->get('usercompetencyid')); $this->assertEquals($u1ctx->id, $evidence->get('contextid')); $this->assertEquals(\core_competency\evidence::ACTION_COMPLETE, $evidence->get('action')); $this->assertEquals('invaliddata', $evidence->get('descidentifier')); $this->assertEquals('error', $evidence->get('desccomponent')); $this->assertSame(null, $evidence->get('desca')); $this->assertSame(null, $evidence->get('url')); $this->assertEquals(2, $evidence->get('grade')); $this->assertSame(null, $evidence->get('actionuserid')); // Creating an evidence complete on competency with custom scale. $evidence = api::add_evidence($u1->id, $c2->get('id'), $u1ctx->id, \core_competency\evidence::ACTION_COMPLETE, 'invaliddata', 'error'); $evidence->read(); $uc = \core_competency\user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c2->get('id'))); $this->assertEquals(\core_competency\user_competency::STATUS_IDLE, $uc->get('status')); $this->assertEquals(4, $uc->get('grade')); // The grade has been set automatically to the competency default. $this->assertEquals(true, $uc->get('proficiency')); $this->assertEquals($uc->get('id'), $evidence->get('usercompetencyid')); $this->assertEquals($u1ctx->id, $evidence->get('contextid')); $this->assertEquals(\core_competency\evidence::ACTION_COMPLETE, $evidence->get('action')); $this->assertEquals('invaliddata', $evidence->get('descidentifier')); $this->assertEquals('error', $evidence->get('desccomponent')); $this->assertSame(null, $evidence->get('desca')); $this->assertSame(null, $evidence->get('url')); $this->assertEquals(4, $evidence->get('grade')); $this->assertSame(null, $evidence->get('actionuserid')); // Creating an evidence complete on a user competency with an existing grade. $uc = $lpg->create_user_competency(array('userid' => $u1->id, 'competencyid' => $c3->get('id'), 'grade' => 1, 'proficiency' => 0)); $this->assertEquals(1, $uc->get('grade')); $this->assertEquals(0, $uc->get('proficiency')); $evidence = api::add_evidence($u1->id, $c3->get('id'), $u1ctx->id, \core_competency\evidence::ACTION_COMPLETE, 'invaliddata', 'error'); $evidence->read(); $uc->read(); $this->assertEquals(\core_competency\user_competency::STATUS_IDLE, $uc->get('status')); $this->assertEquals(1, $uc->get('grade')); // The grade has not been changed. $this->assertEquals(0, $uc->get('proficiency')); $this->assertEquals($uc->get('id'), $evidence->get('usercompetencyid')); $this->assertEquals($u1ctx->id, $evidence->get('contextid')); $this->assertEquals(\core_competency\evidence::ACTION_COMPLETE, $evidence->get('action')); $this->assertEquals('invaliddata', $evidence->get('descidentifier')); $this->assertEquals('error', $evidence->get('desccomponent')); $this->assertSame(null, $evidence->get('desca')); $this->assertSame(null, $evidence->get('url')); $this->assertEquals(2, $evidence->get('grade')); // The complete grade has been set. $this->assertSame(null, $evidence->get('actionuserid')); // Creating a standard evidence and send for review. $evidence = api::add_evidence($u1->id, $c2->get('id'), $u1ctx->id, \core_competency\evidence::ACTION_COMPLETE, 'invaliddata', 'error', null, true); $evidence->read(); $uc = \core_competency\user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c2->get('id'))); $this->assertEquals(\core_competency\user_competency::STATUS_WAITING_FOR_REVIEW, $uc->get('status')); // Trying to pass a grade should throw an exception. try { api::add_evidence($u1->id, $c2->get('id'), $u1ctx->id, \core_competency\evidence::ACTION_COMPLETE, 'invaliddata', 'error', null, false, null, 1); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/grade MUST NOT be set/', $e->getMessage()); } } public function test_add_evidence_override(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $u1 = $dg->create_user(); $u1ctx = \context_user::instance($u1->id); $f1 = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); // Creating an evidence with minimal information. $evidence = api::add_evidence($u1->id, $c1->get('id'), $u1ctx->id, \core_competency\evidence::ACTION_OVERRIDE, 'invaliddata', 'error'); $evidence->read(); $uc = \core_competency\user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c1->get('id'))); $this->assertEquals(\core_competency\user_competency::STATUS_IDLE, $uc->get('status')); $this->assertSame(null, $uc->get('grade')); // We overrode with 'null'. $this->assertSame(null, $uc->get('proficiency')); $this->assertEquals($uc->get('id'), $evidence->get('usercompetencyid')); $this->assertEquals($u1ctx->id, $evidence->get('contextid')); $this->assertEquals(\core_competency\evidence::ACTION_OVERRIDE, $evidence->get('action')); $this->assertEquals('invaliddata', $evidence->get('descidentifier')); $this->assertEquals('error', $evidence->get('desccomponent')); $this->assertSame(null, $evidence->get('desca')); $this->assertSame(null, $evidence->get('url')); $this->assertSame(null, $evidence->get('grade')); // We overrode with 'null'. $this->assertSame(null, $evidence->get('actionuserid')); // Creating an evidence with a grade information. $evidence = api::add_evidence($u1->id, $c1->get('id'), $u1ctx->id, \core_competency\evidence::ACTION_OVERRIDE, 'invaliddata', 'error', null, false, null, 3); $evidence->read(); $uc = \core_competency\user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c1->get('id'))); $this->assertEquals(\core_competency\user_competency::STATUS_IDLE, $uc->get('status')); $this->assertEquals(3, $uc->get('grade')); $this->assertEquals(true, $uc->get('proficiency')); $this->assertEquals($uc->get('id'), $evidence->get('usercompetencyid')); $this->assertEquals($u1ctx->id, $evidence->get('contextid')); $this->assertEquals(\core_competency\evidence::ACTION_OVERRIDE, $evidence->get('action')); $this->assertEquals('invaliddata', $evidence->get('descidentifier')); $this->assertEquals('error', $evidence->get('desccomponent')); $this->assertSame(null, $evidence->get('desca')); $this->assertSame(null, $evidence->get('url')); $this->assertEquals(3, $evidence->get('grade')); $this->assertSame(null, $evidence->get('actionuserid')); // Creating an evidence with another grade information. $evidence = api::add_evidence($u1->id, $c1->get('id'), $u1ctx->id, \core_competency\evidence::ACTION_OVERRIDE, 'invaliddata', 'error', null, false, null, 1); $evidence->read(); $uc = \core_competency\user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c1->get('id'))); $this->assertEquals(\core_competency\user_competency::STATUS_IDLE, $uc->get('status')); $this->assertEquals(1, $uc->get('grade')); $this->assertEquals(0, $uc->get('proficiency')); $this->assertEquals($uc->get('id'), $evidence->get('usercompetencyid')); $this->assertEquals($u1ctx->id, $evidence->get('contextid')); $this->assertEquals(\core_competency\evidence::ACTION_OVERRIDE, $evidence->get('action')); $this->assertEquals('invaliddata', $evidence->get('descidentifier')); $this->assertEquals('error', $evidence->get('desccomponent')); $this->assertSame(null, $evidence->get('desca')); $this->assertSame(null, $evidence->get('url')); $this->assertEquals(1, $evidence->get('grade')); $this->assertSame(null, $evidence->get('actionuserid')); // Creating reverting the grade and send for review. $evidence = api::add_evidence($u1->id, $c1->get('id'), $u1ctx->id, \core_competency\evidence::ACTION_OVERRIDE, 'invaliddata', 'error', null, true); $evidence->read(); $uc = \core_competency\user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c1->get('id'))); $this->assertSame(null, $uc->get('grade')); $this->assertSame(null, $uc->get('proficiency')); $this->assertEquals(\core_competency\user_competency::STATUS_WAITING_FOR_REVIEW, $uc->get('status')); $this->assertSame(null, $evidence->get('grade')); } public function test_add_evidence_and_send_for_review(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $u1 = $dg->create_user(); $u1ctx = \context_user::instance($u1->id); $f1 = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); // Non-existing user competencies are created up for review. $evidence = api::add_evidence($u1->id, $c1->get('id'), $u1ctx->id, \core_competency\evidence::ACTION_LOG, 'invaliddata', 'error', null, true); $uc = \core_competency\user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c1->get('id'))); $this->assertEquals(\core_competency\user_competency::STATUS_WAITING_FOR_REVIEW, $uc->get('status')); // Existing user competencies sent for review don't change. $evidence = api::add_evidence($u1->id, $c1->get('id'), $u1ctx->id, \core_competency\evidence::ACTION_LOG, 'invaliddata', 'error', null, true); $uc->read(); $this->assertEquals(\core_competency\user_competency::STATUS_WAITING_FOR_REVIEW, $uc->get('status')); // A user competency with a status non-idle won't change. $uc->set('status', \core_competency\user_competency::STATUS_IN_REVIEW); $uc->update(); $evidence = api::add_evidence($u1->id, $c1->get('id'), $u1ctx->id, \core_competency\evidence::ACTION_LOG, 'invaliddata', 'error', null, true); $uc->read(); $this->assertEquals(\core_competency\user_competency::STATUS_IN_REVIEW, $uc->get('status')); } /** * Test add evidence for existing user_competency. */ public function test_add_evidence_existing_user_competency(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $syscontext = \context_system::instance(); // Create users. $user = $dg->create_user(); $this->setUser($user); // Create a framework and assign competencies. $framework = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c3 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $uc = $lpg->create_user_competency(array('userid' => $user->id, 'competencyid' => $c1->get('id'))); $this->assertSame(null, $uc->get('grade')); $this->assertSame(null, $uc->get('proficiency')); // Create an evidence and check it was created with the right usercomptencyid and information. $evidence = api::add_evidence($user->id, $c1->get('id'), $syscontext->id, \core_competency\evidence::ACTION_OVERRIDE, 'invalidevidencedesc', 'core_competency', array('a' => 'b'), false, 'http://moodle.org', 1, 2); $this->assertEquals(1, \core_competency\evidence::count_records()); $evidence->read(); $uc->read(); $this->assertEquals($uc->get('id'), $evidence->get('usercompetencyid')); $this->assertEquals('invalidevidencedesc', $evidence->get('descidentifier')); $this->assertEquals('core_competency', $evidence->get('desccomponent')); $this->assertEquals((object) array('a' => 'b'), $evidence->get('desca')); $this->assertEquals('http://moodle.org', $evidence->get('url')); $this->assertEquals(\core_competency\evidence::ACTION_OVERRIDE, $evidence->get('action')); $this->assertEquals(2, $evidence->get('actionuserid')); $this->assertEquals(1, $evidence->get('grade')); $this->assertEquals(1, $uc->get('grade')); $this->assertEquals(0, $uc->get('proficiency')); } /** * Test add evidence for non-existing user_competency. */ public function test_add_evidence_no_existing_user_competency(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $syscontext = \context_system::instance(); // Create users. $user = $dg->create_user(); $this->setUser($user); // Create a framework and assign competencies. $framework = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $this->assertEquals(0, \core_competency\user_competency::count_records()); // Create an evidence without a user competency record. $evidence = api::add_evidence($user->id, $c1->get('id'), $syscontext->id, \core_competency\evidence::ACTION_OVERRIDE, 'invalidevidencedesc', 'core_competency', 'Hello world!', false, 'http://moodle.org', 1, 2); $this->assertEquals(1, \core_competency\evidence::count_records()); $this->assertEquals(1, \core_competency\user_competency::count_records()); $uc = \core_competency\user_competency::get_record(array('userid' => $user->id, 'competencyid' => $c1->get('id'))); $evidence->read(); $this->assertEquals($uc->get('id'), $evidence->get('usercompetencyid')); $this->assertEquals('invalidevidencedesc', $evidence->get('descidentifier')); $this->assertEquals('core_competency', $evidence->get('desccomponent')); $this->assertEquals('Hello world!', $evidence->get('desca')); $this->assertEquals('http://moodle.org', $evidence->get('url')); $this->assertEquals(\core_competency\evidence::ACTION_OVERRIDE, $evidence->get('action')); $this->assertEquals(2, $evidence->get('actionuserid')); $this->assertEquals(1, $evidence->get('grade')); $this->assertEquals(1, $uc->get('grade')); $this->assertEquals(0, $uc->get('proficiency')); } public function test_add_evidence_applies_competency_rules(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $syscontext = \context_system::instance(); $ctxid = $syscontext->id; $u1 = $dg->create_user(); // Setting up the framework. $f1 = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c1a = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1->get('id'))); $c1b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2a = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c2->get('id'))); $c3 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c3a = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c3->get('id'))); $c4 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c4a = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c4->get('id'))); $c5 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); // Setting up the rules. $c1->set('ruletype', 'core_competency\\competency_rule_all'); $c1->set('ruleoutcome', \core_competency\competency::OUTCOME_COMPLETE); $c1->update(); $c2->set('ruletype', 'core_competency\\competency_rule_all'); $c2->set('ruleoutcome', \core_competency\competency::OUTCOME_RECOMMEND); $c2->update(); $c3->set('ruletype', 'core_competency\\competency_rule_all'); $c3->set('ruleoutcome', \core_competency\competency::OUTCOME_EVIDENCE); $c3->update(); $c4->set('ruletype', 'core_competency\\competency_rule_all'); $c4->set('ruleoutcome', \core_competency\competency::OUTCOME_NONE); $c4->update(); // Confirm the current data. $this->assertEquals(0, user_competency::count_records()); $this->assertEquals(0, evidence::count_records()); // Let's do this! // First let's confirm that evidence not marking a completion have no impact. api::add_evidence($u1->id, $c1a, $ctxid, evidence::ACTION_LOG, 'commentincontext', 'core'); $uc1a = user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c1a->get('id'))); $this->assertSame(null, $uc1a->get('proficiency')); $this->assertFalse(user_competency::record_exists_select('userid = ? AND competencyid = ?', array($u1->id, $c1->get('id')))); // Now let's try complete a competency but the rule won't match (not all children are complete). // The parent (the thing with the rule) will be created but won't have any evidence attached, and not // not be marked as completed. api::add_evidence($u1->id, $c1a, $ctxid, evidence::ACTION_COMPLETE, 'commentincontext', 'core'); $uc1a = user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c1a->get('id'))); $this->assertEquals(true, $uc1a->get('proficiency')); $uc1 = user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c1->get('id'))); $this->assertSame(null, $uc1->get('proficiency')); $this->assertEquals(0, evidence::count_records(array('usercompetencyid' => $uc1->get('id')))); // Now we complete the other child. That will mark the parent as complete with an evidence. api::add_evidence($u1->id, $c1b, $ctxid, evidence::ACTION_COMPLETE, 'commentincontext', 'core'); $uc1b = user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c1b->get('id'))); $this->assertEquals(true, $uc1a->get('proficiency')); $uc1 = user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c1->get('id'))); $this->assertEquals(true, $uc1->get('proficiency')); $this->assertEquals(user_competency::STATUS_IDLE, $uc1->get('status')); $this->assertEquals(1, evidence::count_records(array('usercompetencyid' => $uc1->get('id')))); // Check rule recommending. api::add_evidence($u1->id, $c2a, $ctxid, evidence::ACTION_COMPLETE, 'commentincontext', 'core'); $uc2a = user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c2a->get('id'))); $this->assertEquals(true, $uc1a->get('proficiency')); $uc2 = user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c2->get('id'))); $this->assertSame(null, $uc2->get('proficiency')); $this->assertEquals(user_competency::STATUS_WAITING_FOR_REVIEW, $uc2->get('status')); $this->assertEquals(1, evidence::count_records(array('usercompetencyid' => $uc2->get('id')))); // Check rule evidence. api::add_evidence($u1->id, $c3a, $ctxid, evidence::ACTION_COMPLETE, 'commentincontext', 'core'); $uc3a = user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c3a->get('id'))); $this->assertEquals(true, $uc1a->get('proficiency')); $uc3 = user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c3->get('id'))); $this->assertSame(null, $uc3->get('proficiency')); $this->assertEquals(user_competency::STATUS_IDLE, $uc3->get('status')); $this->assertEquals(1, evidence::count_records(array('usercompetencyid' => $uc3->get('id')))); // Check rule nothing. api::add_evidence($u1->id, $c4a, $ctxid, evidence::ACTION_COMPLETE, 'commentincontext', 'core'); $uc4a = user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c4a->get('id'))); $this->assertEquals(true, $uc1a->get('proficiency')); $this->assertFalse(user_competency::record_exists_select('userid = ? AND competencyid = ?', array($u1->id, $c4->get('id')))); // Check marking on something that has no parent. This just checks that nothing breaks. api::add_evidence($u1->id, $c5, $ctxid, evidence::ACTION_COMPLETE, 'commentincontext', 'core'); } /** * Tests for the user_competency_course data when api::add_evidence() is invoked when * grading a user competency in the system context. */ public function test_add_evidence_for_user_competency_course_grade_outside_course(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $syscontext = \context_system::instance(); // Create a student. $student = $dg->create_user(); // Create a competency for the course. $lpg = $dg->get_plugin_generator('core_competency'); $framework = $lpg->create_framework(); $comp = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); // Add evidence. api::add_evidence($student->id, $comp, $syscontext, evidence::ACTION_OVERRIDE, 'commentincontext', 'core', null, false, null, 1); // Query for user_competency_course data. $filterparams = array( 'userid' => $student->id, 'competencyid' => $comp->get('id'), ); $usercompcourse = \core_competency\user_competency_course::get_record($filterparams); // There should be no user_competency_course object created when grading. $this->assertFalse($usercompcourse); } /** * Tests for the user_competency_course data when api::add_evidence() is invoked when * grading a user competency in a course. */ public function test_add_evidence_user_competency_course_grade_in_course(): void { global $USER; $this->resetAfterTest(true); $dg = $this->getDataGenerator(); // Create and assign a current user. $currentuser = $dg->create_user(); $this->setUser($currentuser); // Create a course. $course = $dg->create_course(); $record = array('courseid' => $course->id, 'pushratingstouserplans' => false); $settings = new course_competency_settings(0, (object) $record); $settings->create(); $coursecontext = \context_course::instance($course->id); // Create a student and enrol into the course. $student = $dg->create_user(); $studentarch = get_archetype_roles('student'); $studentrole = array_shift($studentarch); $dg->role_assign($studentrole->id, $student->id, $coursecontext->id); $dg->enrol_user($student->id, $course->id, $studentrole->id); // Create a competency for the course. $lpg = $dg->get_plugin_generator('core_competency'); $framework = $lpg->create_framework(); // Do not push ratings from course to user plans. $comp = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $lpg->create_course_competency(array('courseid' => $course->id, 'competencyid' => $comp->get('id'))); // Query for user_competency_course data. $filterparams = array( 'userid' => $student->id, 'competencyid' => $comp->get('id'), 'courseid' => $course->id ); // Add evidence that sets a grade to the course. $evidence = api::add_evidence($student->id, $comp, $coursecontext, evidence::ACTION_OVERRIDE, 'commentincontext', 'core', null, false, null, 3, $USER->id); // Get user competency course record. $usercompcourse = \core_competency\user_competency_course::get_record($filterparams); // There should be a user_competency_course object when adding a grade. $this->assertNotEmpty($usercompcourse); $grade = $evidence->get('grade'); $this->assertEquals($grade, $usercompcourse->get('grade')); $this->assertEquals(3, $usercompcourse->get('grade')); $proficiency = $comp->get_proficiency_of_grade($grade); $this->assertEquals($proficiency, $usercompcourse->get('proficiency')); // Confirm that the user competency's grade/proficiency has not been affected by the grade. $usercompetencyparams = [ 'userid' => $student->id, 'competencyid' => $comp->get('id'), ]; $usercompetency = \core_competency\user_competency::get_record($usercompetencyparams); $this->assertNotEmpty($usercompetency); $this->assertNotEquals($usercompcourse->get('grade'), $usercompetency->get('grade')); $this->assertNotEquals($usercompcourse->get('proficiency'), $usercompetency->get('proficiency')); } public function test_observe_course_completed(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); // Set-up users, framework, competencies and course competencies. $course = $dg->create_course(); $coursectx = \context_course::instance($course->id); $u1 = $dg->create_user(); $f1 = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c3 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c4 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $cc1 = $lpg->create_course_competency(array('competencyid' => $c1->get('id'), 'courseid' => $course->id, 'ruleoutcome' => \core_competency\course_competency::OUTCOME_NONE)); $cc2 = $lpg->create_course_competency(array('competencyid' => $c2->get('id'), 'courseid' => $course->id, 'ruleoutcome' => \core_competency\course_competency::OUTCOME_EVIDENCE)); $cc3 = $lpg->create_course_competency(array('competencyid' => $c3->get('id'), 'courseid' => $course->id, 'ruleoutcome' => \core_competency\course_competency::OUTCOME_RECOMMEND)); $cc4 = $lpg->create_course_competency(array('competencyid' => $c4->get('id'), 'courseid' => $course->id, 'ruleoutcome' => \core_competency\course_competency::OUTCOME_COMPLETE)); $event = \core\event\course_completed::create(array( 'objectid' => 1, 'relateduserid' => $u1->id, 'context' => $coursectx, 'courseid' => $course->id, 'other' => array('relateduserid' => $u1->id) )); $this->assertEquals(0, \core_competency\user_competency::count_records()); $this->assertEquals(0, \core_competency\evidence::count_records()); // Let's go! api::observe_course_completed($event); $this->assertEquals(3, \core_competency\user_competency::count_records()); $this->assertEquals(3, \core_competency\evidence::count_records()); // Outcome NONE did nothing. $this->assertFalse(\core_competency\user_competency::record_exists_select('userid = :uid AND competencyid = :cid', array( 'uid' => $u1->id, 'cid' => $c1->get('id') ))); // Outcome evidence. $uc2 = \core_competency\user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c2->get('id'))); $ev2 = \core_competency\evidence::get_record(array('usercompetencyid' => $uc2->get('id'))); $this->assertEquals(null, $uc2->get('grade')); $this->assertEquals(null, $uc2->get('proficiency')); $this->assertEquals(\core_competency\user_competency::STATUS_IDLE, $uc2->get('status')); $this->assertEquals('evidence_coursecompleted', $ev2->get('descidentifier')); $this->assertEquals('core_competency', $ev2->get('desccomponent')); $this->assertEquals($course->shortname, $ev2->get('desca')); $this->assertStringEndsWith('/report/completion/index.php?course=' . $course->id, $ev2->get('url')); $this->assertEquals(null, $ev2->get('grade')); $this->assertEquals($coursectx->id, $ev2->get('contextid')); $this->assertEquals(\core_competency\evidence::ACTION_LOG, $ev2->get('action')); $this->assertEquals(null, $ev2->get('actionuserid')); // Outcome recommend. $uc3 = \core_competency\user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c3->get('id'))); $ev3 = \core_competency\evidence::get_record(array('usercompetencyid' => $uc3->get('id'))); $this->assertEquals(null, $uc3->get('grade')); $this->assertEquals(null, $uc3->get('proficiency')); $this->assertEquals(\core_competency\user_competency::STATUS_WAITING_FOR_REVIEW, $uc3->get('status')); $this->assertEquals('evidence_coursecompleted', $ev3->get('descidentifier')); $this->assertEquals('core_competency', $ev3->get('desccomponent')); $this->assertEquals($course->shortname, $ev3->get('desca')); $this->assertStringEndsWith('/report/completion/index.php?course=' . $course->id, $ev3->get('url')); $this->assertEquals(null, $ev3->get('grade')); $this->assertEquals($coursectx->id, $ev3->get('contextid')); $this->assertEquals(\core_competency\evidence::ACTION_LOG, $ev3->get('action')); $this->assertEquals(null, $ev3->get('actionuserid')); // Outcome complete. $uc4 = \core_competency\user_competency::get_record(array('userid' => $u1->id, 'competencyid' => $c4->get('id'))); $ev4 = \core_competency\evidence::get_record(array('usercompetencyid' => $uc4->get('id'))); $this->assertEquals(3, $uc4->get('grade')); $this->assertEquals(1, $uc4->get('proficiency')); $this->assertEquals(\core_competency\user_competency::STATUS_IDLE, $uc4->get('status')); $this->assertEquals('evidence_coursecompleted', $ev4->get('descidentifier')); $this->assertEquals('core_competency', $ev4->get('desccomponent')); $this->assertEquals($course->shortname, $ev4->get('desca')); $this->assertStringEndsWith('/report/completion/index.php?course=' . $course->id, $ev4->get('url')); $this->assertEquals(3, $ev4->get('grade')); $this->assertEquals($coursectx->id, $ev4->get('contextid')); $this->assertEquals(\core_competency\evidence::ACTION_COMPLETE, $ev4->get('action')); $this->assertEquals(null, $ev4->get('actionuserid')); } public function test_list_evidence_in_course(): void { global $SITE; $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $u1 = $dg->create_user(); $course = $dg->create_course(); $coursecontext = \context_course::instance($course->id); $this->setAdminUser(); $f = $lpg->create_framework(); $c = $lpg->create_competency(array('competencyframeworkid' => $f->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $f->get('id'))); $cc = api::add_competency_to_course($course->id, $c->get('id')); $cc2 = api::add_competency_to_course($course->id, $c2->get('id')); $pagegenerator = $this->getDataGenerator()->get_plugin_generator('mod_page'); $page = $pagegenerator->create_instance(array('course' => $course->id)); $cm = get_coursemodule_from_instance('page', $page->id); $cmcontext = \context_module::instance($cm->id); // Add the competency to the course module. $ccm = api::add_competency_to_course_module($cm, $c->get('id')); // Now add the evidence to the course. $evidence1 = api::add_evidence($u1->id, $c->get('id'), $coursecontext->id, \core_competency\evidence::ACTION_LOG, 'invaliddata', 'error'); $result = api::list_evidence_in_course($u1->id, $course->id, $c->get('id')); $this->assertEquals($result[0]->get('id'), $evidence1->get('id')); // Now add the evidence to the course module. $evidence2 = api::add_evidence($u1->id, $c->get('id'), $cmcontext->id, \core_competency\evidence::ACTION_LOG, 'invaliddata', 'error'); $result = api::list_evidence_in_course($u1->id, $course->id, $c->get('id'), 'timecreated', 'ASC'); $this->assertEquals($evidence1->get('id'), $result[0]->get('id')); $this->assertEquals($evidence2->get('id'), $result[1]->get('id')); } public function test_list_course_modules_using_competency(): void { global $SITE; $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $course = $dg->create_course(); $course2 = $dg->create_course(); $this->setAdminUser(); $f = $lpg->create_framework(); $c = $lpg->create_competency(array('competencyframeworkid' => $f->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $f->get('id'))); $cc = api::add_competency_to_course($course->id, $c->get('id')); $cc2 = api::add_competency_to_course($course->id, $c2->get('id')); // First check we get an empty list when there are no links. $expected = array(); $result = api::list_course_modules_using_competency($c->get('id'), $course->id); $this->assertEquals($expected, $result); $pagegenerator = $this->getDataGenerator()->get_plugin_generator('mod_page'); $page = $pagegenerator->create_instance(array('course' => $course->id)); $cm = get_coursemodule_from_instance('page', $page->id); // Add a link and list again. $ccm = api::add_competency_to_course_module($cm, $c->get('id')); $expected = array($cm->id); $result = api::list_course_modules_using_competency($c->get('id'), $course->id); $this->assertEquals($expected, $result); // Check a different course. $expected = array(); $result = api::list_course_modules_using_competency($c->get('id'), $course2->id); $this->assertEquals($expected, $result); // Remove the link and check again. $result = api::remove_competency_from_course_module($cm, $c->get('id')); $expected = true; $this->assertEquals($expected, $result); $expected = array(); $result = api::list_course_modules_using_competency($c->get('id'), $course->id); $this->assertEquals($expected, $result); // Now add 2 links. api::add_competency_to_course_module($cm, $c->get('id')); api::add_competency_to_course_module($cm, $c2->get('id')); $result = api::list_course_module_competencies_in_course_module($cm->id); $this->assertEquals($result[0]->get('competencyid'), $c->get('id')); $this->assertEquals($result[1]->get('competencyid'), $c2->get('id')); // Now re-order. api::reorder_course_module_competency($cm, $c->get('id'), $c2->get('id')); $result = api::list_course_module_competencies_in_course_module($cm->id); $this->assertEquals($result[0]->get('competencyid'), $c2->get('id')); $this->assertEquals($result[1]->get('competencyid'), $c->get('id')); // And re-order again. api::reorder_course_module_competency($cm, $c->get('id'), $c2->get('id')); $result = api::list_course_module_competencies_in_course_module($cm->id); $this->assertEquals($result[0]->get('competencyid'), $c->get('id')); $this->assertEquals($result[1]->get('competencyid'), $c2->get('id')); // Now get the course competency and coursemodule competency together. $result = api::list_course_module_competencies($cm->id); // Now we should have an array and each element of the array should have a competency and // a coursemodulecompetency. foreach ($result as $instance) { $cmc = $instance['coursemodulecompetency']; $c = $instance['competency']; $this->assertEquals($cmc->get('competencyid'), $c->get('id')); } } /** * Test update ruleoutcome for course_competency. */ public function test_set_ruleoutcome_course_competency(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $course = $dg->create_course(); $this->setAdminUser(); $f = $lpg->create_framework(); $c = $lpg->create_competency(array('competencyframeworkid' => $f->get('id'))); $cc = api::add_competency_to_course($course->id, $c->get('id')); // Check record was created with default rule value Evidence. $this->assertEquals(1, \core_competency\course_competency::count_records()); $recordscc = api::list_course_competencies($course->id); $this->assertEquals(\core_competency\course_competency::OUTCOME_EVIDENCE, $recordscc[0]['coursecompetency']->get('ruleoutcome')); // Check ruleoutcome value is updated to None. $this->assertTrue(api::set_course_competency_ruleoutcome($recordscc[0]['coursecompetency']->get('id'), \core_competency\course_competency::OUTCOME_NONE)); $recordscc = api::list_course_competencies($course->id); $this->assertEquals(\core_competency\course_competency::OUTCOME_NONE, $recordscc[0]['coursecompetency']->get('ruleoutcome')); } /** * Test validation on grade on user_competency. */ public function test_validate_grade_in_user_competency(): void { global $DB; $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $user = $dg->create_user(); $s1 = $dg->create_scale(array("scale" => "value1, value2")); $s2 = $dg->create_scale(array("scale" => "value3, value4, value5, value6")); $scaleconfiguration1 = '[{"scaleid":"'.$s1->id.'"},{"name":"value1","id":1,"scaledefault":1,"proficient":0},' . '{"name":"value2","id":2,"scaledefault":0,"proficient":1}]'; $scaleconfiguration2 = '[{"scaleid":"'.$s2->id.'"},{"name":"value3","id":1,"scaledefault":1,"proficient":0},' . '{"name":"value4","id":2,"scaledefault":0,"proficient":1}]'; // Create a framework with scale configuration1. $frm = array( 'scaleid' => $s1->id, 'scaleconfiguration' => $scaleconfiguration1 ); $framework = $lpg->create_framework($frm); $c1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); // Create competency with its own scale configuration. $c2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'), 'scaleid' => $s2->id, 'scaleconfiguration' => $scaleconfiguration2 )); // Detecte invalid grade in competency using its framework competency scale. try { $usercompetency = new user_competency(0, (object) array('userid' => $user->id, 'competencyid' => $c1->get('id'), 'proficiency' => true, 'grade' => 3 )); $usercompetency->create(); $this->fail('Invalid grade not detected in framework scale'); } catch (\core\invalid_persistent_exception $e) { $this->assertTrue(true); } // Detecte invalid grade in competency using its own scale. try { $usercompetency = new user_competency(0, (object) array('userid' => $user->id, 'competencyid' => $c2->get('id'), 'proficiency' => true, 'grade' => 5 )); $usercompetency->create(); $this->fail('Invalid grade not detected in competency scale'); } catch (\core\invalid_persistent_exception $e) { $this->assertTrue(true); } // Accept valid grade in competency using its framework competency scale. try { $usercompetency = new user_competency(0, (object) array('userid' => $user->id, 'competencyid' => $c1->get('id'), 'proficiency' => true, 'grade' => 1 )); $usercompetency->create(); $this->assertTrue(true); } catch (\core\invalid_persistent_exception $e) { $this->fail('Valide grade rejected in framework scale'); } // Accept valid grade in competency using its framework competency scale. try { $usercompetency = new user_competency(0, (object) array('userid' => $user->id, 'competencyid' => $c2->get('id'), 'proficiency' => true, 'grade' => 4 )); $usercompetency->create(); $this->assertTrue(true); } catch (\core\invalid_persistent_exception $e) { $this->fail('Valide grade rejected in competency scale'); } } /** * Test when adding competency that belong to hidden framework to plan/template/course. */ public function test_hidden_framework(): void { $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $user = $dg->create_user(); // Create a course. $cat1 = $dg->create_category(); $course = $dg->create_course(array('category' => $cat1->id)); // Create a template. $template = $lpg->create_template(); // Create a plan. $plan = $lpg->create_plan(array('userid' => $user->id)); // Create a hidden framework. $frm = array( 'visible' => false ); $framework = $lpg->create_framework($frm); $competency = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); // Linking competency that belong to hidden framework to course. try { api::add_competency_to_course($course->id, $competency->get('id')); $this->fail('A competency belonging to hidden framework can not be linked to course'); } catch (\coding_exception $e) { $this->assertTrue(true); } // Adding competency that belong to hidden framework to template. try { api::add_competency_to_template($template->get('id'), $competency->get('id')); $this->fail('A competency belonging to hidden framework can not be added to template'); } catch (\coding_exception $e) { $this->assertTrue(true); } // Adding competency that belong to hidden framework to plan. try { api::add_competency_to_plan($plan->get('id'), $competency->get('id')); $this->fail('A competency belonging to hidden framework can not be added to plan'); } catch (\coding_exception $e) { $this->assertTrue(true); } } /** * Test when using hidden template in plan/cohort. */ public function test_hidden_template(): void { $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $user = $dg->create_user(); // Create a cohort. $cohort = $dg->create_cohort(); // Create a hidden template. $template = $lpg->create_template(array('visible' => false)); // Can not link hidden template to plan. try { api::create_plan_from_template($template->get('id'), $user->id); $this->fail('Can not link a hidden template to plan'); } catch (\coding_exception $e) { $this->assertTrue(true); } // Can associate hidden template to cohort. $templatecohort = api::create_template_cohort($template->get('id'), $cohort->id); $this->assertInstanceOf('\core_competency\template_cohort', $templatecohort); } /** * Test that completed plan created form a template does not change when template is modified. */ public function test_completed_plan_doesnot_change(): void { global $DB; $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $user = $dg->create_user(); // Create a framework and assign competencies. $framework = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c3 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c4 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); // Create template and assign competencies. $tp = $lpg->create_template(); $tpc1 = $lpg->create_template_competency(array('templateid' => $tp->get('id'), 'competencyid' => $c1->get('id'))); $tpc2 = $lpg->create_template_competency(array('templateid' => $tp->get('id'), 'competencyid' => $c2->get('id'))); $tpc3 = $lpg->create_template_competency(array('templateid' => $tp->get('id'), 'competencyid' => $c3->get('id'))); // Create a plan form template and change it status to complete. $plan = $lpg->create_plan(array('userid' => $user->id, 'templateid' => $tp->get('id'))); api::complete_plan($plan); // Check user competency plan created correctly. $this->assertEquals(3, \core_competency\user_competency_plan::count_records()); $ucp = \core_competency\user_competency_plan::get_records(); $this->assertEquals($ucp[0]->get('competencyid'), $c1->get('id')); $this->assertEquals($ucp[1]->get('competencyid'), $c2->get('id')); $this->assertEquals($ucp[2]->get('competencyid'), $c3->get('id')); // Add and remove a competency from the template. api::add_competency_to_template($tp->get('id'), $c4->get('id')); api::remove_competency_from_template($tp->get('id'), $c1->get('id')); // Check that user competency plan did not change. $competencies = $plan->get_competencies(); $this->assertEquals(3, count($competencies)); $ucp1 = array($c1->get('id'), $c2->get('id'), $c3->get('id')); $ucp2 = array(); foreach ($competencies as $id => $cmp) { $ucp2[] = $id; } $this->assertEquals(0, count(array_diff($ucp1, $ucp2))); } protected function setup_framework_for_reset_rules_tests() { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $this->setAdminUser(); $f1 = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c1a = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1->get('id'))); $c1a1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1a->get('id'))); $c1a1a = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1a1->get('id'))); $c1b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c1b1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1b->get('id'))); $c1b1a = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1b1->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2a = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c1->set('ruleoutcome', competency::OUTCOME_EVIDENCE); $c1->set('ruletype', 'core_competency\\competency_rule_all'); $c1->update(); $c1a->set('ruleoutcome', competency::OUTCOME_EVIDENCE); $c1a->set('ruletype', 'core_competency\\competency_rule_all'); $c1a->update(); $c1a1->set('ruleoutcome', competency::OUTCOME_EVIDENCE); $c1a1->set('ruletype', 'core_competency\\competency_rule_all'); $c1a1->update(); $c1b->set('ruleoutcome', competency::OUTCOME_EVIDENCE); $c1b->set('ruletype', 'core_competency\\competency_rule_all'); $c1b->update(); $c2->set('ruleoutcome', competency::OUTCOME_EVIDENCE); $c2->set('ruletype', 'core_competency\\competency_rule_all'); $c2->update(); return array( 'f1' => $f1, 'c1' => $c1, 'c1a' => $c1a, 'c1a1' => $c1a1, 'c1a1a' => $c1a1a, 'c1b' => $c1b, 'c1b1' => $c1b1, 'c1b1a' => $c1b1a, 'c2' => $c2, 'c2a' => $c2a, ); } public function test_moving_competency_reset_rules_updown(): void { $data = $this->setup_framework_for_reset_rules_tests(); $f1 = $data['f1']; $c1 = $data['c1']; $c1a = $data['c1a']; $c1a1 = $data['c1a1']; $c1a1a = $data['c1a1a']; $c1b = $data['c1b']; $c1b1 = $data['c1b1']; $c1b1a = $data['c1b1a']; $c2 = $data['c2']; $c2a = $data['c2a']; // Moving up and down doesn't change anything. api::move_down_competency($c1a->get('id')); $c1->read(); $c1a->read(); $c1a1->read(); $this->assertEquals(competency::OUTCOME_EVIDENCE, $c1->get('ruleoutcome')); $this->assertEquals(competency::OUTCOME_EVIDENCE, $c1a->get('ruleoutcome')); $this->assertEquals(competency::OUTCOME_EVIDENCE, $c1a1->get('ruleoutcome')); api::move_up_competency($c1a->get('id')); $c1->read(); $c1a->read(); $c1a1->read(); $this->assertEquals(competency::OUTCOME_EVIDENCE, $c1->get('ruleoutcome')); $this->assertEquals(competency::OUTCOME_EVIDENCE, $c1a->get('ruleoutcome')); $this->assertEquals(competency::OUTCOME_EVIDENCE, $c1a1->get('ruleoutcome')); } public function test_moving_competency_reset_rules_parent(): void { $data = $this->setup_framework_for_reset_rules_tests(); $f1 = $data['f1']; $c1 = $data['c1']; $c1a = $data['c1a']; $c1a1 = $data['c1a1']; $c1a1a = $data['c1a1a']; $c1b = $data['c1b']; $c1b1 = $data['c1b1']; $c1b1a = $data['c1b1a']; $c2 = $data['c2']; $c2a = $data['c2a']; // Moving out of parent will reset the parent, and the destination. api::set_parent_competency($c1a->get('id'), $c1b->get('id')); $c1->read(); $c1a->read(); $c1a1->read(); $c1b->read(); $c2->read(); $this->assertEquals(competency::OUTCOME_NONE, $c1->get('ruleoutcome')); $this->assertEquals(competency::OUTCOME_EVIDENCE, $c1a->get('ruleoutcome')); $this->assertEquals(competency::OUTCOME_EVIDENCE, $c1a1->get('ruleoutcome')); $this->assertEquals(competency::OUTCOME_NONE, $c1b->get('ruleoutcome')); $this->assertEquals(competency::OUTCOME_EVIDENCE, $c2->get('ruleoutcome')); } public function test_moving_competency_reset_rules_totoplevel(): void { $data = $this->setup_framework_for_reset_rules_tests(); $f1 = $data['f1']; $c1 = $data['c1']; $c1a = $data['c1a']; $c1a1 = $data['c1a1']; $c1a1a = $data['c1a1a']; $c1b = $data['c1b']; $c1b1 = $data['c1b1']; $c1b1a = $data['c1b1a']; $c2 = $data['c2']; $c2a = $data['c2a']; // Moving to top level only affects the initial parent. api::set_parent_competency($c1a1->get('id'), 0); $c1->read(); $c1a->read(); $c1a1->read(); $c1b->read(); $c2->read(); $this->assertEquals(competency::OUTCOME_EVIDENCE, $c1->get('ruleoutcome')); $this->assertEquals(competency::OUTCOME_NONE, $c1a->get('ruleoutcome')); $this->assertEquals(competency::OUTCOME_EVIDENCE, $c1a1->get('ruleoutcome')); $this->assertEquals(competency::OUTCOME_EVIDENCE, $c1b->get('ruleoutcome')); $this->assertEquals(competency::OUTCOME_EVIDENCE, $c2->get('ruleoutcome')); } public function test_moving_competency_reset_rules_fromtoplevel(): void { $data = $this->setup_framework_for_reset_rules_tests(); $f1 = $data['f1']; $c1 = $data['c1']; $c1a = $data['c1a']; $c1a1 = $data['c1a1']; $c1a1a = $data['c1a1a']; $c1b = $data['c1b']; $c1b1 = $data['c1b1']; $c1b1a = $data['c1b1a']; $c2 = $data['c2']; $c2a = $data['c2a']; // Moving from top level only affects the destination parent. api::set_parent_competency($c2->get('id'), $c1a1->get('id')); $c1->read(); $c1a->read(); $c1a1->read(); $c1b->read(); $c2->read(); $this->assertEquals(competency::OUTCOME_EVIDENCE, $c1->get('ruleoutcome')); $this->assertEquals(competency::OUTCOME_EVIDENCE, $c1a->get('ruleoutcome')); $this->assertEquals(competency::OUTCOME_NONE, $c1a1->get('ruleoutcome')); $this->assertEquals(competency::OUTCOME_EVIDENCE, $c1b->get('ruleoutcome')); $this->assertEquals(competency::OUTCOME_EVIDENCE, $c2->get('ruleoutcome')); } public function test_moving_competency_reset_rules_child(): void { $data = $this->setup_framework_for_reset_rules_tests(); $f1 = $data['f1']; $c1 = $data['c1']; $c1a = $data['c1a']; $c1a1 = $data['c1a1']; $c1a1a = $data['c1a1a']; $c1b = $data['c1b']; $c1b1 = $data['c1b1']; $c1b1a = $data['c1b1a']; $c2 = $data['c2']; $c2a = $data['c2a']; // Moving to a child of self resets self, parent and destination. api::set_parent_competency($c1a->get('id'), $c1a1->get('id')); $c1->read(); $c1a->read(); $c1a1->read(); $c1b->read(); $c2->read(); $this->assertEquals(competency::OUTCOME_NONE, $c1->get('ruleoutcome')); $this->assertEquals(competency::OUTCOME_NONE, $c1a->get('ruleoutcome')); $this->assertEquals(competency::OUTCOME_NONE, $c1a1->get('ruleoutcome')); $this->assertEquals(competency::OUTCOME_EVIDENCE, $c1b->get('ruleoutcome')); $this->assertEquals(competency::OUTCOME_EVIDENCE, $c2->get('ruleoutcome')); } public function test_create_competency_reset_rules(): void { $data = $this->setup_framework_for_reset_rules_tests(); $f1 = $data['f1']; $c1 = $data['c1']; $c1a = $data['c1a']; $c1a1 = $data['c1a1']; $c1a1a = $data['c1a1a']; $c1b = $data['c1b']; $c1b1 = $data['c1b1']; $c1b1a = $data['c1b1a']; $c2 = $data['c2']; $c2a = $data['c2a']; // Adding a new competency resets the rule of its parent. api::create_competency((object) array('shortname' => 'A', 'parentid' => $c1->get('id'), 'idnumber' => 'A', 'competencyframeworkid' => $f1->get('id'))); $c1->read(); $c1a->read(); $c1a1->read(); $c1b->read(); $c2->read(); $this->assertEquals(competency::OUTCOME_NONE, $c1->get('ruleoutcome')); $this->assertEquals(competency::OUTCOME_EVIDENCE, $c1a->get('ruleoutcome')); $this->assertEquals(competency::OUTCOME_EVIDENCE, $c1a1->get('ruleoutcome')); $this->assertEquals(competency::OUTCOME_EVIDENCE, $c1b->get('ruleoutcome')); $this->assertEquals(competency::OUTCOME_EVIDENCE, $c2->get('ruleoutcome')); } public function test_delete_competency_reset_rules(): void { $data = $this->setup_framework_for_reset_rules_tests(); $f1 = $data['f1']; $c1 = $data['c1']; $c1a = $data['c1a']; $c1a1 = $data['c1a1']; $c1a1a = $data['c1a1a']; $c1b = $data['c1b']; $c1b1 = $data['c1b1']; $c1b1a = $data['c1b1a']; $c2 = $data['c2']; $c2a = $data['c2a']; // Deleting a competency resets the rule of its parent. api::delete_competency($c1a->get('id')); $c1->read(); $c1b->read(); $c2->read(); $this->assertEquals(competency::OUTCOME_NONE, $c1->get('ruleoutcome')); $this->assertEquals(competency::OUTCOME_EVIDENCE, $c1b->get('ruleoutcome')); $this->assertEquals(competency::OUTCOME_EVIDENCE, $c2->get('ruleoutcome')); } public function test_template_has_related_data(): void { $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $user = $dg->create_user(); $lpg = $dg->get_plugin_generator('core_competency'); $tpl1 = $lpg->create_template(); $tpl2 = $lpg->create_template(); // Create plans for first template. $time = time(); $plan1 = $lpg->create_plan(array('templateid' => $tpl1->get('id'), 'userid' => $user->id, 'name' => 'Not good name', 'duedate' => $time + 3600, 'description' => 'Ahah', 'descriptionformat' => FORMAT_PLAIN)); $this->assertTrue(api::template_has_related_data($tpl1->get('id'))); $this->assertFalse(api::template_has_related_data($tpl2->get('id'))); } public function test_delete_template_delete_plans(): void { $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $u1 = $dg->create_user(); $f = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $f->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $f->get('id'))); $tpl = $lpg->create_template(); $tplc1 = $lpg->create_template_competency(array('templateid' => $tpl->get('id'), 'competencyid' => $c1->get('id'), 'sortorder' => 1)); $tplc2 = $lpg->create_template_competency(array('templateid' => $tpl->get('id'), 'competencyid' => $c2->get('id'), 'sortorder' => 2)); $p1 = $lpg->create_plan(array('templateid' => $tpl->get('id'), 'userid' => $u1->id)); // Check pre-test. $this->assertTrue(\core_competency\template::record_exists($tpl->get('id'))); $this->assertEquals(2, \core_competency\template_competency::count_competencies($tpl->get('id'))); $this->assertEquals(1, count(\core_competency\plan::get_records(array('templateid' => $tpl->get('id'))))); $result = api::delete_template($tpl->get('id'), true); $this->assertTrue($result); // Check that the template does not exist anymore. $this->assertFalse(\core_competency\template::record_exists($tpl->get('id'))); // Check that associated competencies are also deleted. $this->assertEquals(0, \core_competency\template_competency::count_competencies($tpl->get('id'))); // Check that associated plan are also deleted. $this->assertEquals(0, count(\core_competency\plan::get_records(array('templateid' => $tpl->get('id'))))); } public function test_delete_template_unlink_plans(): void { $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $u1 = $dg->create_user(); $f = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $f->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $f->get('id'))); $tpl = $lpg->create_template(); $tplc1 = $lpg->create_template_competency(array('templateid' => $tpl->get('id'), 'competencyid' => $c1->get('id'), 'sortorder' => 1)); $tplc2 = $lpg->create_template_competency(array('templateid' => $tpl->get('id'), 'competencyid' => $c2->get('id'), 'sortorder' => 2)); $p1 = $lpg->create_plan(array('templateid' => $tpl->get('id'), 'userid' => $u1->id)); // Check pre-test. $this->assertTrue(\core_competency\template::record_exists($tpl->get('id'))); $this->assertEquals(2, \core_competency\template_competency::count_competencies($tpl->get('id'))); $this->assertEquals(1, count(\core_competency\plan::get_records(array('templateid' => $tpl->get('id'))))); $result = api::delete_template($tpl->get('id'), false); $this->assertTrue($result); // Check that the template does not exist anymore. $this->assertFalse(\core_competency\template::record_exists($tpl->get('id'))); // Check that associated competencies are also deleted. $this->assertEquals(0, \core_competency\template_competency::count_competencies($tpl->get('id'))); // Check that associated plan still exist but unlink from template. $plans = \core_competency\plan::get_records(array('id' => $p1->get('id'))); $this->assertEquals(1, count($plans)); $this->assertEquals($plans[0]->get('origtemplateid'), $tpl->get('id')); $this->assertNull($plans[0]->get('templateid')); } public function test_delete_competency(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $this->setAdminUser(); $u1 = $dg->create_user(); $f1 = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c1a = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1->get('id'))); $c1b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1a->get('id'))); $c11b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1b->get('id'))); $c12b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1b->get('id'))); // Set rules on parent competency. $c1->set('ruleoutcome', competency::OUTCOME_EVIDENCE); $c1->set('ruletype', 'core_competency\\competency_rule_all'); $c1->update(); // If we delete competeny, the related competencies relations and evidences should be deleted. // Create related competencies using one of c1a competency descendants. $rc = $lpg->create_related_competency(array( 'competencyid' => $c2->get('id'), 'relatedcompetencyid' => $c11b->get('id') )); $this->assertEquals($c11b->get('id'), $rc->get('relatedcompetencyid')); // Creating a standard evidence with minimal information. $uc2 = $lpg->create_user_competency(array('userid' => $u1->id, 'competencyid' => $c11b->get('id'))); $evidence = $lpg->create_evidence(array('usercompetencyid' => $uc2->get('id'))); $this->assertEquals($uc2->get('id'), $evidence->get('usercompetencyid')); $uc2->delete(); $this->assertTrue(api::delete_competency($c1a->get('id'))); $this->assertFalse(competency::record_exists($c1a->get('id'))); // Check that on delete, we reset the rule on parent competency. $c1->read(); $this->assertNull($c1->get('ruletype')); $this->assertNull($c1->get('ruletype')); $this->assertEquals(competency::OUTCOME_NONE, $c1->get('ruleoutcome')); // Check that descendants were also deleted. $this->assertFalse(competency::record_exists($c1b->get('id'))); $this->assertFalse(competency::record_exists($c11b->get('id'))); $this->assertFalse(competency::record_exists($c12b->get('id'))); // Check if evidence are also deleted. $this->assertEquals(0, \core_competency\user_evidence_competency::count_records(array('competencyid' => $c11b->get('id')))); // Check if related conpetency relation is deleted. $this->assertEquals(0, count(api::list_related_competencies($c2->get('id')))); // Delete a simple competency. $this->assertTrue(api::delete_competency($c2->get('id'))); $this->assertFalse(competency::record_exists($c2->get('id'))); } public function test_delete_competency_used_in_plan(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $this->setAdminUser(); $u1 = $dg->create_user(); $plan = $lpg->create_plan((object) array('userid' => $u1->id)); $f1 = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c1a = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1->get('id'))); $c1b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1a->get('id'))); $c11b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1b->get('id'))); $c12b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1b->get('id'))); // Add competency to plan. $pc = $lpg->create_plan_competency(array('planid' => $plan->get('id'), 'competencyid' => $c11b->get('id'))); // We can not delete a competency , if competency or competency children is associated to plan. $this->assertFalse(api::delete_competency($c1a->get('id'))); // We can delete the competency if we remove the competency from the plan. $pc->delete(); $this->assertTrue(api::delete_competency($c1a->get('id'))); $this->assertFalse(competency::record_exists($c1a->get('id'))); $this->assertFalse(competency::record_exists($c1b->get('id'))); $this->assertFalse(competency::record_exists($c11b->get('id'))); $this->assertFalse(competency::record_exists($c12b->get('id'))); } public function test_delete_competency_used_in_usercompetency(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $this->setAdminUser(); $u1 = $dg->create_user(); $f1 = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c1a = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1->get('id'))); $c1b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1a->get('id'))); $c11b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1b->get('id'))); $c12b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1b->get('id'))); // Create user competency. $uc1 = $lpg->create_user_competency(array('userid' => $u1->id, 'competencyid' => $c11b->get('id'))); // We can not delete a competency , if competency or competency children exist in user competency. $this->assertFalse(api::delete_competency($c1a->get('id'))); // We can delete the competency if we remove the competency from user competency. $uc1->delete(); $this->assertTrue(api::delete_competency($c1a->get('id'))); $this->assertFalse(competency::record_exists($c1a->get('id'))); $this->assertFalse(competency::record_exists($c1b->get('id'))); $this->assertFalse(competency::record_exists($c11b->get('id'))); $this->assertFalse(competency::record_exists($c12b->get('id'))); } public function test_delete_competency_used_in_usercompetencyplan(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $this->setAdminUser(); $u1 = $dg->create_user(); $plan = $lpg->create_plan((object) array('userid' => $u1->id)); $f1 = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c1a = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1->get('id'))); $c1b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1a->get('id'))); $c11b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1b->get('id'))); $c12b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1b->get('id'))); // Create user competency plan. $uc2 = $lpg->create_user_competency_plan(array( 'userid' => $u1->id, 'competencyid' => $c11b->get('id'), 'planid' => $plan->get('id') )); // We can not delete a competency , if competency or competency children exist in user competency plan. $this->assertFalse(api::delete_competency($c1a->get('id'))); // We can delete the competency if we remove the competency from user competency plan. $uc2->delete(); $this->assertTrue(api::delete_competency($c1a->get('id'))); $this->assertFalse(competency::record_exists($c1a->get('id'))); $this->assertFalse(competency::record_exists($c1b->get('id'))); $this->assertFalse(competency::record_exists($c11b->get('id'))); $this->assertFalse(competency::record_exists($c12b->get('id'))); } public function test_delete_competency_used_in_template(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $this->setAdminUser(); $template = $lpg->create_template(); $f1 = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c1a = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1->get('id'))); $c1b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1a->get('id'))); $c11b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1b->get('id'))); $c12b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1b->get('id'))); // Add competency to a template. $tc = $lpg->create_template_competency(array( 'templateid' => $template->get('id'), 'competencyid' => $c11b->get('id') )); // We can not delete a competency , if competency or competency children is linked to template. $this->assertFalse(api::delete_competency($c1a->get('id'))); // We can delete the competency if we remove the competency from template. $tc->delete(); $this->assertTrue(api::delete_competency($c1a->get('id'))); $this->assertFalse(competency::record_exists($c1a->get('id'))); $this->assertFalse(competency::record_exists($c1b->get('id'))); $this->assertFalse(competency::record_exists($c11b->get('id'))); $this->assertFalse(competency::record_exists($c12b->get('id'))); } public function test_delete_competency_used_in_course(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $this->setAdminUser(); $cat1 = $dg->create_category(); $course = $dg->create_course(array('category' => $cat1->id)); $f1 = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c1a = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1->get('id'))); $c1b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1a->get('id'))); $c11b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1b->get('id'))); $c12b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1b->get('id'))); // Add competency to course. $cc = $lpg->create_course_competency(array( 'courseid' => $course->id, 'competencyid' => $c11b->get('id') )); // We can not delete a competency if the competency or competencies children is linked to a course. $this->assertFalse(api::delete_competency($c1a->get('id'))); // We can delete the competency if we remove the competency from course. $cc->delete(); $this->assertTrue(api::delete_competency($c1a->get('id'))); $this->assertFalse(competency::record_exists($c1a->get('id'))); $this->assertFalse(competency::record_exists($c1b->get('id'))); $this->assertFalse(competency::record_exists($c11b->get('id'))); $this->assertFalse(competency::record_exists($c12b->get('id'))); } public function test_delete_framework(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $this->setAdminUser(); $u1 = $dg->create_user(); $f1 = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2id = $c2->get('id'); $c1a = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1->get('id'))); $c1b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1a->get('id'))); $c11b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1b->get('id'))); $c12b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1b->get('id'))); // If we delete framework, the related competencies relations and evidences should be deleted. // Create related competencies using one of c1a competency descendants. $rc = $lpg->create_related_competency(array( 'competencyid' => $c2->get('id'), 'relatedcompetencyid' => $c11b->get('id') )); $this->assertEquals($c11b->get('id'), $rc->get('relatedcompetencyid')); // Creating a standard evidence with minimal information. $uc2 = $lpg->create_user_competency(array('userid' => $u1->id, 'competencyid' => $c11b->get('id'))); $evidence = $lpg->create_evidence(array('usercompetencyid' => $uc2->get('id'))); $this->assertEquals($uc2->get('id'), $evidence->get('usercompetencyid')); $uc2->delete(); $this->assertTrue(api::delete_framework($f1->get('id'))); $this->assertFalse(competency_framework::record_exists($f1->get('id'))); // Check that all competencies were also deleted. $this->assertFalse(competency::record_exists($c1->get('id'))); $this->assertFalse(competency::record_exists($c2->get('id'))); $this->assertFalse(competency::record_exists($c1a->get('id'))); $this->assertFalse(competency::record_exists($c1b->get('id'))); $this->assertFalse(competency::record_exists($c11b->get('id'))); $this->assertFalse(competency::record_exists($c12b->get('id'))); // Check if evidence are also deleted. $this->assertEquals(0, \core_competency\user_evidence_competency::count_records(array('competencyid' => $c11b->get('id')))); // Check if related conpetency relation is deleted. $this->assertEquals(0, count(\core_competency\related_competency::get_multiple_relations(array($c2id)))); // Delete a simple framework. $f2 = $lpg->create_framework(); $this->assertTrue(api::delete_framework($f2->get('id'))); $this->assertFalse(competency_framework::record_exists($f2->get('id'))); } public function test_delete_framework_competency_used_in_plan(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $this->setAdminUser(); $u1 = $dg->create_user(); $plan = $lpg->create_plan((object) array('userid' => $u1->id)); $f1 = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2id = $c2->get('id'); $c1a = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1->get('id'))); $c1b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1a->get('id'))); $c11b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1b->get('id'))); $c12b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1b->get('id'))); // Create related competencies. $rc = $lpg->create_related_competency(array( 'competencyid' => $c2->get('id'), 'relatedcompetencyid' => $c11b->get('id') )); $this->assertEquals($c11b->get('id'), $rc->get('relatedcompetencyid')); // Creating a standard evidence with minimal information. $uc2 = $lpg->create_user_competency(array('userid' => $u1->id, 'competencyid' => $c11b->get('id'))); $usercompetencyid = $uc2->get('id'); $evidence = $lpg->create_evidence(array('usercompetencyid' => $usercompetencyid)); $this->assertEquals($uc2->get('id'), $evidence->get('usercompetencyid')); $uc2->delete(); // Add competency to plan. $pc = $lpg->create_plan_competency(array('planid' => $plan->get('id'), 'competencyid' => $c11b->get('id'))); // We can not delete a framework , if competency or competency children is associated to plan. $this->assertFalse(api::delete_framework($f1->get('id'))); // Check that none of associated data are deleted. $this->assertEquals($usercompetencyid, $evidence->read()->get('usercompetencyid')); $this->assertEquals($c2->get('id'), $rc->read()->get('competencyid')); // We can delete the competency if we remove the competency from the plan. $pc->delete(); $this->assertTrue(api::delete_framework($f1->get('id'))); $this->assertFalse(competency::record_exists($c1->get('id'))); $this->assertFalse(competency::record_exists($c2->get('id'))); $this->assertFalse(competency::record_exists($c1a->get('id'))); $this->assertFalse(competency::record_exists($c1b->get('id'))); $this->assertFalse(competency::record_exists($c11b->get('id'))); $this->assertFalse(competency::record_exists($c12b->get('id'))); // Check if evidence are also deleted. $this->assertEquals(0, \core_competency\user_evidence_competency::count_records(array('competencyid' => $c11b->get('id')))); // Check if related conpetency relation is deleted. $this->assertEquals(0, count(\core_competency\related_competency::get_multiple_relations(array($c2id)))); } public function test_delete_framework_competency_used_in_usercompetency(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $this->setAdminUser(); $u1 = $dg->create_user(); $f1 = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2id = $c2->get('id'); $c1a = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1->get('id'))); $c1b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1a->get('id'))); $c11b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1b->get('id'))); $c12b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1b->get('id'))); // Create related competencies. $rc = $lpg->create_related_competency(array( 'competencyid' => $c2->get('id'), 'relatedcompetencyid' => $c11b->get('id') )); $this->assertEquals($c11b->get('id'), $rc->get('relatedcompetencyid')); // Creating a standard evidence with minimal information. $uc1 = $lpg->create_user_competency(array('userid' => $u1->id, 'competencyid' => $c11b->get('id'))); $usercompetencyid = $uc1->get('id'); $evidence = $lpg->create_evidence(array('usercompetencyid' => $usercompetencyid)); $this->assertEquals($uc1->get('id'), $evidence->get('usercompetencyid')); $uc1->delete(); // Create user competency. $uc2 = $lpg->create_user_competency(array('userid' => $u1->id, 'competencyid' => $c11b->get('id'))); // We can not delete a framework , if competency or competency children exist in user competency. $this->assertFalse(api::delete_framework($f1->get('id'))); // Check that none of associated data are deleted. $this->assertEquals($usercompetencyid, $evidence->read()->get('usercompetencyid')); $this->assertEquals($c2->get('id'), $rc->read()->get('competencyid')); // We can delete the framework if we remove the competency from user competency. $uc2->delete(); $this->assertTrue(api::delete_framework($f1->get('id'))); $this->assertFalse(competency::record_exists($c1->get('id'))); $this->assertFalse(competency::record_exists($c2->get('id'))); $this->assertFalse(competency::record_exists($c1a->get('id'))); $this->assertFalse(competency::record_exists($c1b->get('id'))); $this->assertFalse(competency::record_exists($c11b->get('id'))); $this->assertFalse(competency::record_exists($c12b->get('id'))); // Check if evidence are also deleted. $this->assertEquals(0, \core_competency\user_evidence_competency::count_records(array('competencyid' => $c11b->get('id')))); // Check if related conpetency relation is deleted. $this->assertEquals(0, count(\core_competency\related_competency::get_multiple_relations(array($c2id)))); } public function test_delete_framework_competency_used_in_usercompetencyplan(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $this->setAdminUser(); $u1 = $dg->create_user(); $plan = $lpg->create_plan((object) array('userid' => $u1->id)); $f1 = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2id = $c2->get('id'); $c1a = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1->get('id'))); $c1b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1a->get('id'))); $c11b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1b->get('id'))); $c12b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1b->get('id'))); // Create related competencies. $rc = $lpg->create_related_competency(array( 'competencyid' => $c2->get('id'), 'relatedcompetencyid' => $c11b->get('id') )); $this->assertEquals($c11b->get('id'), $rc->get('relatedcompetencyid')); // Creating a standard evidence with minimal information. $uc1 = $lpg->create_user_competency(array('userid' => $u1->id, 'competencyid' => $c11b->get('id'))); $usercompetencyid = $uc1->get('id'); $evidence = $lpg->create_evidence(array('usercompetencyid' => $usercompetencyid)); $this->assertEquals($uc1->get('id'), $evidence->get('usercompetencyid')); $uc1->delete(); // Create user competency plan. $uc2 = $lpg->create_user_competency_plan(array( 'userid' => $u1->id, 'competencyid' => $c11b->get('id'), 'planid' => $plan->get('id') )); // We can not delete a framework , if competency or competency children exist in user competency plan. $this->assertFalse(api::delete_framework($f1->get('id'))); // Check that none of associated data are deleted. $this->assertEquals($usercompetencyid, $evidence->read()->get('usercompetencyid')); $this->assertEquals($c2->get('id'), $rc->read()->get('competencyid')); // We can delete the framework if we remove the competency from user competency plan. $uc2->delete(); $this->assertTrue(api::delete_framework($f1->get('id'))); $this->assertFalse(competency::record_exists($c1->get('id'))); $this->assertFalse(competency::record_exists($c2->get('id'))); $this->assertFalse(competency::record_exists($c1a->get('id'))); $this->assertFalse(competency::record_exists($c1b->get('id'))); $this->assertFalse(competency::record_exists($c11b->get('id'))); $this->assertFalse(competency::record_exists($c12b->get('id'))); // Check if evidence are also deleted. $this->assertEquals(0, \core_competency\user_evidence_competency::count_records(array('competencyid' => $c11b->get('id')))); // Check if related conpetency relation is deleted. $this->assertEquals(0, count(\core_competency\related_competency::get_multiple_relations(array($c2id)))); } public function test_delete_framework_competency_used_in_template(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $this->setAdminUser(); $u1 = $dg->create_user(); $template = $lpg->create_template(); $f1 = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2id = $c2->get('id'); $c1a = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1->get('id'))); $c1b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1a->get('id'))); $c11b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1b->get('id'))); $c12b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1b->get('id'))); // Create related competencies. $rc = $lpg->create_related_competency(array( 'competencyid' => $c2->get('id'), 'relatedcompetencyid' => $c11b->get('id') )); $this->assertEquals($c11b->get('id'), $rc->get('relatedcompetencyid')); // Creating a standard evidence with minimal information. $uc1 = $lpg->create_user_competency(array('userid' => $u1->id, 'competencyid' => $c11b->get('id'))); $usercompetencyid = $uc1->get('id'); $evidence = $lpg->create_evidence(array('usercompetencyid' => $usercompetencyid)); $this->assertEquals($uc1->get('id'), $evidence->get('usercompetencyid')); $uc1->delete(); // Add competency to a template. $tc = $lpg->create_template_competency(array( 'templateid' => $template->get('id'), 'competencyid' => $c11b->get('id') )); // We can not delete a framework , if competency or competency children is linked to template. $this->assertFalse(api::delete_framework($f1->get('id'))); // Check that none of associated data are deleted. $this->assertEquals($usercompetencyid, $evidence->read()->get('usercompetencyid')); $this->assertEquals($c2->get('id'), $rc->read()->get('competencyid')); // We can delete the framework if we remove the competency from template. $tc->delete(); $this->assertTrue(api::delete_framework($f1->get('id'))); $this->assertFalse(competency::record_exists($c1->get('id'))); $this->assertFalse(competency::record_exists($c2->get('id'))); $this->assertFalse(competency::record_exists($c1a->get('id'))); $this->assertFalse(competency::record_exists($c1b->get('id'))); $this->assertFalse(competency::record_exists($c11b->get('id'))); $this->assertFalse(competency::record_exists($c12b->get('id'))); // Check if evidence are also deleted. $this->assertEquals(0, \core_competency\user_evidence_competency::count_records(array('competencyid' => $c11b->get('id')))); // Check if related conpetency relation is deleted. $this->assertEquals(0, count(\core_competency\related_competency::get_multiple_relations(array($c2id)))); } public function test_delete_framework_competency_used_in_course(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $this->setAdminUser(); $cat1 = $dg->create_category(); $u1 = $dg->create_user(); $course = $dg->create_course(array('category' => $cat1->id)); $f1 = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2id = $c2->get('id'); $c1a = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1->get('id'))); $c1b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1a->get('id'))); $c11b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1b->get('id'))); $c12b = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1b->get('id'))); // Create related competencies. $rc = $lpg->create_related_competency(array( 'competencyid' => $c2->get('id'), 'relatedcompetencyid' => $c11b->get('id') )); $this->assertEquals($c11b->get('id'), $rc->get('relatedcompetencyid')); // Creating a standard evidence with minimal information. $uc1 = $lpg->create_user_competency(array('userid' => $u1->id, 'competencyid' => $c11b->get('id'))); $usercompetencyid = $uc1->get('id'); $evidence = $lpg->create_evidence(array('usercompetencyid' => $usercompetencyid)); $this->assertEquals($uc1->get('id'), $evidence->get('usercompetencyid')); $uc1->delete(); // Add competency to course. $cc = $lpg->create_course_competency(array( 'courseid' => $course->id, 'competencyid' => $c11b->get('id') )); // We can not delete a framework if the competency or competencies children is linked to a course. $this->assertFalse(api::delete_framework($f1->get('id'))); // Check that none of associated data are deleted. $this->assertEquals($usercompetencyid, $evidence->read()->get('usercompetencyid')); $this->assertEquals($c2->get('id'), $rc->read()->get('competencyid')); // We can delete the framework if we remove the competency from course. $cc->delete(); $this->assertTrue(api::delete_framework($f1->get('id'))); $this->assertFalse(competency::record_exists($c1->get('id'))); $this->assertFalse(competency::record_exists($c2->get('id'))); $this->assertFalse(competency::record_exists($c1a->get('id'))); $this->assertFalse(competency::record_exists($c1b->get('id'))); $this->assertFalse(competency::record_exists($c11b->get('id'))); $this->assertFalse(competency::record_exists($c12b->get('id'))); // Check if evidence are also deleted. $this->assertEquals(0, \core_competency\user_evidence_competency::count_records(array('competencyid' => $c11b->get('id')))); // Check if related conpetency relation is deleted. $this->assertEquals(0, count(\core_competency\related_competency::get_multiple_relations(array($c2id)))); } public function test_grade_competency_in_course_permissions(): void { $this->resetAfterTest(); $dg = $this->getDataGenerator(); $c1 = $dg->create_course(); $c2 = $dg->create_course(); $sysctx = \context_system::instance(); $c1ctx = \context_course::instance($c1->id); $c2ctx = \context_course::instance($c2->id); $teacher1 = $dg->create_user(); $noneditingteacher = $dg->create_user(); $student1 = $dg->create_user(); $student2 = $dg->create_user(); $notstudent1 = $dg->create_user(); $lpg = $dg->get_plugin_generator('core_competency'); $framework = $lpg->create_framework(); $comp1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $comp2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $lpg->create_course_competency(array('courseid' => $c1->id, 'competencyid' => $comp1->get('id'))); $studentarch = get_archetype_roles('student'); $studentrole = array_shift($studentarch); $gradablerole = $dg->create_role(); assign_capability('moodle/competency:coursecompetencygradable', CAP_ALLOW, $gradablerole, $sysctx->id); $notgradablerole = $dg->create_role(); assign_capability('moodle/competency:coursecompetencygradable', CAP_PROHIBIT, $notgradablerole, $sysctx->id); $canviewucrole = $dg->create_role(); assign_capability('moodle/competency:usercompetencyview', CAP_ALLOW, $canviewucrole, $sysctx->id); $cannotviewcomp = $dg->create_role(); assign_capability('moodle/competency:competencyview', CAP_PROHIBIT, $cannotviewcomp, $sysctx->id); $canmanagecomp = $dg->create_role(); assign_capability('moodle/competency:competencymanage', CAP_ALLOW, $canmanagecomp, $sysctx->id); $cangraderole = $dg->create_role(); assign_capability('moodle/competency:competencygrade', CAP_ALLOW, $cangraderole, $sysctx->id); // Enrol s1 and s2 as students in course 1. $dg->enrol_user($student1->id, $c1->id, $studentrole->id); $dg->enrol_user($student2->id, $c1->id, $studentrole->id); // Mark the s2 as not being 'gradable'. $dg->role_assign($notgradablerole, $student2->id, $c1ctx->id); // Mark the 'non a student' as 'gradable' throughout the site. $dg->role_assign($gradablerole, $notstudent1->id, $sysctx->id); // From now we'll iterate over each permission. accesslib_clear_all_caches_for_unit_testing(); $this->setUser($teacher1); $this->assertExceptionWithGradeCompetencyInCourse('required_capability_exception', 'View a user competency', $c1->id, $student1->id, $comp1->get('id')); // Give permission to view competencies. $dg->role_assign($canviewucrole, $teacher1->id, $c1ctx->id); accesslib_clear_all_caches_for_unit_testing(); $this->assertExceptionWithGradeCompetencyInCourse('required_capability_exception', 'Set competency rating', $c1->id, $student1->id, $comp1->get('id')); // Give permission to rate. $dg->role_assign($cangraderole, $teacher1->id, $c1ctx->id); accesslib_clear_all_caches_for_unit_testing(); $this->assertSuccessWithGradeCompetencyInCourse($c1->id, $student1->id, $comp1->get('id')); // Remove permssion to read competencies, this leads to error. $dg->role_assign($cannotviewcomp, $teacher1->id, $sysctx->id); accesslib_clear_all_caches_for_unit_testing(); $this->assertExceptionWithGradeCompetencyInCourse('required_capability_exception', 'View competency frameworks', $c1->id, $student1->id, $comp1->get('id')); // Give permssion to manage course competencies, this leads to success. $dg->role_assign($canmanagecomp, $teacher1->id, $sysctx->id); accesslib_clear_all_caches_for_unit_testing(); $this->assertSuccessWithGradeCompetencyInCourse($c1->id, $student1->id, $comp1->get('id')); // Try to grade a user that is not gradable, lead to errors. $this->assertExceptionWithGradeCompetencyInCourse('coding_exception', 'The competency may not be rated at this time.', $c1->id, $student2->id, $comp1->get('id')); // Try to grade a competency not in the course. $this->assertExceptionWithGradeCompetencyInCourse('coding_exception', 'The competency does not belong to this course', $c1->id, $student1->id, $comp2->get('id')); // Try to grade a user that is not enrolled, even though they are 'gradable'. $this->assertExceptionWithGradeCompetencyInCourse('coding_exception', 'The competency may not be rated at this time.', $c1->id, $notstudent1->id, $comp1->get('id')); // Give permission for non-editing teacher to grade. $dg->role_assign($canviewucrole, $noneditingteacher->id, $c1ctx->id); $dg->role_assign($cangraderole, $noneditingteacher->id, $c1ctx->id); $this->setUser($noneditingteacher); accesslib_clear_all_caches_for_unit_testing(); $this->assertSuccessWithGradeCompetencyInCourse($c1->id, $student1->id, $comp1->get('id')); } /** * Assert that a competency was graded in a course. * * @param int $courseid The course ID. * @param int $userid The user ID. * @param int $compid The competency ID. * @param int $grade The grade. */ protected function assertSuccessWithGradeCompetencyInCourse($courseid, $userid, $compid, $grade = 1) { $beforecount = evidence::count_records(); api::grade_competency_in_course($courseid, $userid, $compid, $grade); $this->assertEquals($beforecount + 1, evidence::count_records()); $uc = user_competency::get_record(array('userid' => $userid, 'competencyid' => $compid)); $records = evidence::get_records(array(), 'id', 'DESC', 0, 1); $evidence = array_pop($records); $this->assertEquals($uc->get('id'), $evidence->get('usercompetencyid')); } /** * Assert that grading a competency in course throws an exception. * * @param string $exceptiontype The exception type. * @param string $exceptiontest The exceptiont text. * @param int $courseid The course ID. * @param int $userid The user ID. * @param int $compid The competency ID. * @param int $grade The grade. */ protected function assertExceptionWithGradeCompetencyInCourse($exceptiontype, $exceptiontext, $courseid, $userid, $compid, $grade = 1) { $raised = false; try { api::grade_competency_in_course($courseid, $userid, $compid, $grade); } catch (\moodle_exception $e) { $raised = true; $this->assertInstanceOf($exceptiontype, $e); $this->assertMatchesRegularExpression('@' . $exceptiontext . '@', $e->getMessage()); } if (!$raised) { $this->fail('Grading should not be allowed.'); } } /** * Test list of evidences for plan completed and not completed. */ public function test_list_evidence(): void { global $DB; $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $syscontext = \context_system::instance(); // Create users. $user = $dg->create_user(); $this->setUser($user); // Create a framework and assign competencies. $framework = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); // Create 2 user plans and add competency to each plan. $p1 = $lpg->create_plan(array('userid' => $user->id)); $p2 = $lpg->create_plan(array('userid' => $user->id)); $pc1 = $lpg->create_plan_competency(array('planid' => $p1->get('id'), 'competencyid' => $c1->get('id'))); $pc2 = $lpg->create_plan_competency(array('planid' => $p2->get('id'), 'competencyid' => $c1->get('id'))); // Create user competency and add an evidence. $uc = $lpg->create_user_competency(array('userid' => $user->id, 'competencyid' => $c1->get('id'))); $e1 = $lpg->create_evidence(array('usercompetencyid' => $uc->get('id'))); // Check both plans as one evidence. $this->assertEquals(1, count(api::list_evidence($user->id, $c1->get('id'), $p1->get('id')))); $this->assertEquals(1, count(api::list_evidence($user->id, $c1->get('id'), $p2->get('id')))); // Complete second plan. $p2->set('status', plan::STATUS_COMPLETE); $p2->update(); // Add another evidence for the same competency, but in the future (time + 1). $e2 = $lpg->create_evidence(array('usercompetencyid' => $uc->get('id'))); $evidencesql = "UPDATE {" . evidence::TABLE . "} SET timecreated = :currenttime WHERE id = :evidenceid"; $DB->execute($evidencesql, array('currenttime' => time() + 1, 'evidenceid' => $e2->get('id'))); // Check that the first plan, which is not completed, has all the evidence. $this->assertEquals(2, count(api::list_evidence($user->id, $c1->get('id'), $p1->get('id')))); // Check that the second plan, completed before the new evidence, only has the first piece of evidence. $listevidences = api::list_evidence($user->id, $c1->get('id'), $p2->get('id')); $this->assertEquals(1, count($listevidences)); $this->assertEquals($e1->get('id'), $listevidences[$e1->get('id')]->get('id')); } /** * Get a user competency in a course. */ public function test_get_user_competency_in_course(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $this->setAdminUser(); $user = $dg->create_user(); $c1 = $dg->create_course(); // Enrol the user so they can be rated in the course. $studentarch = get_archetype_roles('student'); $studentrole = array_shift($studentarch); $coursecontext = \context_course::instance($c1->id); $dg->role_assign($studentrole->id, $user->id, $coursecontext->id); $dg->enrol_user($user->id, $c1->id, $studentrole->id); $framework = $lpg->create_framework(); $comp1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $comp2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $lpg->create_course_competency(array('competencyid' => $comp1->get('id'), 'courseid' => $c1->id)); $lpg->create_course_competency(array('competencyid' => $comp2->get('id'), 'courseid' => $c1->id)); // Create a user competency for comp1. api::grade_competency_in_course($c1, $user->id, $comp1->get('id'), 3, 'Unit test'); // Test for competency already exist in user_competency. $uc = api::get_user_competency_in_course($c1->id, $user->id, $comp1->get('id')); $this->assertEquals($comp1->get('id'), $uc->get('competencyid')); $this->assertEquals($user->id, $uc->get('userid')); $this->assertEquals(3, $uc->get('grade')); $this->assertEquals(true, $uc->get('proficiency')); // Test for competency does not exist in user_competency. $uc2 = api::get_user_competency_in_course($c1->id, $user->id, $comp2->get('id')); $this->assertEquals($comp2->get('id'), $uc2->get('competencyid')); $this->assertEquals($user->id, $uc2->get('userid')); $this->assertEquals(null, $uc2->get('grade')); $this->assertEquals(null, $uc2->get('proficiency')); } /** * Test course statistics api functions. */ public function test_course_statistics(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $this->setAdminUser(); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $u3 = $dg->create_user(); $u4 = $dg->create_user(); $c1 = $dg->create_course(); $framework = $lpg->create_framework(); // Enrol students in the course. $studentarch = get_archetype_roles('student'); $studentrole = array_shift($studentarch); $coursecontext = \context_course::instance($c1->id); $dg->role_assign($studentrole->id, $u1->id, $coursecontext->id); $dg->enrol_user($u1->id, $c1->id, $studentrole->id); $dg->role_assign($studentrole->id, $u2->id, $coursecontext->id); $dg->enrol_user($u2->id, $c1->id, $studentrole->id); $dg->role_assign($studentrole->id, $u3->id, $coursecontext->id); $dg->enrol_user($u3->id, $c1->id, $studentrole->id); $dg->role_assign($studentrole->id, $u4->id, $coursecontext->id); $dg->enrol_user($u4->id, $c1->id, $studentrole->id); // Create 6 competencies. $comp1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $comp2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $comp3 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $comp4 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $comp5 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $comp6 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); // Link 6 out of 6 to a course. $lpg->create_course_competency(array('competencyid' => $comp1->get('id'), 'courseid' => $c1->id)); $lpg->create_course_competency(array('competencyid' => $comp2->get('id'), 'courseid' => $c1->id)); $lpg->create_course_competency(array('competencyid' => $comp3->get('id'), 'courseid' => $c1->id)); $lpg->create_course_competency(array('competencyid' => $comp4->get('id'), 'courseid' => $c1->id)); $lpg->create_course_competency(array('competencyid' => $comp5->get('id'), 'courseid' => $c1->id)); $lpg->create_course_competency(array('competencyid' => $comp6->get('id'), 'courseid' => $c1->id)); // Rate some competencies. // User 1. api::grade_competency_in_course($c1, $u1->id, $comp1->get('id'), 4, 'Unit test'); api::grade_competency_in_course($c1, $u1->id, $comp2->get('id'), 4, 'Unit test'); api::grade_competency_in_course($c1, $u1->id, $comp3->get('id'), 4, 'Unit test'); api::grade_competency_in_course($c1, $u1->id, $comp4->get('id'), 4, 'Unit test'); // User 2. api::grade_competency_in_course($c1, $u2->id, $comp1->get('id'), 1, 'Unit test'); api::grade_competency_in_course($c1, $u2->id, $comp2->get('id'), 1, 'Unit test'); api::grade_competency_in_course($c1, $u2->id, $comp3->get('id'), 1, 'Unit test'); api::grade_competency_in_course($c1, $u2->id, $comp4->get('id'), 1, 'Unit test'); // User 3. api::grade_competency_in_course($c1, $u3->id, $comp1->get('id'), 3, 'Unit test'); api::grade_competency_in_course($c1, $u3->id, $comp2->get('id'), 3, 'Unit test'); // User 4. api::grade_competency_in_course($c1, $u4->id, $comp1->get('id'), 2, 'Unit test'); api::grade_competency_in_course($c1, $u4->id, $comp2->get('id'), 2, 'Unit test'); // OK we have enough data - lets call some API functions and check for expected results. $result = api::count_proficient_competencies_in_course_for_user($c1->id, $u1->id); $this->assertEquals(4, $result); $result = api::count_proficient_competencies_in_course_for_user($c1->id, $u2->id); $this->assertEquals(0, $result); $result = api::count_proficient_competencies_in_course_for_user($c1->id, $u3->id); $this->assertEquals(2, $result); $result = api::count_proficient_competencies_in_course_for_user($c1->id, $u4->id); $this->assertEquals(0, $result); $result = api::get_least_proficient_competencies_for_course($c1->id, 0, 2); // We should get 5 and 6 in repeatable order. $valid = false; if (($comp5->get('id') == $result[0]->get('id')) || ($comp6->get('id') == $result[0]->get('id'))) { $valid = true; } $this->assertTrue($valid); $valid = false; if (($comp5->get('id') == $result[1]->get('id')) || ($comp6->get('id') == $result[1]->get('id'))) { $valid = true; } $this->assertTrue($valid); $expected = $result[1]->get('id'); $result = api::get_least_proficient_competencies_for_course($c1->id, 1, 1); $this->assertEquals($result[0]->get('id'), $expected); } /** * Test template statistics api functions. */ public function test_template_statistics(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $this->setAdminUser(); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $u3 = $dg->create_user(); $u4 = $dg->create_user(); $c1 = $dg->create_course(); $framework = $lpg->create_framework(); // Create 6 competencies. $comp1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $comp2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $comp3 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $comp4 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $comp5 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $comp6 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); // Link 5 out of 6 to a course. $lpg->create_course_competency(array('competencyid' => $comp1->get('id'), 'courseid' => $c1->id)); $lpg->create_course_competency(array('competencyid' => $comp2->get('id'), 'courseid' => $c1->id)); $lpg->create_course_competency(array('competencyid' => $comp3->get('id'), 'courseid' => $c1->id)); $lpg->create_course_competency(array('competencyid' => $comp4->get('id'), 'courseid' => $c1->id)); $lpg->create_course_competency(array('competencyid' => $comp5->get('id'), 'courseid' => $c1->id)); // Put all 6 in a template. $tpl = $this->getDataGenerator()->get_plugin_generator('core_competency')->create_template(); $tplc1 = $lpg->create_template_competency(array('templateid' => $tpl->get('id'), 'competencyid' => $comp1->get('id'))); $tplc2 = $lpg->create_template_competency(array('templateid' => $tpl->get('id'), 'competencyid' => $comp2->get('id'))); $tplc3 = $lpg->create_template_competency(array('templateid' => $tpl->get('id'), 'competencyid' => $comp3->get('id'))); $tplc4 = $lpg->create_template_competency(array('templateid' => $tpl->get('id'), 'competencyid' => $comp4->get('id'))); $tplc5 = $lpg->create_template_competency(array('templateid' => $tpl->get('id'), 'competencyid' => $comp5->get('id'))); $tplc6 = $lpg->create_template_competency(array('templateid' => $tpl->get('id'), 'competencyid' => $comp6->get('id'))); // Create some plans from the template. $p1 = $lpg->create_plan(array('templateid' => $tpl->get('id'), 'userid' => $u1->id)); $p2 = $lpg->create_plan(array('templateid' => $tpl->get('id'), 'userid' => $u2->id)); $p3 = $lpg->create_plan(array('templateid' => $tpl->get('id'), 'userid' => $u3->id)); $p4 = $lpg->create_plan(array('templateid' => $tpl->get('id'), 'userid' => $u4->id)); // Rate some competencies. // User 1. $lpg->create_user_competency(array('userid' => $u1->id, 'competencyid' => $comp1->get('id'), 'proficiency' => true, 'grade' => 1 )); $lpg->create_user_competency(array('userid' => $u1->id, 'competencyid' => $comp2->get('id'), 'proficiency' => true, 'grade' => 1 )); $lpg->create_user_competency(array('userid' => $u1->id, 'competencyid' => $comp3->get('id'), 'proficiency' => true, 'grade' => 1 )); // User 2. $lpg->create_user_competency(array('userid' => $u2->id, 'competencyid' => $comp1->get('id'), 'proficiency' => false, 'grade' => 1 )); $lpg->create_user_competency(array('userid' => $u2->id, 'competencyid' => $comp2->get('id'), 'proficiency' => false, 'grade' => 1 )); $lpg->create_user_competency(array('userid' => $u2->id, 'competencyid' => $comp3->get('id'), 'proficiency' => false, 'grade' => 1 )); // User 3. $lpg->create_user_competency(array('userid' => $u3->id, 'competencyid' => $comp2->get('id'), 'proficiency' => false, 'grade' => 1 )); $lpg->create_user_competency(array('userid' => $u3->id, 'competencyid' => $comp3->get('id'), 'proficiency' => true, 'grade' => 1 )); $lpg->create_user_competency(array('userid' => $u3->id, 'competencyid' => $comp4->get('id'), 'proficiency' => false, 'grade' => 1 )); $lpg->create_user_competency(array('userid' => $u3->id, 'competencyid' => $comp5->get('id'), 'proficiency' => true, 'grade' => 1 )); // User 4. $lpg->create_user_competency(array('userid' => $u4->id, 'competencyid' => $comp3->get('id'), 'proficiency' => true, 'grade' => 1 )); $lpg->create_user_competency(array('userid' => $u4->id, 'competencyid' => $comp5->get('id'), 'proficiency' => true, 'grade' => 1 )); // Complete 3 out of 4 plans. api::complete_plan($p1->get('id')); api::complete_plan($p2->get('id')); api::complete_plan($p3->get('id')); // OK we have enough data - lets call some API functions and check for expected results. $result = api::count_competencies_in_template_with_no_courses($tpl->get('id')); $this->assertEquals(1, $result); $result = api::count_plans_for_template($tpl->get('id')); $this->assertEquals(4, $result); $result = api::count_plans_for_template($tpl->get('id'), plan::STATUS_COMPLETE); $this->assertEquals(3, $result); // This counts the records of competencies in completed plans for all users with a plan from this template. $result = api::count_user_competency_plans_for_template($tpl->get('id')); // There should be 3 plans * 6 competencies. $this->assertEquals(18, $result); // This counts the records of proficient competencies in completed plans for all users with a plan from this template. $result = api::count_user_competency_plans_for_template($tpl->get('id'), true); // There should be 5. $this->assertEquals(5, $result); // This counts the records of not proficient competencies in completed plans for all users with a plan from this template. $result = api::count_user_competency_plans_for_template($tpl->get('id'), false); // There should be 13. $this->assertEquals(13, $result); // This lists the plans based on this template, optionally filtered by status. $result = api::list_plans_for_template($tpl->get('id')); $this->assertEquals(4, count($result)); foreach ($result as $one) { $this->assertInstanceOf('\core_competency\plan', $one); } // This lists the plans based on this template, optionally filtered by status. $result = api::list_plans_for_template($tpl->get('id'), plan::STATUS_COMPLETE); $this->assertEquals(3, count($result)); foreach ($result as $one) { $this->assertInstanceOf('\core_competency\plan', $one); $this->assertEquals(plan::STATUS_COMPLETE, $one->get('status')); } $result = api::get_least_proficient_competencies_for_template($tpl->get('id'), 0, 2); // Our times completed counts should look like this: // - comp1 - 1 // - comp2 - 1 // - comp3 - 2 // - comp4 - 0 // - comp5 - 1 // - comp6 - 0 // // And this is a fullstop to make CiBoT happy. $this->assertEquals(2, count($result)); $leastarray = array($comp4->get('id'), $comp6->get('id')); foreach ($result as $one) { $this->assertInstanceOf('\core_competency\competency', $one); $this->assertContainsEquals($one->get('id'), $leastarray); } } public function test_is_scale_used_anywhere(): void { $this->resetAfterTest(); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $scale1 = $dg->create_scale(); $scale2 = $dg->create_scale(); $scale3 = $dg->create_scale(); $scale4 = $dg->create_scale(); $this->assertFalse(api::is_scale_used_anywhere($scale1->id)); $this->assertFalse(api::is_scale_used_anywhere($scale2->id)); $this->assertFalse(api::is_scale_used_anywhere($scale3->id)); $this->assertFalse(api::is_scale_used_anywhere($scale4->id)); // Using scale 1 in a framework. $f1 = $lpg->create_framework([ 'scaleid' => $scale1->id, 'scaleconfiguration' => json_encode([ ['scaleid' => $scale1->id], ['id' => 1, 'scaledefault' => 1, 'proficient' => 1] ]) ]); $this->assertTrue(api::is_scale_used_anywhere($scale1->id)); $this->assertFalse(api::is_scale_used_anywhere($scale2->id)); $this->assertFalse(api::is_scale_used_anywhere($scale3->id)); $this->assertFalse(api::is_scale_used_anywhere($scale4->id)); // Using scale 2 in a competency. $f2 = $lpg->create_framework(); $c2 = $lpg->create_competency([ 'competencyframeworkid' => $f2->get('id'), 'scaleid' => $scale2->id, 'scaleconfiguration' => json_encode([ ['scaleid' => $scale2->id], ['id' => 1, 'scaledefault' => 1, 'proficient' => 1] ]) ]); $this->assertTrue(api::is_scale_used_anywhere($scale1->id)); $this->assertTrue(api::is_scale_used_anywhere($scale2->id)); $this->assertFalse(api::is_scale_used_anywhere($scale3->id)); $this->assertFalse(api::is_scale_used_anywhere($scale4->id)); // Using scale 3 in a framework, and scale 4 in a competency of that framework. $f3 = $lpg->create_framework([ 'scaleid' => $scale3->id, 'scaleconfiguration' => json_encode([ ['scaleid' => $scale3->id], ['id' => 1, 'scaledefault' => 1, 'proficient' => 1] ]) ]); $c3 = $lpg->create_competency([ 'competencyframeworkid' => $f3->get('id'), 'scaleid' => $scale4->id, 'scaleconfiguration' => json_encode([ ['scaleid' => $scale4->id], ['id' => 1, 'scaledefault' => 1, 'proficient' => 1] ]) ]); $this->assertTrue(api::is_scale_used_anywhere($scale1->id)); $this->assertTrue(api::is_scale_used_anywhere($scale2->id)); $this->assertTrue(api::is_scale_used_anywhere($scale3->id)); $this->assertTrue(api::is_scale_used_anywhere($scale4->id)); // Multiple occurrences of the same scale (3, and 4). $f4 = $lpg->create_framework([ 'scaleid' => $scale3->id, 'scaleconfiguration' => json_encode([ ['scaleid' => $scale3->id], ['id' => 1, 'scaledefault' => 1, 'proficient' => 1] ]) ]); $c4 = $lpg->create_competency([ 'competencyframeworkid' => $f3->get('id'), 'scaleid' => $scale4->id, 'scaleconfiguration' => json_encode([ ['scaleid' => $scale4->id], ['id' => 1, 'scaledefault' => 1, 'proficient' => 1] ]) ]); $this->assertTrue(api::is_scale_used_anywhere($scale1->id)); $this->assertTrue(api::is_scale_used_anywhere($scale2->id)); $this->assertTrue(api::is_scale_used_anywhere($scale3->id)); $this->assertTrue(api::is_scale_used_anywhere($scale4->id)); } public function test_delete_evidence(): void { $this->resetAfterTest(); $dg = $this->getDataGenerator(); $ccg = $dg->get_plugin_generator('core_competency'); $u1 = $dg->create_user(); $f1 = $ccg->create_framework(); $comp1 = $ccg->create_competency(['competencyframeworkid' => $f1->get('id')]); $uc1 = $ccg->create_user_competency(['userid' => $u1->id, 'competencyid' => $comp1->get('id')]); $ev1 = $ccg->create_evidence(['usercompetencyid' => $uc1->get('id')]); $ev2 = $ccg->create_evidence(['usercompetencyid' => $uc1->get('id')]); $this->setAdminUser($u1); $this->assertEquals(2, evidence::count_records()); api::delete_evidence($ev1); $this->assertEquals(1, evidence::count_records()); $this->assertFalse(evidence::record_exists($ev1->get('id'))); $this->assertTrue(evidence::record_exists($ev2->get('id'))); } public function test_delete_evidence_without_permissions(): void { $this->resetAfterTest(); $dg = $this->getDataGenerator(); $ccg = $dg->get_plugin_generator('core_competency'); $u1 = $dg->create_user(); $f1 = $ccg->create_framework(); $comp1 = $ccg->create_competency(['competencyframeworkid' => $f1->get('id')]); $uc1 = $ccg->create_user_competency(['userid' => $u1->id, 'competencyid' => $comp1->get('id')]); $ev1 = $ccg->create_evidence(['usercompetencyid' => $uc1->get('id')]); $this->setUser($u1); $this->expectException(\required_capability_exception::class); api::delete_evidence($ev1); } public function test_list_plans_to_review(): void { $dg = $this->getDataGenerator(); $this->resetAfterTest(); $ccg = $dg->get_plugin_generator('core_competency'); $sysctx = \context_system::instance(); $this->setAdminUser(); $reviewer = $dg->create_user(); $roleallow = $dg->create_role(); $roleprohibit = $dg->create_role(); assign_capability('moodle/competency:planreview', CAP_ALLOW, $roleallow, $sysctx->id); assign_capability('moodle/competency:planreview', CAP_PROHIBIT, $roleprohibit, $sysctx->id); role_assign($roleallow, $reviewer->id, $sysctx->id); accesslib_clear_all_caches_for_unit_testing(); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $f1 = $ccg->create_framework(); $comp1 = $ccg->create_competency(['competencyframeworkid' => $f1->get('id')]); $p1a = $ccg->create_plan(['userid' => $u1->id, 'status' => plan::STATUS_WAITING_FOR_REVIEW]); $p1b = $ccg->create_plan(['userid' => $u1->id, 'status' => plan::STATUS_IN_REVIEW, 'reviewerid' => $reviewer->id]); $p1c = $ccg->create_plan(['userid' => $u1->id, 'status' => plan::STATUS_DRAFT]); $p2a = $ccg->create_plan(['userid' => $u2->id, 'status' => plan::STATUS_WAITING_FOR_REVIEW]); $p2b = $ccg->create_plan(['userid' => $u2->id, 'status' => plan::STATUS_IN_REVIEW]); $p2c = $ccg->create_plan(['userid' => $u2->id, 'status' => plan::STATUS_ACTIVE]); $p2d = $ccg->create_plan(['userid' => $u2->id, 'status' => plan::STATUS_ACTIVE]); api::complete_plan($p2d); // The reviewer can review all plans waiting for review, or in review where they are the reviewer. $this->setUser($reviewer); $result = api::list_plans_to_review(); $this->assertEquals(3, $result['count']); $this->assertEquals($p1a->get('id'), $result['plans'][0]->plan->get('id')); $this->assertEquals($p1b->get('id'), $result['plans'][1]->plan->get('id')); $this->assertEquals($p2a->get('id'), $result['plans'][2]->plan->get('id')); // The reviewer cannot view the plans when they do not have the permission in the user's context. role_assign($roleprohibit, $reviewer->id, \context_user::instance($u2->id)->id); accesslib_clear_all_caches_for_unit_testing(); $result = api::list_plans_to_review(); $this->assertEquals(2, $result['count']); $this->assertEquals($p1a->get('id'), $result['plans'][0]->plan->get('id')); $this->assertEquals($p1b->get('id'), $result['plans'][1]->plan->get('id')); } public function test_list_user_competencies_to_review(): void { global $CFG; require_once($CFG->dirroot . '/user/lib.php'); $dg = $this->getDataGenerator(); $this->resetAfterTest(); $ccg = $dg->get_plugin_generator('core_competency'); $sysctx = \context_system::instance(); $this->setAdminUser(); $reviewer = $dg->create_user(); $roleallow = $dg->create_role(); $roleprohibit = $dg->create_role(); assign_capability('moodle/competency:usercompetencyreview', CAP_ALLOW, $roleallow, $sysctx->id); assign_capability('moodle/competency:usercompetencyreview', CAP_PROHIBIT, $roleprohibit, $sysctx->id); role_assign($roleallow, $reviewer->id, $sysctx->id); accesslib_clear_all_caches_for_unit_testing(); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $u3 = $dg->create_user(); $f1 = $ccg->create_framework(); $c1 = $ccg->create_competency(['competencyframeworkid' => $f1->get('id')]); $c2 = $ccg->create_competency(['competencyframeworkid' => $f1->get('id')]); $c3 = $ccg->create_competency(['competencyframeworkid' => $f1->get('id')]); $c4 = $ccg->create_competency(['competencyframeworkid' => $f1->get('id')]); $uc1a = $ccg->create_user_competency(['userid' => $u1->id, 'competencyid' => $c1->get('id'), 'status' => user_competency::STATUS_IDLE]); $uc1b = $ccg->create_user_competency(['userid' => $u1->id, 'competencyid' => $c2->get('id'), 'status' => user_competency::STATUS_WAITING_FOR_REVIEW]); $uc1c = $ccg->create_user_competency(['userid' => $u1->id, 'competencyid' => $c3->get('id'), 'status' => user_competency::STATUS_IN_REVIEW, 'reviewerid' => $reviewer->id]); $uc2a = $ccg->create_user_competency(['userid' => $u2->id, 'competencyid' => $c1->get('id'), 'status' => user_competency::STATUS_WAITING_FOR_REVIEW]); $uc2b = $ccg->create_user_competency(['userid' => $u2->id, 'competencyid' => $c2->get('id'), 'status' => user_competency::STATUS_IDLE]); $uc2c = $ccg->create_user_competency(['userid' => $u2->id, 'competencyid' => $c3->get('id'), 'status' => user_competency::STATUS_IN_REVIEW]); $uc3a = $ccg->create_user_competency(['userid' => $u3->id, 'competencyid' => $c4->get('id'), 'status' => user_competency::STATUS_WAITING_FOR_REVIEW]); // The reviewer can review all plans waiting for review, or in review where they are the reviewer. $this->setUser($reviewer); $result = api::list_user_competencies_to_review(); $this->assertEquals(4, $result['count']); $this->assertEquals($uc2a->get('id'), $result['competencies'][0]->usercompetency->get('id')); $this->assertEquals($uc1b->get('id'), $result['competencies'][1]->usercompetency->get('id')); $this->assertEquals($uc1c->get('id'), $result['competencies'][2]->usercompetency->get('id')); $this->assertEquals($uc3a->get('id'), $result['competencies'][3]->usercompetency->get('id')); // Now, let's delete user 3. // It should not be listed on user competencies to review any more. user_delete_user($u3); $result = api::list_user_competencies_to_review(); $this->assertEquals(3, $result['count']); // The reviewer cannot view the plans when they do not have the permission in the user's context. role_assign($roleprohibit, $reviewer->id, \context_user::instance($u2->id)->id); accesslib_clear_all_caches_for_unit_testing(); $result = api::list_user_competencies_to_review(); $this->assertEquals(2, $result['count']); $this->assertEquals($uc1b->get('id'), $result['competencies'][0]->usercompetency->get('id')); $this->assertEquals($uc1c->get('id'), $result['competencies'][1]->usercompetency->get('id')); } /** * Test we can get all of a users plans with a competency. */ public function test_list_plans_with_competency(): void { $this->resetAfterTest(true); $this->setAdminUser(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $u1 = $this->getDataGenerator()->create_user(); $tpl = $this->getDataGenerator()->get_plugin_generator('core_competency')->create_template(); // Create a framework and assign competencies. $framework = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); // Create two plans and assign the competency to each. $plan1 = $lpg->create_plan(array('userid' => $u1->id)); $plan2 = $lpg->create_plan(array('userid' => $u1->id)); $lpg->create_plan_competency(array('planid' => $plan1->get('id'), 'competencyid' => $c1->get('id'))); $lpg->create_plan_competency(array('planid' => $plan2->get('id'), 'competencyid' => $c1->get('id'))); // Create one more plan without the competency. $plan3 = $lpg->create_plan(array('userid' => $u1->id)); $plans = api::list_plans_with_competency($u1->id, $c1); $this->assertEquals(2, count($plans)); $this->assertEquals(reset($plans)->get('id'), $plan1->get('id')); $this->assertEquals(end($plans)->get('id'), $plan2->get('id')); } } tests/event/events_test.php000064400000207403152265256010012115 0ustar00. namespace core_competency\event; use core_competency\api; use core_competency\plan; use core_competency\url; defined('MOODLE_INTERNAL') || die(); global $CFG; require_once($CFG->dirroot . '/comment/lib.php'); /** * Event tests. * * @package core_competency * @copyright 2016 Serge Gauthier * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class events_test extends \advanced_testcase { /** * Test the competency framework created event. * */ public function test_competency_framework_created(): void { $this->resetAfterTest(true); $this->setAdminUser(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); // Use DataGenerator to have a record framework with the right format. $record = $lpg->create_framework()->to_record(); $record->id = 0; $record->shortname = "New shortname"; $record->idnumber = "New idnumber"; // Trigger and capture the event. $sink = $this->redirectEvents(); $framework = api::create_framework((object) $record); // Get our event event. $events = $sink->get_events(); $event = reset($events); // Check that the event data is valid. $this->assertInstanceOf('\core\event\competency_framework_created', $event); $this->assertEquals($framework->get('id'), $event->objectid); $this->assertEquals($framework->get('contextid'), $event->contextid); $this->assertEventContextNotUsed($event); $this->assertDebuggingNotCalled(); } /** * Test the competency framework deleted event. * */ public function test_competency_framework_deleted(): void { $this->resetAfterTest(true); $this->setAdminUser(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $framework = $lpg->create_framework(); // Trigger and capture the event. $sink = $this->redirectEvents(); api::delete_framework($framework->get('id')); // Get our event event. $events = $sink->get_events(); $event = reset($events); // Check that the event data is valid. $this->assertInstanceOf('\core\event\competency_framework_deleted', $event); $this->assertEquals($framework->get('id'), $event->objectid); $this->assertEquals($framework->get('contextid'), $event->contextid); $this->assertEventContextNotUsed($event); $this->assertDebuggingNotCalled(); } /** * Test the competency framework updated event. * */ public function test_competency_framework_updated(): void { $this->resetAfterTest(true); $this->setAdminUser(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $framework = $lpg->create_framework(); // Trigger and capture the event. $sink = $this->redirectEvents(); $framework->set('shortname', 'Shortname modified'); api::update_framework($framework->to_record()); // Get our event event. $events = $sink->get_events(); $event = reset($events); // Check that the event data is valid. $this->assertInstanceOf('\core\event\competency_framework_updated', $event); $this->assertEquals($framework->get('id'), $event->objectid); $this->assertEquals($framework->get('contextid'), $event->contextid); $this->assertEventContextNotUsed($event); $this->assertDebuggingNotCalled(); } /** * Test the competency framework viewed event. * */ public function test_competency_framework_viewed(): void { $this->resetAfterTest(true); $this->setAdminUser(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $framework = $lpg->create_framework(); // Trigger and capture the event. $sink = $this->redirectEvents(); api::competency_framework_viewed($framework); // Get our event event. $events = $sink->get_events(); $event = reset($events); // Check that the event data is valid. $this->assertInstanceOf('\core\event\competency_framework_viewed', $event); $this->assertEquals($framework->get('id'), $event->objectid); $this->assertEquals($framework->get('contextid'), $event->contextid); $this->assertEventContextNotUsed($event); $this->assertDebuggingNotCalled(); } /** * Test the competency viewed event. * */ public function test_competency_viewed(): void { $this->resetAfterTest(true); $this->setAdminUser(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $framework = $lpg->create_framework(); $competency = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); // Trigger and capture the event. $sink = $this->redirectEvents(); api::competency_viewed($competency); // Get our event event. $events = $sink->get_events(); $event = reset($events); // Check that the event data is valid. $this->assertInstanceOf('\core\event\competency_viewed', $event); $this->assertEquals($competency->get('id'), $event->objectid); $this->assertEquals($competency->get_context()->id, $event->contextid); $this->assertEventContextNotUsed($event); $this->assertDebuggingNotCalled(); } /** * Test the template viewed event. * */ public function test_template_viewed(): void { $this->resetAfterTest(true); $this->setAdminUser(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $template = $lpg->create_template(); // Trigger and capture the event. $sink = $this->redirectEvents(); api::template_viewed($template); // Get our event event. $events = $sink->get_events(); $event = reset($events); // Check that the event data is valid. $this->assertInstanceOf('\core\event\competency_template_viewed', $event); $this->assertEquals($template->get('id'), $event->objectid); $this->assertEquals($template->get('contextid'), $event->contextid); $this->assertEventContextNotUsed($event); $this->assertDebuggingNotCalled(); } /** * Test the template created event. * */ public function test_template_created(): void { $this->resetAfterTest(true); $this->setAdminUser(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); // Use DataGenerator to have a template record with the right format. $record = $lpg->create_template()->to_record(); $record->id = 0; $record->shortname = "New shortname"; // Trigger and capture the event. $sink = $this->redirectEvents(); $template = api::create_template((object) $record); // Get our event event. $events = $sink->get_events(); $event = reset($events); $this->assertInstanceOf('\core\event\competency_template_created', $event); $this->assertEquals($template->get('id'), $event->objectid); $this->assertEquals($template->get('contextid'), $event->contextid); $this->assertEventContextNotUsed($event); $this->assertDebuggingNotCalled(); } /** * Test the template deleted event. * */ public function test_template_deleted(): void { $this->resetAfterTest(true); $this->setAdminUser(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $template = $lpg->create_template(); // Trigger and capture the event. $sink = $this->redirectEvents(); api::delete_template($template->get('id')); // Get our event event. $events = $sink->get_events(); $event = reset($events); // Check that the event data is valid. $this->assertInstanceOf('\core\event\competency_template_deleted', $event); $this->assertEquals($template->get('id'), $event->objectid); $this->assertEquals($template->get('contextid'), $event->contextid); $this->assertEventContextNotUsed($event); $this->assertDebuggingNotCalled(); } /** * Test the template updated event. * */ public function test_template_updated(): void { $this->resetAfterTest(true); $this->setAdminUser(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $template = $lpg->create_template(); // Trigger and capture the event. $sink = $this->redirectEvents(); $template->set('shortname', 'Shortname modified'); api::update_template($template->to_record()); // Get our event event. $events = $sink->get_events(); $event = reset($events); // Check that the event data is valid. $this->assertInstanceOf('\core\event\competency_template_updated', $event); $this->assertEquals($template->get('id'), $event->objectid); $this->assertEquals($template->get('contextid'), $event->contextid); $this->assertEventContextNotUsed($event); $this->assertDebuggingNotCalled(); } /** * Test the competency updated event. * */ public function test_competency_updated(): void { $this->resetAfterTest(true); $this->setAdminUser(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $f1 = $lpg->create_framework(); $competency = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c12 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1->get('id'))); $c13 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1->get('id'))); // Trigger and capture the event. $sink = $this->redirectEvents(); $competency->set('shortname', 'Shortname modified'); api::update_competency($competency->to_record()); // Get our event event. $events = $sink->get_events(); $event = reset($events); // Check that the event data is valid. $this->assertInstanceOf('\core\event\competency_updated', $event); $this->assertEquals($competency->get('id'), $event->objectid); $this->assertEquals($competency->get_context()->id, $event->contextid); $this->assertEventContextNotUsed($event); $this->assertDebuggingNotCalled(); } /** * Test the competency created event. * */ public function test_competency_created(): void { $this->resetAfterTest(true); $this->setAdminUser(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $f1 = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $record = $c1->to_record(); $record->id = 0; $record->idnumber = 'comp idnumber'; // Trigger and capture the event. $sink = $this->redirectEvents(); // Create competency should trigger a created event. $competency = api::create_competency($record); // Get our event event. $events = $sink->get_events(); $event = reset($events); $this->assertInstanceOf('\core\event\competency_created', $event); $this->assertEquals($competency->get('id'), $event->objectid); $this->assertEquals($competency->get_context()->id, $event->contextid); $this->assertEventContextNotUsed($event); $this->assertDebuggingNotCalled(); } /** * Test the competency created event by duplicate framework. * */ public function test_competency_created_by_duplicateframework(): void { $this->resetAfterTest(true); $this->setAdminUser(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $f1 = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c12 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1->get('id'))); // Trigger and capture the event. $sink = $this->redirectEvents(); // Create framework should trigger a created event for competencies. api::duplicate_framework($f1->get('id')); // Get our event event. $events = $sink->get_events(); $this->assertEquals(4, count($events)); $event = array_shift($events); $this->assertInstanceOf('\core\event\competency_created', $event); $event = array_shift($events); $this->assertInstanceOf('\core\event\competency_created', $event); $event = array_shift($events); $this->assertInstanceOf('\core\event\competency_created', $event); $event = array_shift($events); $this->assertInstanceOf('\core\event\competency_framework_created', $event); } /** * Test the competency deleted event. * */ public function test_competency_deleted(): void { $this->resetAfterTest(true); $this->setAdminUser(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $f1 = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c1id = $c1->get('id'); $contextid = $c1->get_context()->id; // Trigger and capture the event. $sink = $this->redirectEvents(); // Delete competency should trigger a deleted event. api::delete_competency($c1id); // Get our event event. $events = $sink->get_events(); $event = reset($events); $this->assertInstanceOf('\core\event\competency_deleted', $event); $this->assertEquals($c1id, $event->objectid); $this->assertEquals($contextid, $event->contextid); $this->assertEventContextNotUsed($event); $this->assertDebuggingNotCalled(); } /** * Test the competency deleted event by delete framework. * */ public function test_competency_deleted_by_deleteframework(): void { $this->resetAfterTest(true); $this->setAdminUser(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $f1 = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'))); $c12 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id'), 'parentid' => $c1->get('id'))); // Trigger and capture the event. $sink = $this->redirectEvents(); // Delete framework should trigger a deleted event for competencies. api::delete_framework($f1->get('id')); // Get our event event. $events = $sink->get_events(); $this->assertEquals(4, count($events)); $event = array_shift($events); $this->assertInstanceOf('\core\event\competency_framework_deleted', $event); $event = array_shift($events); $this->assertInstanceOf('\core\event\competency_deleted', $event); $event = array_shift($events); $this->assertInstanceOf('\core\event\competency_deleted', $event); $event = array_shift($events); $this->assertInstanceOf('\core\event\competency_deleted', $event); } /** * Test the plan created event. * */ public function test_plan_created(): void { $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $user = $dg->create_user(); $plan = array ( 'name' => 'plan', 'userid' => $user->id ); // Trigger and capture the event. $sink = $this->redirectEvents(); $plan = api::create_plan((object)$plan); // Get our event event. $events = $sink->get_events(); $event = reset($events); // Check that the event data is valid. $this->assertInstanceOf('\core\event\competency_plan_created', $event); $this->assertEquals($plan->get('id'), $event->objectid); $this->assertEquals($plan->get_context()->id, $event->contextid); $this->assertEventContextNotUsed($event); $this->assertDebuggingNotCalled(); } /** * Test the plan created event using template_cohort. * */ public function test_plan_created_using_templatecohort(): void { $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $user1 = $dg->create_user(); $user2 = $dg->create_user(); $c1 = $dg->create_cohort(); // Add 2 users to the cohort. cohort_add_member($c1->id, $user1->id); cohort_add_member($c1->id, $user2->id); $t1 = $lpg->create_template(); $tc = $lpg->create_template_cohort(array( 'templateid' => $t1->get('id'), 'cohortid' => $c1->id )); // Trigger and capture the event. $sink = $this->redirectEvents(); api::create_plans_from_template_cohort($t1->get('id'), $c1->id); // Get our event event. $plans = plan::get_records(array('templateid' => $t1->get('id')), 'id'); $events = $sink->get_events(); $this->assertCount(2, $events); $this->assertCount(2, $plans); $event = $events[0]; $plan = $plans[0]; // Check that the event data is valid. $this->assertInstanceOf('\core\event\competency_plan_created', $event); $this->assertEquals($plan->get('id'), $event->objectid); $this->assertEquals($plan->get_context()->id, $event->contextid); $event = $events[1]; $plan = $plans[1]; $this->assertInstanceOf('\core\event\competency_plan_created', $event); $this->assertEquals($plan->get('id'), $event->objectid); $this->assertEquals($plan->get_context()->id, $event->contextid); $this->assertEventContextNotUsed($event); $this->assertDebuggingNotCalled(); } /** * Test the plan updated event. * */ public function test_plan_updated(): void { $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $user1 = $dg->create_user(); $plan = $lpg->create_plan(array('userid' => $user1->id)); $record = $plan->to_record(); $record->name = 'Plan updated'; // Trigger and capture the event. $sink = $this->redirectEvents(); $plan = api::update_plan($record); $this->assertEquals('Plan updated', $plan->get('name')); // Get our event event. $events = $sink->get_events(); $event = reset($events); $this->assertInstanceOf('\core\event\competency_plan_updated', $event); $this->assertEquals($plan->get('id'), $event->objectid); $this->assertEquals($plan->get_context()->id, $event->contextid); $this->assertEventContextNotUsed($event); $this->assertDebuggingNotCalled(); } /** * Test the plan deleted event. * */ public function test_plan_deleted(): void { $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $user1 = $dg->create_user(); $plan = $lpg->create_plan(array('userid' => $user1->id)); $planid = $plan->get('id'); $contextid = $plan->get_context()->id; // Trigger and capture the event. $sink = $this->redirectEvents(); $result = api::delete_plan($plan->get('id')); $this->assertTrue($result); // Get our event event. $events = $sink->get_events(); $event = reset($events); $this->assertInstanceOf('\core\event\competency_plan_deleted', $event); $this->assertEquals($planid, $event->objectid); $this->assertEquals($contextid, $event->contextid); $this->assertEventContextNotUsed($event); $this->assertDebuggingNotCalled(); } /** * Test the plan viewed event. * */ public function test_plan_viewed(): void { $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $user1 = $dg->create_user(); $plan = $lpg->create_plan(array('userid' => $user1->id)); // Trigger and capture the event. $sink = $this->redirectEvents(); api::plan_viewed($plan); // Get our event event. $events = $sink->get_events(); $event = reset($events); // Check that the event data is valid. $this->assertInstanceOf('\core\event\competency_plan_viewed', $event); $this->assertEquals($plan->get('id'), $event->objectid); $this->assertEquals($plan->get_context()->id, $event->contextid); $this->assertEventContextNotUsed($event); $this->assertDebuggingNotCalled(); } /** * Test the evidence of prior learning created event. * */ public function test_user_evidence_created(): void { $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $user = $dg->create_user(); // Use DataGenerator to have a user_evidence record with the right format. $record = $userevidence = $lpg->create_user_evidence(array('userid' => $user->id))->to_record(); $record->id = 0; $record->name = "New name"; // Trigger and capture the event. $sink = $this->redirectEvents(); $userevidence = api::create_user_evidence((object) $record); // Get our event event. $events = $sink->get_events(); $event = reset($events); $this->assertInstanceOf('\core\event\competency_user_evidence_created', $event); $this->assertEquals($userevidence->get('id'), $event->objectid); $this->assertEquals($userevidence->get_context()->id, $event->contextid); $this->assertEventContextNotUsed($event); $this->assertDebuggingNotCalled(); } /** * Test the evidence of prior learning deleted event. * */ public function test_user_evidence_deleted(): void { $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $user = $dg->create_user(); $userevidence = $lpg->create_user_evidence(array('userid' => $user->id)); // Trigger and capture the event. $sink = $this->redirectEvents(); api::delete_user_evidence($userevidence->get('id')); // Get our event event. $events = $sink->get_events(); $event = reset($events); // Check that the event data is valid. $this->assertInstanceOf('\core\event\competency_user_evidence_deleted', $event); $this->assertEquals($userevidence->get('id'), $event->objectid); $this->assertEquals($userevidence->get_context()->id, $event->contextid); $this->assertEventContextNotUsed($event); $this->assertDebuggingNotCalled(); } /** * Test the evidence of prior learning updated event. * */ public function test_user_evidence_updated(): void { $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $user = $dg->create_user(); $userevidence = $lpg->create_user_evidence(array('userid' => $user->id)); // Trigger and capture the event. $sink = $this->redirectEvents(); $userevidence->set('name', 'Name modified'); api::update_user_evidence($userevidence->to_record()); // Get our event event. $events = $sink->get_events(); $event = reset($events); // Check that the event data is valid. $this->assertInstanceOf('\core\event\competency_user_evidence_updated', $event); $this->assertEquals($userevidence->get('id'), $event->objectid); $this->assertEquals($userevidence->get_context()->id, $event->contextid); $this->assertEventContextNotUsed($event); $this->assertDebuggingNotCalled(); } /** * Test the user competency viewed event in plan. * */ public function test_user_competency_viewed_in_plan(): void { $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $user = $dg->create_user(); $plan = $lpg->create_plan(array('userid' => $user->id)); $fr = $lpg->create_framework(); $c = $lpg->create_competency(array('competencyframeworkid' => $fr->get('id'))); $pc = $lpg->create_plan_competency(array('planid' => $plan->get('id'), 'competencyid' => $c->get('id'))); $uc = $lpg->create_user_competency(array('userid' => $user->id, 'competencyid' => $c->get('id'))); // Can not log the event for user competency using completed plan. api::complete_plan($plan); try { api::user_competency_viewed_in_plan($uc, $plan->get('id')); $this->fail('To log the user competency in a completed plan ' . 'use the user_competency_plan_viewed method.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/To log the user competency in a completed plan ' . 'use the user_competency_plan_viewed method./', $e->getMessage()); } api::reopen_plan($plan); // Trigger and capture the event. $sink = $this->redirectEvents(); api::user_competency_viewed_in_plan($uc, $plan->get('id')); // Get our event event. $events = $sink->get_events(); $event = reset($events); // Check that the event data is valid. $this->assertInstanceOf('\core\event\competency_user_competency_viewed_in_plan', $event); $this->assertEquals($uc->get('id'), $event->objectid); $this->assertEquals($uc->get_context()->id, $event->contextid); $this->assertEquals($uc->get('userid'), $event->relateduserid); $this->assertEquals($plan->get('id'), $event->other['planid']); $this->assertEquals($c->get('id'), $event->other['competencyid']); $this->assertEventContextNotUsed($event); $this->assertDebuggingNotCalled(); // Test validation. $params = array ( 'objectid' => $uc->get('id'), 'contextid' => $uc->get_context()->id, 'other' => null ); // Other value null. try { \core\event\competency_user_competency_viewed_in_plan::create($params)->trigger(); $this->fail('The \'competencyid\' and \'planid\' values must be set.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression("/The 'competencyid' and 'planid' values must be set./", $e->getMessage()); } $params['other']['anythingelse'] = ''; // Missing competencyid. try { \core\event\competency_user_competency_viewed_in_plan::create($params)->trigger(); $this->fail('The \'competencyid\' value must be set.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression("/The 'competencyid' value must be set./", $e->getMessage()); } $params['other']['competencyid'] = $c->get('id'); // Missing planid. try { \core\event\competency_user_competency_viewed_in_plan::create($params)->trigger(); $this->fail('The \'planid\' value must be set.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression("/The 'planid' value must be set./", $e->getMessage()); } } /** * Test the user competency viewed event in course. * */ public function test_user_competency_viewed_in_course(): void { $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $user = $dg->create_user(); $course = $dg->create_course(); $fr = $lpg->create_framework(); $c = $lpg->create_competency(array('competencyframeworkid' => $fr->get('id'))); $pc = $lpg->create_course_competency(array('courseid' => $course->id, 'competencyid' => $c->get('id'))); $params = array('userid' => $user->id, 'competencyid' => $c->get('id'), 'courseid' => $course->id); $ucc = $lpg->create_user_competency_course($params); // Trigger and capture the event. $sink = $this->redirectEvents(); api::user_competency_viewed_in_course($ucc); // Get our event event. $events = $sink->get_events(); $event = reset($events); // Check that the event data is valid. $this->assertInstanceOf('\core\event\competency_user_competency_viewed_in_course', $event); $this->assertEquals($ucc->get('id'), $event->objectid); $this->assertEquals(\context_course::instance($course->id)->id, $event->contextid); $this->assertEquals($ucc->get('userid'), $event->relateduserid); $this->assertEquals($course->id, $event->courseid); $this->assertEquals($c->get('id'), $event->other['competencyid']); $this->assertEventContextNotUsed($event); $this->assertDebuggingNotCalled(); // Test validation. $params = array ( 'objectid' => $ucc->get('id'), 'contextid' => $ucc->get_context()->id, 'other' => null ); // Missing courseid. try { \core\event\competency_user_competency_viewed_in_course::create($params)->trigger(); $this->fail('The \'courseid\' value must be set.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression("/The 'courseid' value must be set./", $e->getMessage()); } $params['contextid'] = \context_course::instance($course->id)->id; $params['courseid'] = $course->id; // Missing competencyid. try { \core\event\competency_user_competency_viewed_in_course::create($params)->trigger(); $this->fail('The \'competencyid\' value must be set.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression("/The 'competencyid' value must be set./", $e->getMessage()); } } /** * Test the user competency plan viewed event. * */ public function test_user_competency_plan_viewed(): void { $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $user = $dg->create_user(); $plan = $lpg->create_plan(array('userid' => $user->id)); $fr = $lpg->create_framework(); $c = $lpg->create_competency(array('competencyframeworkid' => $fr->get('id'))); $ucp = $lpg->create_user_competency_plan(array( 'userid' => $user->id, 'competencyid' => $c->get('id'), 'planid' => $plan->get('id') )); // Can not log the event for user competency using non completed plan. try { api::user_competency_plan_viewed($ucp); $this->fail('To log the user competency in non-completed plan ' . 'use user_competency_viewed_in_plan method.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression('/To log the user competency in non-completed plan ' . 'use user_competency_viewed_in_plan method./', $e->getMessage()); } // Complete the plan. api::complete_plan($plan); // Trigger and capture the event. $sink = $this->redirectEvents(); api::user_competency_plan_viewed($ucp); // Get our event event. $events = $sink->get_events(); $event = reset($events); // Check that the event data is valid. $this->assertInstanceOf('\core\event\competency_user_competency_plan_viewed', $event); $this->assertEquals($ucp->get('id'), $event->objectid); $this->assertEquals($ucp->get_context()->id, $event->contextid); $this->assertEquals($ucp->get('userid'), $event->relateduserid); $this->assertEquals($plan->get('id'), $event->other['planid']); $this->assertEquals($c->get('id'), $event->other['competencyid']); $this->assertEventContextNotUsed($event); $this->assertDebuggingNotCalled(); // Test validation. $params = array ( 'objectid' => $ucp->get('id'), 'contextid' => $ucp->get_context()->id, 'other' => null ); // Other value null. try { \core\event\competency_user_competency_plan_viewed::create($params)->trigger(); $this->fail('The \'competencyid\' and \'planid\' values must be set.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression("/The 'competencyid' and 'planid' values must be set./", $e->getMessage()); } $params['other']['anythingelse'] = ''; // Missing competencyid. try { \core\event\competency_user_competency_plan_viewed::create($params)->trigger(); $this->fail('The \'competencyid\' value must be set.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression("/The 'competencyid' value must be set./", $e->getMessage()); } $params['other']['competencyid'] = $c->get('id'); // Missing planid. try { \core\event\competency_user_competency_plan_viewed::create($params)->trigger(); $this->fail('The \'planid\' value must be set.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression("/The 'planid' value must be set./", $e->getMessage()); } } /** * Test the user competency viewed event. * */ public function test_user_competency_viewed(): void { $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $user = $dg->create_user(); $fr = $lpg->create_framework(); $c = $lpg->create_competency(array('competencyframeworkid' => $fr->get('id'))); $uc = $lpg->create_user_competency(array( 'userid' => $user->id, 'competencyid' => $c->get('id') )); // Trigger and capture the event. $sink = $this->redirectEvents(); api::user_competency_viewed($uc); // Get our event event. $events = $sink->get_events(); $event = reset($events); // Check that the event data is valid. $this->assertInstanceOf('\core\event\competency_user_competency_viewed', $event); $this->assertEquals($uc->get('id'), $event->objectid); $this->assertEquals($uc->get_context()->id, $event->contextid); $this->assertEquals($uc->get('userid'), $event->relateduserid); $this->assertEquals($c->get('id'), $event->other['competencyid']); $this->assertEventContextNotUsed($event); $this->assertDebuggingNotCalled(); // Test validation. $params = array ( 'objectid' => $uc->get('id'), 'contextid' => $uc->get_context()->id ); // Missing competencyid. try { \core\event\competency_user_competency_viewed::create($params)->trigger(); $this->fail('The \'competencyid\' value must be set.'); } catch (\coding_exception $e) { $this->assertMatchesRegularExpression("/The 'competencyid' value must be set./", $e->getMessage()); } } /** * Test the plan approved event. * */ public function test_plan_approved(): void { $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $user1 = $dg->create_user(); $plan = $lpg->create_plan(array('userid' => $user1->id)); $planid = $plan->get('id'); $contextid = $plan->get_context()->id; // Trigger and capture the event. $sink = $this->redirectEvents(); $result = api::approve_plan($plan->get('id')); $this->assertTrue($result); // Get our event event. $events = $sink->get_events(); $event = reset($events); $this->assertInstanceOf('\core\event\competency_plan_approved', $event); $this->assertEquals($planid, $event->objectid); $this->assertEquals($contextid, $event->contextid); $this->assertEquals($plan->get('userid'), $event->relateduserid); $this->assertEventContextNotUsed($event); $this->assertDebuggingNotCalled(); } /** * Test the plan unapproved event. * */ public function test_plan_unapproved(): void { $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $user1 = $dg->create_user(); $plan = $lpg->create_plan(array('userid' => $user1->id, 'status' => \core_competency\plan::STATUS_ACTIVE)); $planid = $plan->get('id'); $contextid = $plan->get_context()->id; // Trigger and capture the event. $sink = $this->redirectEvents(); $result = api::unapprove_plan($plan->get('id')); $this->assertTrue($result); // Get our event event. $events = $sink->get_events(); $event = reset($events); $this->assertInstanceOf('\core\event\competency_plan_unapproved', $event); $this->assertEquals($planid, $event->objectid); $this->assertEquals($contextid, $event->contextid); $this->assertEquals($plan->get('userid'), $event->relateduserid); $this->assertEventContextNotUsed($event); $this->assertDebuggingNotCalled(); } /** * Test the plan reopened event. * */ public function test_plan_reopened(): void { $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $user1 = $dg->create_user(); $plan = $lpg->create_plan(array('userid' => $user1->id, 'status' => \core_competency\plan::STATUS_COMPLETE)); $planid = $plan->get('id'); $contextid = $plan->get_context()->id; // Trigger and capture the event. $sink = $this->redirectEvents(); $result = api::reopen_plan($plan->get('id')); $this->assertTrue($result); // Get our event event. $events = $sink->get_events(); $event = reset($events); $this->assertInstanceOf('\core\event\competency_plan_reopened', $event); $this->assertEquals($planid, $event->objectid); $this->assertEquals($contextid, $event->contextid); $this->assertEquals($plan->get('userid'), $event->relateduserid); $this->assertEventContextNotUsed($event); $this->assertDebuggingNotCalled(); } /** * Test the plan completed event. * */ public function test_plan_completed(): void { $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $user1 = $dg->create_user(); $plan = $lpg->create_plan(array('userid' => $user1->id, 'status' => \core_competency\plan::STATUS_ACTIVE)); $planid = $plan->get('id'); $contextid = $plan->get_context()->id; // Trigger and capture the event. $sink = $this->redirectEvents(); $result = api::complete_plan($plan->get('id')); $this->assertTrue($result); // Get our event event. $events = $sink->get_events(); $event = reset($events); $this->assertInstanceOf('\core\event\competency_plan_completed', $event); $this->assertEquals($planid, $event->objectid); $this->assertEquals($contextid, $event->contextid); $this->assertEquals($plan->get('userid'), $event->relateduserid); $this->assertEventContextNotUsed($event); $this->assertDebuggingNotCalled(); } /** * Test the plan unlinked event. * */ public function test_plan_unlinked(): void { $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $user1 = $dg->create_user(); $template = $lpg->create_template(); $plan = $lpg->create_plan(array( 'userid' => $user1->id, 'status' => \core_competency\plan::STATUS_ACTIVE, 'templateid' => $template->get('id') )); $planid = $plan->get('id'); $contextid = $plan->get_context()->id; // Trigger and capture the event. $sink = $this->redirectEvents(); $result = api::unlink_plan_from_template($plan->get('id')); $this->assertTrue($result); // Get our event event. $events = $sink->get_events(); $event = reset($events); $this->assertInstanceOf('\core\event\competency_plan_unlinked', $event); $this->assertEquals($planid, $event->objectid); $this->assertEquals($contextid, $event->contextid); $this->assertEquals($plan->get('userid'), $event->relateduserid); $this->assertEventContextNotUsed($event); $this->assertDebuggingNotCalled(); } /** * Test the plan review requested event. * */ public function test_plan_review_requested(): void { $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $user1 = $dg->create_user(); $plan = $lpg->create_plan(array('userid' => $user1->id)); $planid = $plan->get('id'); $contextid = $plan->get_context()->id; // Trigger and capture the event. $sink = $this->redirectEvents(); $result = api::plan_request_review($plan->get('id')); $this->assertTrue($result); // Get our event event. $events = $sink->get_events(); $event = reset($events); $this->assertInstanceOf('\core\event\competency_plan_review_requested', $event); $this->assertEquals($planid, $event->objectid); $this->assertEquals($contextid, $event->contextid); $this->assertEquals($plan->get('userid'), $event->relateduserid); $this->assertEventContextNotUsed($event); $this->assertDebuggingNotCalled(); } /** * Test the plan review request cancelled event. * */ public function test_plan_review_request_cancelled(): void { $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $user1 = $dg->create_user(); $plan = $lpg->create_plan(array('userid' => $user1->id, 'status' => \core_competency\plan::STATUS_WAITING_FOR_REVIEW)); $planid = $plan->get('id'); $contextid = $plan->get_context()->id; // Trigger and capture the event. $sink = $this->redirectEvents(); $result = api::plan_cancel_review_request($plan->get('id')); $this->assertTrue($result); // Get our event event. $events = $sink->get_events(); $event = reset($events); $this->assertInstanceOf('\core\event\competency_plan_review_request_cancelled', $event); $this->assertEquals($planid, $event->objectid); $this->assertEquals($contextid, $event->contextid); $this->assertEquals($plan->get('userid'), $event->relateduserid); $this->assertEventContextNotUsed($event); $this->assertDebuggingNotCalled(); } /** * Test the plan review started event. * */ public function test_plan_review_started(): void { $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $user1 = $dg->create_user(); $plan = $lpg->create_plan(array('userid' => $user1->id, 'status' => \core_competency\plan::STATUS_WAITING_FOR_REVIEW)); $planid = $plan->get('id'); $contextid = $plan->get_context()->id; // Trigger and capture the event. $sink = $this->redirectEvents(); $result = api::plan_start_review($plan->get('id')); $this->assertTrue($result); // Get our event event. $events = $sink->get_events(); $event = reset($events); $this->assertInstanceOf('\core\event\competency_plan_review_started', $event); $this->assertEquals($planid, $event->objectid); $this->assertEquals($contextid, $event->contextid); $this->assertEquals($plan->get('userid'), $event->relateduserid); $this->assertEventContextNotUsed($event); $this->assertDebuggingNotCalled(); } /** * Test the plan review stopped event. * */ public function test_plan_review_stopped(): void { $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $user1 = $dg->create_user(); $plan = $lpg->create_plan(array('userid' => $user1->id, 'status' => \core_competency\plan::STATUS_IN_REVIEW)); $planid = $plan->get('id'); $contextid = $plan->get_context()->id; // Trigger and capture the event. $sink = $this->redirectEvents(); $result = api::plan_stop_review($plan->get('id')); $this->assertTrue($result); // Get our event event. $events = $sink->get_events(); $event = reset($events); $this->assertInstanceOf('\core\event\competency_plan_review_stopped', $event); $this->assertEquals($planid, $event->objectid); $this->assertEquals($contextid, $event->contextid); $this->assertEquals($plan->get('userid'), $event->relateduserid); $this->assertEventContextNotUsed($event); $this->assertDebuggingNotCalled(); } /** * Test plan comment created event. */ public function test_plan_comment_created(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $user = $dg->create_user(); $this->setUser($user); $plan = $lpg->create_plan(array('userid' => $user->id)); $context = \context_user::instance($user->id); $cmt = new \stdClass(); $cmt->context = $context; $cmt->area = 'plan'; $cmt->itemid = $plan->get('id'); $cmt->component = 'competency'; $cmt->showcount = 1; $manager = new \comment($cmt); $manager->set_post_permission(true); // Triggering and capturing the event. $sink = $this->redirectEvents(); $manager->add("New comment for plan"); $events = $sink->get_events(); // Add comment will trigger 2 other events message_viewed and message_sent. $this->assertCount(1, $events); $event = array_pop($events); // Checking that the event contains the expected values. $this->assertInstanceOf('\core\event\competency_comment_created', $event); $this->assertEquals($context, $event->get_context()); $this->assertEquals($plan->get('id'), $event->other['itemid']); $this->assertEventContextNotUsed($event); $this->assertDebuggingNotCalled(); } /** * Test plan comment deleted event. */ public function test_plan_comment_deleted(): void { $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $user1 = $dg->create_user(); $plan = $lpg->create_plan(array('userid' => $user1->id)); $context = \context_user::instance($user1->id); $cmt = new \stdClass(); $cmt->context = $context; $cmt->area = 'plan'; $cmt->itemid = $plan->get('id'); $cmt->component = 'competency'; $manager = new \comment($cmt); $newcomment = $manager->add("Comment to be deleted"); // Triggering and capturing the event. $sink = $this->redirectEvents(); $manager->delete($newcomment->id); $events = $sink->get_events(); $this->assertCount(1, $events); $event = reset($events); // Checking that the event contains the expected values. $this->assertInstanceOf('\core\event\competency_comment_deleted', $event); $this->assertEquals($context, $event->get_context()); $this->assertEquals($plan->get('id'), $event->other['itemid']); $this->assertEventContextNotUsed($event); $this->assertDebuggingNotCalled(); } /** * Test evidence_created event. */ public function test_evidence_created(): void { global $USER; $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $syscontext = \context_system::instance(); // Create a student. $student = $dg->create_user(); // Create a competency for the course. $lpg = $dg->get_plugin_generator('core_competency'); $framework = $lpg->create_framework(); $comp = $lpg->create_competency(['competencyframeworkid' => $framework->get('id')]); // Trigger and capture the event. $sink = $this->redirectEvents(); // Add evidence. $recommend = false; $evidence = api::add_evidence($student->id, $comp, $syscontext, \core_competency\evidence::ACTION_OVERRIDE, 'commentincontext', 'core', null, $recommend, null, 1); // Get event. $events = $sink->get_events(); $event = reset($events); // Check that the event data is valid. $this->assertInstanceOf('\core\event\competency_evidence_created', $event); $this->assertEquals($evidence->get('contextid'), $event->contextid); $this->assertEquals($evidence->get('id'), $event->objectid); $this->assertEquals($evidence->get('actionuserid'), $event->userid); $this->assertEquals($student->id, $event->relateduserid); $this->assertEquals($evidence->get('usercompetencyid'), $event->other['usercompetencyid']); $this->assertEquals($comp->get('id'), $event->other['competencyid']); $this->assertEquals($evidence->get('action'), $event->other['action']); $this->assertEquals($recommend, $event->other['recommend']); // Test get_name(). $this->assertEquals(get_string('eventevidencecreated', 'core_competency'), $event->get_name()); // Test get_description(). $description = "The user with id '$USER->id' created an evidence with id '{$evidence->get('id')}'."; $this->assertEquals($description, $event->get_description()); // Test get_url(). $url = url::user_competency($evidence->get('usercompetencyid')); $this->assertEquals($url, $event->get_url()); // Test get_objectid_mapping(). $this->assertEquals(\core\event\base::NOT_MAPPED, $event->get_objectid_mapping()); $this->assertEventContextNotUsed($event); $this->assertDebuggingNotCalled(); } /** * Test evidence_created event by linking an invalid user competency to an evidence. */ public function test_evidence_created_with_invalid_user_competency(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $syscontext = \context_system::instance(); // Create students. $student = $dg->create_user(); $student2 = $dg->create_user(); // Create a competency for the course. $lpg = $dg->get_plugin_generator('core_competency'); $framework = $lpg->create_framework(); $comp = $lpg->create_competency(['competencyframeworkid' => $framework->get('id')]); // Create a different user competency. $otheruc = \core_competency\user_competency::create_relation($student2->id, $comp->get('id')); $otheruc->create(); // Add evidence. $recommend = false; $evidence = api::add_evidence($student->id, $comp, $syscontext, \core_competency\evidence::ACTION_OVERRIDE, 'commentincontext', 'core', null, $recommend, null, 1); // We expect this to fail and throw a coding exception. $this->expectException('coding_exception'); $this->expectExceptionMessage('The user competency linked with this evidence is invalid.'); \core\event\competency_evidence_created::create_from_evidence($evidence, $otheruc, $recommend)->trigger(); } /** * Test creation of evidence_created event with missing data. * * These data are validated by \core_competency\evidence_created::validate_data(). */ public function test_evidence_created_with_missing_data(): void { $eventdata = [ 'contextid' => 1, 'objectid' => 1, 'userid' => 1 ]; // No relateduserid. $errormsg = 'The \'relateduserid\' must be set.'; try { \core\event\competency_evidence_created::create($eventdata)->trigger(); $this->fail('Coding exception should have been thrown: ' . $errormsg); } catch (\coding_exception $e) { $this->assertStringContainsString($errormsg, $e->getMessage()); } $eventdata['relateduserid'] = 1; // No other['usercompetencyid']. $errormsg = 'The \'usercompetencyid\' data in \'other\' must be set.'; try { \core\event\competency_evidence_created::create($eventdata)->trigger(); $this->fail('Coding exception should have been thrown: ' . $errormsg); } catch (\coding_exception $e) { $this->assertStringContainsString($errormsg, $e->getMessage()); } $eventdata['other']['usercompetencyid'] = 1; // No other['competencyid']. $errormsg = 'The \'competencyid\' data in \'other\' must be set.'; try { \core\event\competency_evidence_created::create($eventdata)->trigger(); $this->fail('Coding exception should have been thrown: ' . $errormsg); } catch (\coding_exception $e) { $this->assertStringContainsString($errormsg, $e->getMessage()); } $eventdata['other']['competencyid'] = 1; // No other['action']. $errormsg = 'The \'action\' data in \'other\' must be set.'; try { \core\event\competency_evidence_created::create($eventdata)->trigger(); $this->fail('Coding exception should have been thrown: ' . $errormsg); } catch (\coding_exception $e) { $this->assertStringContainsString($errormsg, $e->getMessage()); } $eventdata['other']['action'] = 1; // No other['recommend']. $errormsg = 'The \'recommend\' data in \'other\' must be set.'; try { \core\event\competency_evidence_created::create($eventdata)->trigger(); $this->fail('Coding exception should have been thrown: ' . $errormsg); } catch (\coding_exception $e) { $this->assertStringContainsString($errormsg, $e->getMessage()); } $eventdata['other']['recommend'] = 1; // Event should be triggered without any problems. \core\event\competency_evidence_created::create($eventdata)->trigger(); } /** * Test the user competency grade rated event. * */ public function test_user_competency_rated(): void { $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $scale = $dg->create_scale(array('scale' => 'A,B,C,D')); $scaleconfig = array(array('scaleid' => $scale->id)); $scaleconfig[] = array('name' => 'A', 'id' => 1, 'scaledefault' => 0, 'proficient' => 0); $scaleconfig[] = array('name' => 'B', 'id' => 2, 'scaledefault' => 1, 'proficient' => 0); $scaleconfig[] = array('name' => 'C', 'id' => 3, 'scaledefault' => 0, 'proficient' => 1); $scaleconfig[] = array('name' => 'D', 'id' => 4, 'scaledefault' => 0, 'proficient' => 1); $fr = $lpg->create_framework(); $c = $lpg->create_competency(array( 'competencyframeworkid' => $fr->get('id'), 'scaleid' => $scale->id, 'scaleconfiguration' => $scaleconfig )); $user = $dg->create_user(); $uc = $lpg->create_user_competency(array( 'userid' => $user->id, 'competencyid' => $c->get('id'))); // Trigger and capture the event. $sink = $this->redirectEvents(); api::grade_competency($user->id, $c->get('id'), 2, true); // Get our event event. $events = $sink->get_events(); // Evidence created. $this->assertCount(2, $events); $evidencecreatedevent = $events[0]; $event = $events[1]; // Check that the event data is valid. $this->assertInstanceOf('\core\event\competency_evidence_created', $evidencecreatedevent); $this->assertInstanceOf('\core\event\competency_user_competency_rated', $event); $this->assertEquals($uc->get('id'), $event->objectid); $this->assertEquals($uc->get_context()->id, $event->contextid); $this->assertEquals($uc->get('userid'), $event->relateduserid); $this->assertEquals(2, $event->other['grade']); $this->assertEventContextNotUsed($event); $this->assertDebuggingNotCalled(); } /** * Test the user competency grade rated in course event. * */ public function test_user_competency_rated_in_course(): void { $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $scale = $dg->create_scale(array('scale' => 'A,B,C,D')); $course = $dg->create_course(); $user = $dg->create_user(); $studentarch = get_archetype_roles('student'); $studentrole = array_shift($studentarch); $scaleconfig = array(array('scaleid' => $scale->id)); $scaleconfig[] = array('name' => 'A', 'id' => 1, 'scaledefault' => 0, 'proficient' => 0); $scaleconfig[] = array('name' => 'B', 'id' => 2, 'scaledefault' => 1, 'proficient' => 0); $scaleconfig[] = array('name' => 'C', 'id' => 3, 'scaledefault' => 0, 'proficient' => 1); $scaleconfig[] = array('name' => 'D', 'id' => 4, 'scaledefault' => 0, 'proficient' => 1); $fr = $lpg->create_framework(); $c = $lpg->create_competency(array( 'competencyframeworkid' => $fr->get('id'), 'scaleid' => $scale->id, 'scaleconfiguration' => $scaleconfig )); // Enrol the user as students in course. $dg->enrol_user($user->id, $course->id, $studentrole->id); $lpg->create_course_competency(array( 'courseid' => $course->id, 'competencyid' => $c->get('id'))); $uc = $lpg->create_user_competency(array( 'userid' => $user->id, 'competencyid' => $c->get('id'))); // Trigger and capture the event. $sink = $this->redirectEvents(); api::grade_competency_in_course($course->id, $user->id, $c->get('id'), 2, true); // Get our event event. $events = $sink->get_events(); // Evidence created. $this->assertCount(2, $events); $evidencecreatedevent = $events[0]; $event = $events[1]; // Check that the event data is valid. $this->assertInstanceOf('\core\event\competency_evidence_created', $evidencecreatedevent); $this->assertInstanceOf('\core\event\competency_user_competency_rated_in_course', $event); $this->assertEquals(\context_course::instance($course->id)->id, $event->contextid); $this->assertEquals($course->id, $event->courseid); $this->assertEquals($uc->get('userid'), $event->relateduserid); $this->assertEquals($uc->get('competencyid'), $event->other['competencyid']); $this->assertEquals(2, $event->other['grade']); $this->assertEventContextNotUsed($event); $this->assertDebuggingNotCalled(); } /** * Test the user competency grade rated in plan event. * */ public function test_user_competency_rated_in_plan(): void { $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $scale = $dg->create_scale(array('scale' => 'A,B,C,D')); $user = $dg->create_user(); $scaleconfig = array(array('scaleid' => $scale->id)); $scaleconfig[] = array('name' => 'A', 'id' => 1, 'scaledefault' => 0, 'proficient' => 0); $scaleconfig[] = array('name' => 'B', 'id' => 2, 'scaledefault' => 1, 'proficient' => 0); $scaleconfig[] = array('name' => 'C', 'id' => 3, 'scaledefault' => 0, 'proficient' => 1); $scaleconfig[] = array('name' => 'D', 'id' => 4, 'scaledefault' => 0, 'proficient' => 1); $plan = $lpg->create_plan(array('userid' => $user->id)); $fr = $lpg->create_framework(); $c = $lpg->create_competency(array( 'competencyframeworkid' => $fr->get('id'), 'scaleid' => $scale->id, 'scaleconfiguration' => $scaleconfig )); $pc = $lpg->create_plan_competency(array('planid' => $plan->get('id'), 'competencyid' => $c->get('id'))); $uc = $lpg->create_user_competency(array( 'userid' => $user->id, 'competencyid' => $c->get('id'))); // Trigger and capture the event. $sink = $this->redirectEvents(); api::grade_competency_in_plan($plan->get('id'), $c->get('id'), 3, true); // Get our event event. $events = $sink->get_events(); // Evidence created. $this->assertCount(2, $events); $evidencecreatedevent = $events[0]; $event = $events[1]; // Check that the event data is valid. $this->assertInstanceOf('\core\event\competency_evidence_created', $evidencecreatedevent); $this->assertInstanceOf('\core\event\competency_user_competency_rated_in_plan', $event); $this->assertEquals($uc->get('id'), $event->objectid); $this->assertEquals($uc->get_context()->id, $event->contextid); $this->assertEquals($uc->get('userid'), $event->relateduserid); $this->assertEquals($uc->get('competencyid'), $event->other['competencyid']); $this->assertEquals(3, $event->other['grade']); $this->assertEventContextNotUsed($event); $this->assertDebuggingNotCalled(); } /** * Test user competency comment created event. */ public function test_user_competency_comment_created(): void { $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $user = $dg->create_user(); $this->setUser($user); $fr = $lpg->create_framework(); $c = $lpg->create_competency(array('competencyframeworkid' => $fr->get('id'))); $uc = $lpg->create_user_competency(array( 'userid' => $user->id, 'competencyid' => $c->get('id') )); $context = \context_user::instance($user->id); $cmt = new \stdClass(); $cmt->context = $context; $cmt->area = 'user_competency'; $cmt->itemid = $uc->get('id'); $cmt->component = 'competency'; $cmt->showcount = 1; $manager = new \comment($cmt); // Triggering and capturing the event. $sink = $this->redirectEvents(); $manager->add("New comment for user competency"); $events = $sink->get_events(); // Add comment will trigger 2 other events message_viewed and message_sent. $this->assertCount(1, $events); $event = reset($events); // Checking that the event contains the expected values. $this->assertInstanceOf('\core\event\competency_comment_created', $event); $this->assertEquals($context, $event->get_context()); $this->assertEquals($uc->get('id'), $event->other['itemid']); $this->assertEventContextNotUsed($event); $this->assertDebuggingNotCalled(); } /** * Test plan comment deleted event. */ public function test_user_competency_comment_deleted(): void { $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $user = $dg->create_user(); $fr = $lpg->create_framework(); $c = $lpg->create_competency(array('competencyframeworkid' => $fr->get('id'))); $uc = $lpg->create_user_competency(array( 'userid' => $user->id, 'competencyid' => $c->get('id') )); $context = \context_user::instance($user->id); $cmt = new \stdClass(); $cmt->context = $context; $cmt->area = 'user_competency'; $cmt->itemid = $uc->get('id'); $cmt->component = 'competency'; $manager = new \comment($cmt); $newcomment = $manager->add("Comment to be deleted"); // Triggering and capturing the event. $sink = $this->redirectEvents(); $manager->delete($newcomment->id); $events = $sink->get_events(); $this->assertCount(1, $events); $event = reset($events); // Checking that the event contains the expected values. $this->assertInstanceOf('\core\event\competency_comment_deleted', $event); $this->assertEquals($context, $event->get_context()); $this->assertEquals($uc->get('id'), $event->other['itemid']); $this->assertEventContextNotUsed($event); $this->assertDebuggingNotCalled(); } } tests/task/task_test.php000064400000033620152265256010011372 0ustar00. namespace core_competency\task; use core_competency\api; use core_competency\plan; use core_competency\template; /** * Task tests. * * @package core_competency * @copyright 2015 Issam Taboubi * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class task_test extends \advanced_testcase { public function test_sync_plans_from_cohorts_task(): void { global $DB; $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); // Sql to simulate the execution in time. $cmsql = "UPDATE {cohort_members} SET timeadded = :currenttime WHERE cohortid = :cohortid AND userid = :userid"; $tplsql = "UPDATE {" . template::TABLE . "} SET timemodified = :currenttime WHERE id = :templateid"; $plansql = "UPDATE {" . plan::TABLE . "} SET timemodified = :currenttime WHERE id = :planid"; $currenttime = time(); $user1 = $dg->create_user(); $user2 = $dg->create_user(); $user3 = $dg->create_user(); $user4 = $dg->create_user(); $user5 = $dg->create_user(); $cohort = $dg->create_cohort(); $tpl = $lpg->create_template(); // Add 2 users to the cohort. cohort_add_member($cohort->id, $user1->id); cohort_add_member($cohort->id, $user2->id); // Creating plans from template cohort. $templatecohort = api::create_template_cohort($tpl->get('id'), $cohort->id); $created = api::create_plans_from_template_cohort($tpl->get('id'), $cohort->id); $this->assertEquals(2, $created); $task = \core\task\manager::get_scheduled_task('\\core\\task\\sync_plans_from_template_cohorts_task'); $this->assertInstanceOf('\core\task\sync_plans_from_template_cohorts_task', $task); // Add two more users to the cohort. cohort_add_member($cohort->id, $user3->id); cohort_add_member($cohort->id, $user4->id); $currenttime = $currenttime + 1; $task->execute(); $task->set_last_run_time($currenttime); $this->assertEquals(4, plan::count_records(array('templateid' => $tpl->get('id')))); // Test if remove user from cohort will affect plans. cohort_remove_member($cohort->id, $user3->id); cohort_remove_member($cohort->id, $user4->id); $currenttime = $currenttime + 1; $task->execute(); $task->set_last_run_time($currenttime); $this->assertEquals(4, plan::count_records(array('templateid' => $tpl->get('id')))); // The template is now hidden, and I've added a user with a missing plan. Nothing should happen. $currenttime = $currenttime + 1; $tpl->set('visible', false); $tpl->update(); $DB->execute($tplsql, array('currenttime' => $currenttime, 'templateid' => $tpl->get('id'))); $currenttime = $currenttime + 1; cohort_add_member($cohort->id, $user5->id); $DB->execute($cmsql, array('currenttime' => $currenttime, 'cohortid' => $cohort->id, 'userid' => $user5->id)); $this->assertFalse(plan::record_exists_select('userid = ? AND templateid = ?', array($user5->id, $tpl->get('id')))); $this->assertEquals(4, plan::count_records(array('templateid' => $tpl->get('id')))); $currenttime = $currenttime + 1; $task->execute(); $task->set_last_run_time($currenttime); $this->assertFalse(plan::record_exists_select('userid = ? AND templateid = ?', array($user5->id, $tpl->get('id')))); $this->assertEquals(4, plan::count_records(array('templateid' => $tpl->get('id')))); // Now I set the template as visible again, the plan is created. $currenttime = $currenttime + 1; $tpl->set('visible', true); $tpl->update(); $DB->execute($tplsql, array('currenttime' => $currenttime, 'templateid' => $tpl->get('id'))); $currenttime = $currenttime + 1; $task->execute(); $task->set_last_run_time($currenttime); $this->assertTrue(plan::record_exists_select('userid = ? AND templateid = ?', array($user5->id, $tpl->get('id')))); $this->assertEquals(5, plan::count_records(array('templateid' => $tpl->get('id')))); // Let's unlink the plan and run the task again, it should not be recreated. $currenttime = $currenttime + 1; $plan = plan::get_record(array('userid' => $user5->id, 'templateid' => $tpl->get('id'))); api::unlink_plan_from_template($plan); $DB->execute($plansql, array('currenttime' => $currenttime, 'planid' => $plan->get('id'))); $this->assertTrue(plan::record_exists_select('userid = ?', array($user5->id))); $this->assertFalse(plan::record_exists_select('userid = ? AND templateid = ?', array($user5->id, $tpl->get('id')))); $this->assertEquals(4, plan::count_records(array('templateid' => $tpl->get('id')))); $currenttime = $currenttime + 1; $task->execute(); $task->set_last_run_time($currenttime); $this->assertTrue(plan::record_exists_select('userid = ?', array($user5->id))); $this->assertFalse(plan::record_exists_select('userid = ? AND templateid = ?', array($user5->id, $tpl->get('id')))); $this->assertEquals(4, plan::count_records(array('templateid' => $tpl->get('id')))); // Adding users to cohort that already exist in plans. $currenttime = $currenttime + 1; cohort_add_member($cohort->id, $user3->id); cohort_add_member($cohort->id, $user4->id); $DB->execute($cmsql, array('currenttime' => $currenttime, 'cohortid' => $cohort->id, 'userid' => $user3->id)); $DB->execute($cmsql, array('currenttime' => $currenttime, 'cohortid' => $cohort->id, 'userid' => $user3->id)); $currenttime = $currenttime + 1; $task->execute(); $task->set_last_run_time($currenttime); $this->assertEquals(4, plan::count_records(array('templateid' => $tpl->get('id')))); // Test a user plan deleted will not be recreated. $currenttime = $currenttime + 1; $plan = plan::get_record(array('userid' => $user4->id, 'templateid' => $tpl->get('id'))); api::delete_plan($plan->get('id')); $currenttime = $currenttime + 1; $task->execute(); $task->set_last_run_time($currenttime); $this->assertEquals(3, plan::count_records(array('templateid' => $tpl->get('id')))); } public function test_sync_plans_from_cohorts_with_templateduedate_task(): void { $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $user1 = $dg->create_user(); $user2 = $dg->create_user(); $user3 = $dg->create_user(); $user4 = $dg->create_user(); $user5 = $dg->create_user(); $cohort = $dg->create_cohort(); $tpl = $lpg->create_template(array('duedate' => time() + 400)); // Add 2 users to the cohort. cohort_add_member($cohort->id, $user1->id); cohort_add_member($cohort->id, $user2->id); // Creating plans from template cohort. $templatecohort = api::create_template_cohort($tpl->get('id'), $cohort->id); $created = api::create_plans_from_template_cohort($tpl->get('id'), $cohort->id); $this->assertEquals(2, $created); $task = \core\task\manager::get_scheduled_task('\\core\\task\\sync_plans_from_template_cohorts_task'); $this->assertInstanceOf('\core\task\sync_plans_from_template_cohorts_task', $task); // Add two more users to the cohort. cohort_add_member($cohort->id, $user3->id); cohort_add_member($cohort->id, $user4->id); $task->execute(); $this->assertEquals(4, plan::count_records(array('templateid' => $tpl->get('id')))); // Test if remove user from cohort will affect plans. cohort_remove_member($cohort->id, $user3->id); cohort_remove_member($cohort->id, $user4->id); $task->execute(); $this->assertEquals(4, plan::count_records(array('templateid' => $tpl->get('id')))); // The template is now hidden, and I've added a user with a missing plan. Nothing should happen. $tpl->set('visible', false); $tpl->update(); cohort_add_member($cohort->id, $user5->id); $this->assertFalse(plan::record_exists_select('userid = ? AND templateid = ?', array($user5->id, $tpl->get('id')))); $this->assertEquals(4, plan::count_records(array('templateid' => $tpl->get('id')))); $task->execute(); $this->assertFalse(plan::record_exists_select('userid = ? AND templateid = ?', array($user5->id, $tpl->get('id')))); $this->assertEquals(4, plan::count_records(array('templateid' => $tpl->get('id')))); // Now I set the template as visible again, the plan is created. $tpl->set('visible', true); $tpl->update(); $task->execute(); $this->assertTrue(plan::record_exists_select('userid = ? AND templateid = ?', array($user5->id, $tpl->get('id')))); $this->assertEquals(5, plan::count_records(array('templateid' => $tpl->get('id')))); // Let's unlink the plan and run the task again, it should not be recreated. $plan = plan::get_record(array('userid' => $user5->id, 'templateid' => $tpl->get('id'))); api::unlink_plan_from_template($plan); $this->assertTrue(plan::record_exists_select('userid = ?', array($user5->id))); $this->assertFalse(plan::record_exists_select('userid = ? AND templateid = ?', array($user5->id, $tpl->get('id')))); $this->assertEquals(4, plan::count_records(array('templateid' => $tpl->get('id')))); $task->execute(); $this->assertTrue(plan::record_exists_select('userid = ?', array($user5->id))); $this->assertFalse(plan::record_exists_select('userid = ? AND templateid = ?', array($user5->id, $tpl->get('id')))); $this->assertEquals(4, plan::count_records(array('templateid' => $tpl->get('id')))); // Adding users to cohort that already exist in plans. cohort_add_member($cohort->id, $user3->id); cohort_add_member($cohort->id, $user4->id); $task->execute(); $this->assertEquals(4, plan::count_records(array('templateid' => $tpl->get('id')))); } public function test_sync_plans_from_cohorts_with_passed_duedate(): void { global $DB; $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $user1 = $dg->create_user(); $user2 = $dg->create_user(); $cohort = $dg->create_cohort(); $tpl = $lpg->create_template(array('duedate' => time() + 1000)); $templatecohort = api::create_template_cohort($tpl->get('id'), $cohort->id); $task = \core\task\manager::get_scheduled_task('\\core\\task\\sync_plans_from_template_cohorts_task'); // Add 1 user to the cohort. cohort_add_member($cohort->id, $user1->id); // Creating plans from template cohort. $task->execute(); $this->assertEquals(1, plan::count_records()); // Now add another user, but this time the template will be expired. cohort_add_member($cohort->id, $user2->id); $record = $tpl->to_record(); $record->duedate = time() - 10000; $DB->update_record(template::TABLE, $record); $tpl->read(); $task->execute(); $this->assertEquals(1, plan::count_records()); // Still only one plan. // Pretend it wasn't expired. $tpl->set('duedate', time() + 100); $tpl->update(); $task->execute(); $this->assertEquals(2, plan::count_records()); // Now there is two. } public function test_complete_plans_task(): void { global $DB; $this->resetAfterTest(true); $this->setAdminUser(); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $user = $dg->create_user(); $up1 = $lpg->create_plan(array('userid' => $user->id, 'status' => plan::STATUS_DRAFT)); $up2 = $lpg->create_plan(array('userid' => $user->id, 'status' => plan::STATUS_ACTIVE)); // Set duedate in the past. $date = new \DateTime('yesterday'); $record1 = $up1->to_record(); $record2 = $up2->to_record(); $record1->duedate = $date->getTimestamp(); $record2->duedate = $date->getTimestamp(); $DB->update_record(plan::TABLE, $record1); $DB->update_record(plan::TABLE, $record2); $task = \core\task\manager::get_scheduled_task('\\core\\task\\complete_plans_task'); $this->assertInstanceOf('\\core\\task\\complete_plans_task', $task); // Test that draft plan can not be completed on running task. $task->execute(); $plandraft = api::read_plan($up1->get('id')); $this->assertEquals(plan::STATUS_DRAFT, $plandraft->get('status')); // Test that active plan can be completed on running task. $task->execute(); $planactive = api::read_plan($up2->get('id')); $this->assertEquals(plan::STATUS_COMPLETE, $planactive->get('status')); } } tests/competency_test.php000064400000006116152265256010011634 0ustar00. namespace core_competency; /** * Competency testcase. * * @package core_competency * @copyright 2016 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class competency_test extends \advanced_testcase { public function test_get_framework_depth(): void { $this->resetAfterTest(); $ccg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $f1 = $ccg->create_framework(); $f2 = $ccg->create_framework(); $f3 = $ccg->create_framework(); $f4 = $ccg->create_framework(); $f1c1 = $ccg->create_competency(['competencyframeworkid' => $f1->get('id')]); $f1c11 = $ccg->create_competency(['competencyframeworkid' => $f1->get('id'), 'parentid' => $f1c1->get('id')]); $f1c111 = $ccg->create_competency(['competencyframeworkid' => $f1->get('id'), 'parentid' => $f1c11->get('id')]); $f1c1111 = $ccg->create_competency(['competencyframeworkid' => $f1->get('id'), 'parentid' => $f1c111->get('id')]); $f2c1 = $ccg->create_competency(['competencyframeworkid' => $f2->get('id')]); $f2c2 = $ccg->create_competency(['competencyframeworkid' => $f2->get('id')]); $f2c21 = $ccg->create_competency(['competencyframeworkid' => $f2->get('id'), 'parentid' => $f2c2->get('id')]); $f2c22 = $ccg->create_competency(['competencyframeworkid' => $f2->get('id'), 'parentid' => $f2c2->get('id')]); $f2c211 = $ccg->create_competency(['competencyframeworkid' => $f2->get('id'), 'parentid' => $f2c21->get('id')]); $f2c221 = $ccg->create_competency(['competencyframeworkid' => $f2->get('id'), 'parentid' => $f2c22->get('id')]); $f2c222 = $ccg->create_competency(['competencyframeworkid' => $f2->get('id'), 'parentid' => $f2c22->get('id')]); $f2c223 = $ccg->create_competency(['competencyframeworkid' => $f2->get('id'), 'parentid' => $f2c22->get('id')]); $f2c3 = $ccg->create_competency(['competencyframeworkid' => $f2->get('id')]); $f3c1 = $ccg->create_competency(['competencyframeworkid' => $f3->get('id')]); $this->assertEquals(4, competency::get_framework_depth($f1->get('id'))); $this->assertEquals(3, competency::get_framework_depth($f2->get('id'))); $this->assertEquals(1, competency::get_framework_depth($f3->get('id'))); $this->assertEquals(0, competency::get_framework_depth($f4->get('id'))); } } tests/course_competency_test.php000064400000014200152265256010013205 0ustar00. namespace core_competency; /** * Course competency persistent testcase. * * @package core_competency * @copyright 2016 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class course_competency_test extends \advanced_testcase { public function test_get_courses_with_competency_and_user(): void { global $CFG, $DB; $this->resetAfterTest(true); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $c1 = $dg->create_course(); $c2 = $dg->create_course(); $c3 = $dg->create_course(); $c4 = $dg->create_course(); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $u3 = $dg->create_user(); $u4 = $dg->create_user(); $flatfileplugin = enrol_get_plugin('flatfile'); $flatfileinstanceid = $flatfileplugin->add_instance($c2); $framework = $lpg->create_framework(); $comp1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); // In C1, and C2. $comp2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); // In C2. $comp3 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); // In None. $comp4 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); // In C4. $lpg->create_course_competency(array('competencyid' => $comp1->get('id'), 'courseid' => $c1->id)); $lpg->create_course_competency(array('competencyid' => $comp1->get('id'), 'courseid' => $c2->id)); $lpg->create_course_competency(array('competencyid' => $comp2->get('id'), 'courseid' => $c2->id)); $lpg->create_course_competency(array('competencyid' => $comp4->get('id'), 'courseid' => $c4->id)); // Enrol the user 1 in C1, C2, and C3. $dg->enrol_user($u1->id, $c1->id); $dg->enrol_user($u1->id, $c2->id); $dg->enrol_user($u1->id, $c3->id); // Enrol the user 2 in C4. $dg->enrol_user($u2->id, $c4->id); // Enrol the user 3 in C1 and C2, but non active in C2. $dg->enrol_user($u3->id, $c1->id); $dg->enrol_user($u3->id, $c2->id, null, 'manual', 0, 0, ENROL_USER_SUSPENDED); // Enrol the user 4 with a plugin which will be enabled/disabled. $dg->enrol_user($u4->id, $c2->id, null, 'flatfile'); // Using the competency that is not used anywhere -> no courses. $this->assertCount(0, course_competency::get_courses_with_competency_and_user($comp3->get('id'), $u1->id)); // Using the competency that is used in a course where the user is not enrolled -> no courses. $this->assertCount(0, course_competency::get_courses_with_competency_and_user($comp4->get('id'), $u1->id)); // Using the competency that is used in a course where the user is enrolled -> one course. $courses = course_competency::get_courses_with_competency_and_user($comp2->get('id'), $u1->id); $this->assertCount(1, $courses); $this->assertArrayHasKey($c2->id, $courses); // Using the competency used multiple times. $courses = course_competency::get_courses_with_competency_and_user($comp1->get('id'), $u1->id); $this->assertCount(2, $courses); $this->assertArrayHasKey($c1->id, $courses); $this->assertArrayHasKey($c2->id, $courses); // Checking for another user where the competency is used twice, but not for them. $courses = course_competency::get_courses_with_competency_and_user($comp1->get('id'), $u2->id); $this->assertCount(0, $courses); // Checking for another user where the competency is used in their course. $courses = course_competency::get_courses_with_competency_and_user($comp4->get('id'), $u2->id); $this->assertCount(1, $courses); $this->assertArrayHasKey($c4->id, $courses); // Checking for a user who is suspended in a course. $courses = course_competency::get_courses_with_competency_and_user($comp1->get('id'), $u3->id); $this->assertCount(1, $courses); $this->assertArrayHasKey($c1->id, $courses); // Check for the user with plugin enabled. $enrolplugins = explode(',', $CFG->enrol_plugins_enabled); $enrolplugins[] = 'flatfile'; $CFG->enrol_plugins_enabled = implode(',', array_unique($enrolplugins)); $courses = course_competency::get_courses_with_competency_and_user($comp1->get('id'), $u4->id); $this->assertCount(1, $courses); $this->assertArrayHasKey($c2->id, $courses); // Check for the user with plugin enabled, but enrolment instance disabled. $flatfileinstance = $DB->get_record('enrol', array('id' => $flatfileinstanceid)); $flatfileplugin->update_status($flatfileinstance, ENROL_INSTANCE_DISABLED); $courses = course_competency::get_courses_with_competency_and_user($comp1->get('id'), $u4->id); $this->assertCount(0, $courses); $flatfileplugin->update_status($flatfileinstance, ENROL_INSTANCE_ENABLED); // Check for the user with plugin disabled. $enrolplugins = array_flip(explode(',', $CFG->enrol_plugins_enabled)); unset($enrolplugins['flatfile']); $CFG->enrol_plugins_enabled = implode(',', array_keys($enrolplugins)); $courses = course_competency::get_courses_with_competency_and_user($comp1->get('id'), $u4->id); $this->assertCount(0, $courses); } } tests/generator_test.php000064400000032170152265256010011453 0ustar00. namespace core_competency; /** * Tool LP data generator testcase. * * @package core_competency * @category test * @copyright 2015 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class generator_test extends \advanced_testcase { public function test_create_framework(): void { $this->resetAfterTest(true); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $this->assertEquals(0, competency_framework::count_records()); $framework = $lpg->create_framework(); $framework = $lpg->create_framework(); $this->assertEquals(2, competency_framework::count_records()); $this->assertInstanceOf('\core_competency\competency_framework', $framework); } public function test_create_competency(): void { $this->resetAfterTest(true); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $framework = $lpg->create_framework(); $this->assertEquals(0, competency::count_records()); $competency = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $competency = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $this->assertEquals(2, competency::count_records()); $this->assertInstanceOf('\core_competency\competency', $competency); } public function test_create_related_competency(): void { $this->resetAfterTest(true); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $framework = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c3 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $this->assertEquals(0, related_competency::count_records()); $rc = $lpg->create_related_competency(array('competencyid' => $c1->get('id'), 'relatedcompetencyid' => $c2->get('id'))); $rc = $lpg->create_related_competency(array('competencyid' => $c2->get('id'), 'relatedcompetencyid' => $c3->get('id'))); $this->assertEquals(2, related_competency::count_records()); $this->assertInstanceOf('\core_competency\related_competency', $rc); } public function test_create_plan(): void { $this->resetAfterTest(true); $user = $this->getDataGenerator()->create_user(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $this->assertEquals(0, plan::count_records()); $plan = $lpg->create_plan(array('userid' => $user->id)); $this->assertEquals(1, plan::count_records()); $this->assertInstanceOf('\core_competency\plan', $plan); } public function test_create_user_competency(): void { $this->resetAfterTest(true); $user = $this->getDataGenerator()->create_user(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $framework = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c3 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $this->assertEquals(0, user_competency::count_records()); $rc = $lpg->create_user_competency(array('userid' => $user->id, 'competencyid' => $c1->get('id'))); $rc = $lpg->create_user_competency(array('userid' => $user->id, 'competencyid' => $c2->get('id'))); $this->assertEquals(2, user_competency::count_records()); $this->assertInstanceOf('\core_competency\user_competency', $rc); } public function test_create_user_competency_plan(): void { $this->resetAfterTest(true); $user = $this->getDataGenerator()->create_user(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $framework = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $plan = $lpg->create_plan(array('userid' => $user->id)); $this->assertEquals(0, user_competency_plan::count_records()); $ucp = $lpg->create_user_competency_plan(array( 'userid' => $user->id, 'competencyid' => $c1->get('id'), 'planid' => $plan->get('id') )); $ucp = $lpg->create_user_competency_plan(array( 'userid' => $user->id, 'competencyid' => $c2->get('id'), 'planid' => $plan->get('id') )); $this->assertEquals(2, user_competency_plan::count_records()); $this->assertInstanceOf('\core_competency\user_competency_plan', $ucp); } public function test_create_template(): void { $this->resetAfterTest(true); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $this->assertEquals(0, template::count_records()); $template = $lpg->create_template(); $template = $lpg->create_template(); $this->assertEquals(2, template::count_records()); $this->assertInstanceOf('\core_competency\template', $template); } public function test_create_template_competency(): void { $this->resetAfterTest(true); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $this->assertEquals(0, template_competency::count_records()); $framework = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $template = $lpg->create_template(); $relation = $lpg->create_template_competency(array('competencyid' => $c1->get('id'), 'templateid' => $template->get('id'))); $relation = $lpg->create_template_competency(array('competencyid' => $c2->get('id'), 'templateid' => $template->get('id'))); $this->assertEquals(2, template_competency::count_records()); $this->assertInstanceOf('\core_competency\template_competency', $relation); } public function test_create_plan_competency(): void { $this->resetAfterTest(true); $user = $this->getDataGenerator()->create_user(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $framework = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $plan = $lpg->create_plan(array('userid' => $user->id)); $pc1 = $lpg->create_plan_competency(array('planid' => $plan->get('id'), 'competencyid' => $c1->get('id'))); $pc2 = $lpg->create_plan_competency(array('planid' => $plan->get('id'), 'competencyid' => $c2->get('id'))); $this->assertEquals(2, plan_competency::count_records()); $this->assertInstanceOf('\core_competency\plan_competency', $pc1); $this->assertInstanceOf('\core_competency\plan_competency', $pc2); $this->assertEquals($plan->get('id'), $pc1->get('planid')); } public function test_create_template_cohort(): void { $this->resetAfterTest(true); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $c1 = $this->getDataGenerator()->create_cohort(); $c2 = $this->getDataGenerator()->create_cohort(); $t1 = $lpg->create_template(); $this->assertEquals(0, template_cohort::count_records()); $tc = $lpg->create_template_cohort(array('templateid' => $t1->get('id'), 'cohortid' => $c1->id)); $this->assertEquals(1, template_cohort::count_records()); $tc = $lpg->create_template_cohort(array('templateid' => $t1->get('id'), 'cohortid' => $c2->id)); $this->assertEquals(2, template_cohort::count_records()); $this->assertInstanceOf('\core_competency\template_cohort', $tc); } public function test_create_evidence(): void { $this->resetAfterTest(true); $user = $this->getDataGenerator()->create_user(); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $framework = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $rc1 = $lpg->create_user_competency(array('userid' => $user->id, 'competencyid' => $c1->get('id'))); $rc2 = $lpg->create_user_competency(array('userid' => $user->id, 'competencyid' => $c2->get('id'))); $e = $lpg->create_evidence(array('usercompetencyid' => $rc1->get('id'))); $e = $lpg->create_evidence(array('usercompetencyid' => $rc2->get('id'))); $this->assertEquals(2, evidence::count_records()); $this->assertInstanceOf('\core_competency\evidence', $e); } public function test_create_course_competency(): void { $this->resetAfterTest(true); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $course1 = $this->getDataGenerator()->create_course(); $course2 = $this->getDataGenerator()->create_course(); $framework = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c3 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $this->assertEquals(0, course_competency::count_records()); $rc = $lpg->create_course_competency(array('competencyid' => $c1->get('id'), 'courseid' => $course1->id)); $rc = $lpg->create_course_competency(array('competencyid' => $c2->get('id'), 'courseid' => $course1->id)); $this->assertEquals(2, course_competency::count_records(array('courseid' => $course1->id))); $this->assertEquals(0, course_competency::count_records(array('courseid' => $course2->id))); $rc = $lpg->create_course_competency(array('competencyid' => $c3->get('id'), 'courseid' => $course2->id)); $this->assertEquals(1, course_competency::count_records(array('courseid' => $course2->id))); $this->assertInstanceOf('\core_competency\course_competency', $rc); } public function test_create_course_module_competency(): void { $this->resetAfterTest(true); $lpg = $this->getDataGenerator()->get_plugin_generator('core_competency'); $course1 = $this->getDataGenerator()->create_course(); $cm1 = $this->getDataGenerator()->create_module('forum', array('course' => $course1->id)); $cm2 = $this->getDataGenerator()->create_module('forum', array('course' => $course1->id)); $framework = $lpg->create_framework(); $c1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $c3 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $this->assertEquals(0, course_module_competency::count_records()); $rc = $lpg->create_course_module_competency(array('competencyid' => $c1->get('id'), 'cmid' => $cm1->cmid)); $rc = $lpg->create_course_module_competency(array('competencyid' => $c2->get('id'), 'cmid' => $cm1->cmid)); $this->assertEquals(2, course_module_competency::count_records(array('cmid' => $cm1->cmid))); $this->assertEquals(0, course_module_competency::count_records(array('cmid' => $cm2->cmid))); $rc = $lpg->create_course_module_competency(array('competencyid' => $c3->get('id'), 'cmid' => $cm2->cmid)); $this->assertEquals(1, course_module_competency::count_records(array('cmid' => $cm2->cmid))); $this->assertInstanceOf('\core_competency\course_module_competency', $rc); } } tests/course_competency_settings_test.php000064400000012606152265256010015135 0ustar00. namespace core_competency; /** * This test ensures that the course competency settings are applied and work correctly. * * @package core_competency * @copyright 2016 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class course_competency_settings_test extends \advanced_testcase { public function test_who_can_change_settings(): void { global $CFG, $DB; $this->resetAfterTest(true); $syscontext = \context_system::instance(); $dg = $this->getDataGenerator(); $lpg = $dg->get_plugin_generator('core_competency'); $role = create_role('Settings changer role', 'settingschanger', 'Someone who can change course competency settings'); assign_capability('moodle/competency:coursecompetencyconfigure', CAP_ALLOW, $role, $syscontext->id); assign_capability('moodle/competency:competencygrade', CAP_ALLOW, $role, $syscontext->id); assign_capability('moodle/competency:coursecompetencyview', CAP_ALLOW, $role, $syscontext->id); assign_capability('moodle/competency:planview', CAP_ALLOW, $role, $syscontext->id); $gradedrole = create_role('Graded role', 'graded', 'Someone who can be graded'); assign_capability('moodle/competency:coursecompetencygradable', CAP_ALLOW, $gradedrole, $syscontext->id); $c1 = $dg->create_course(); $u1 = $dg->create_user(); $u2 = $dg->create_user(); $u3 = $dg->create_user(); $framework = $lpg->create_framework(); $comp1 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $comp2 = $lpg->create_competency(array('competencyframeworkid' => $framework->get('id'))); $lpg->create_course_competency(array('competencyid' => $comp1->get('id'), 'courseid' => $c1->id)); $lpg->create_course_competency(array('competencyid' => $comp2->get('id'), 'courseid' => $c1->id)); // Enrol the user. $dg->enrol_user($u1->id, $c1->id); role_assign($gradedrole, $u1->id, $syscontext->id); // Assign roles. role_assign($role, $u2->id, $syscontext->id); $this->setUser($u2); set_config('pushcourseratingstouserplans', true, 'core_competency'); $coursesettings = course_competency_settings::get_by_courseid($c1->id); $this->assertTrue((boolean)$coursesettings->get('pushratingstouserplans')); set_config('pushcourseratingstouserplans', false, 'core_competency'); $coursesettings = course_competency_settings::get_by_courseid($c1->id); $this->assertFalse((boolean)$coursesettings->get('pushratingstouserplans')); api::update_course_competency_settings($c1->id, (object) array('pushratingstouserplans' => true)); $coursesettings = course_competency_settings::get_by_courseid($c1->id); $this->assertTrue((boolean)$coursesettings->get('pushratingstouserplans')); set_config('pushcourseratingstouserplans', true, 'core_competency'); api::update_course_competency_settings($c1->id, (object) array('pushratingstouserplans' => false)); $coursesettings = course_competency_settings::get_by_courseid($c1->id); $this->assertFalse((boolean)$coursesettings->get('pushratingstouserplans')); // Right now the setting is false. api::grade_competency_in_course($c1->id, $u1->id, $comp1->get('id'), 1, 'Note'); $filterparams = array( 'userid' => $u1->id, 'competencyid' => $comp1->get('id'), ); $usercompcourse = \core_competency\user_competency_course::get_record($filterparams); $usercomp = \core_competency\user_competency::get_record($filterparams); // No grade in plan - only a grade in the course. $this->assertEmpty($usercomp->get('grade')); $this->assertEquals(1, $usercompcourse->get('grade')); api::update_course_competency_settings($c1->id, (object) array('pushratingstouserplans' => true)); api::grade_competency_in_course($c1->id, $u1->id, $comp1->get('id'), 2, 'Note 2'); $filterparams = array( 'userid' => $u1->id, 'competencyid' => $comp1->get('id'), ); $usercompcourse = \core_competency\user_competency_course::get_record($filterparams); $usercomp = \core_competency\user_competency::get_record($filterparams); // Updated grade in plan - updated grade in the course. $this->assertEquals(2, $usercomp->get('grade')); $this->assertEquals(2, $usercompcourse->get('grade')); $this->setUser($u3); $this->expectException('required_capability_exception'); api::update_course_competency_settings($c1->id, (object) array('pushratingstouserplans' => false)); } } db/services.php000064400000002561152266351270007502 0ustar00. /** * Competency report webservice functions * * * @package report_competency * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $functions = array( // Learning plan related functions. 'report_competency_data_for_report' => array( 'classname' => 'report_competency\external', 'methodname' => 'data_for_report', 'classpath' => '', 'description' => 'Load the data for the competency report in a course.', 'type' => 'read', 'capabilities' => 'moodle/competency:coursecompetencyview', 'ajax' => true, ) ); lang/en/report_competency.php000064400000002607152266351270012357 0ustar00. /** * Strings for component 'report_competency', language 'en' * * @package report_competency * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ $string['competency'] = 'Competency'; $string['coursecompetencybreakdownsummary'] = 'A report of all the students in the course, and their progress towards the course competencies'; $string['notrated'] = 'Not rated'; $string['pluginname'] = 'Competency breakdown'; $string['rating'] = 'Rating'; $string['filtermodule'] = 'Competencies linked to "{$a}"'; $string['usercompetencysummary'] = 'User competency summary'; $string['privacy:metadata'] = 'The Competency breakdown plugin does not store any personal data.'; version.php000064400000002263152266351270006756 0ustar00. /** * Plugin version info * * @package report_competency * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ defined('MOODLE_INTERNAL') || die(); $plugin->version = 2025041400; // The current plugin version (Date: YYYYMMDDXX). $plugin->requires = 2025040800; // Requires this Moodle version. $plugin->component = 'report_competency'; // Full name of the plugin (used for diagnostics). $plugin->dependencies = [ 'tool_lp' => ANY_VERSION, ]; classes/output/renderer.php000064400000005676152266351270012107 0ustar00. /** * Renderer class for report_competency * * @package report_competency * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace report_competency\output; defined('MOODLE_INTERNAL') || die; use plugin_renderer_base; use renderable; /** * Renderer class for competency breakdown report * * @package report_competency * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class renderer extends plugin_renderer_base { /** * Defer to template. * * @param report $page * @return string html for the page */ public function render_report(report $page) { $data = $page->export_for_template($this); return parent::render_from_template('report_competency/report', $data); } /** * Defer to template. * * @param user_course_navigation $nav * @return string */ public function render_user_course_navigation(user_course_navigation $nav) { $data = $nav->export_for_template($this); return parent::render_from_template('report_competency/user_course_navigation', $data); } /** * Output a nofication. * * @param string $message the message to print out * @return string HTML fragment. * @see \core\output\notification */ public function notify_message($message) { $n = new \core\output\notification($message, \core\output\notification::NOTIFY_INFO); return $this->render($n); } /** * Output an error notification. * * @param string $message the message to print out * @return string HTML fragment. * @see \core\output\notification */ public function notify_problem($message) { $n = new \core\output\notification($message, \core\output\notification::NOTIFY_ERROR); return $this->render($n); } /** * Output a success notification. * * @param string $message the message to print out * @return string HTML fragment. * @see \core\output\notification */ public function notify_success($message) { $n = new \core\output\notification($message, \core\output\notification::NOTIFY_SUCCESS); return $this->render($n); } } classes/output/report.php000064400000013317152266351270011603 0ustar00. /** * Class containing data for learning plan template competencies page * * @package report_competency * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace report_competency\output; use context_course; use renderable; use core_user; use templatable; use renderer_base; use moodle_url; use stdClass; use core_competency\api; use core_competency\external\user_competency_course_exporter; use core_user\external\user_summary_exporter; use core_competency\external\performance_helper; use core_competency\url; use core_competency\user_competency; use tool_lp\external\competency_summary_exporter; use core_course\external\course_summary_exporter; /** * Class containing data for learning plan template competencies page * * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class report implements renderable, templatable { /** @var context $context */ protected $context; /** @var int $courseid */ protected $courseid; /** @var int $moduleid */ protected $moduleid; /** @var array $competencies */ protected $competencies; /** @var int The user id */ protected $userid; /** * Construct this renderable. * * @param int $courseid The course id * @param int $userid The user id * @param int $moduleid The module id */ public function __construct($courseid, $userid, $moduleid) { $this->courseid = $courseid; $this->userid = $userid; $this->moduleid = $moduleid; $this->context = context_course::instance($courseid); } /** * Export this data so it can be used as the context for a mustache template. * * @param \renderer_base $output * @return stdClass */ public function export_for_template(renderer_base $output) { global $DB; $data = new stdClass(); $data->courseid = $this->courseid; $data->moduleid = $this->moduleid; if (empty($data->moduleid)) { $data->moduleid = 0; } $course = $DB->get_record('course', array('id' => $this->courseid)); $coursecontext = context_course::instance($course->id); $exporter = new course_summary_exporter($course, array('context' => $coursecontext)); $coursecompetencysettings = api::read_course_competency_settings($course->id); $data->pushratingstouserplans = $coursecompetencysettings->get('pushratingstouserplans'); $data->course = $exporter->export($output); $data->usercompetencies = array(); $user = core_user::get_user($this->userid); $exporter = new user_summary_exporter($user); $data->user = $exporter->export($output); $data->usercompetencies = array(); $coursecompetencies = api::list_course_competencies($this->courseid); $usercompetencycourses = api::list_user_competencies_in_course($this->courseid, $user->id); if ($this->moduleid > 0) { $modulecompetencies = api::list_course_module_competencies_in_course_module($this->moduleid); foreach ($usercompetencycourses as $ucid => $usercompetency) { $found = false; foreach ($modulecompetencies as $mcid => $modulecompetency) { if ($modulecompetency->get('competencyid') == $usercompetency->get('competencyid')) { $found = true; break; } } if (!$found) { // We need to filter out this competency. unset($usercompetencycourses[$ucid]); } } } $helper = new performance_helper(); foreach ($usercompetencycourses as $usercompetencycourse) { $onerow = new stdClass(); $competency = null; foreach ($coursecompetencies as $coursecompetency) { if ($coursecompetency['competency']->get('id') == $usercompetencycourse->get('competencyid')) { $competency = $coursecompetency['competency']; break; } } if (!$competency) { continue; } $framework = $helper->get_framework_from_competency($competency); $scale = $helper->get_scale_from_competency($competency); $exporter = new user_competency_course_exporter($usercompetencycourse, array('scale' => $scale)); $record = $exporter->export($output); $onerow->usercompetencycourse = $record; $exporter = new competency_summary_exporter(null, array( 'competency' => $competency, 'framework' => $framework, 'context' => $framework->get_context(), 'relatedcompetencies' => array(), 'linkedcourses' => array() )); $onerow->competency = $exporter->export($output); array_push($data->usercompetencies, $onerow); } return $data; } } classes/output/user_course_navigation.php000064400000011610152266351270015037 0ustar00. /** * User navigation class. * * @package report_competency * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace report_competency\output; use renderable; use renderer_base; use templatable; use context_course; use core_user\external\user_summary_exporter; use core_course\external\course_module_summary_exporter; use stdClass; /** * User course navigation class. * * @package report_competency * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class user_course_navigation implements renderable, templatable { /** @var userid */ protected $userid; /** @var courseid */ protected $courseid; /** @var moduleid */ protected $moduleid; /** @var baseurl */ protected $baseurl; /** * Construct. * * @param int $userid * @param int $courseid * @param int $moduleid * @param string $baseurl */ public function __construct($userid, $courseid, $baseurl, $moduleid) { $this->userid = $userid; $this->courseid = $courseid; $this->moduleid = $moduleid; $this->baseurl = $baseurl; } /** * Export the data. * * @param renderer_base $output * @return stdClass */ public function export_for_template(renderer_base $output) { global $CFG, $DB, $SESSION, $PAGE; $context = context_course::instance($this->courseid); $data = new stdClass(); $data->userid = $this->userid; $data->courseid = $this->courseid; $data->moduleid = $this->moduleid; if (empty($data->moduleid)) { // Moduleid is optional. $data->moduleid = 0; } $data->baseurl = $this->baseurl; $data->groupselector = ''; if (has_any_capability(array('moodle/competency:usercompetencyview', 'moodle/competency:coursecompetencymanage'), $context)) { $course = $DB->get_record('course', array('id' => $this->courseid)); $currentgroup = groups_get_course_group($course, true); if ($currentgroup !== false) { $select = groups_allgroups_course_menu($course, $PAGE->url, true, $currentgroup); $data->groupselector = $select; } // Fetch showactive. $defaultgradeshowactiveenrol = !empty($CFG->grade_report_showonlyactiveenrol); $showonlyactiveenrol = get_user_preferences('grade_report_showonlyactiveenrol', $defaultgradeshowactiveenrol); $showonlyactiveenrol = $showonlyactiveenrol || !has_capability('moodle/course:viewsuspendedusers', $context); // Fetch current active group. $groupmode = groups_get_course_groupmode($course); $users = get_enrolled_users($context, 'moodle/competency:coursecompetencygradable', $currentgroup, 'u.*', null, 0, 0, $showonlyactiveenrol); $data->users = array(); foreach ($users as $user) { $data->users[] = (object)[ 'id' => $user->id, 'fullname' => fullname($user, has_capability('moodle/site:viewfullnames', $context)), 'selected' => $user->id == $this->userid ]; } $data->hasusers = true; $data->hasmodules = true; $data->modules = array(); $empty = (object)['id' => 0, 'name' => get_string('nofiltersapplied')]; $data->modules[] = $empty; $modinfo = get_fast_modinfo($this->courseid); foreach ($modinfo->get_cms() as $cm) { if ($cm->uservisible) { $exporter = new course_module_summary_exporter(null, ['cm' => $cm]); $module = $exporter->export($output); if ($module->id == $this->moduleid) { $module->selected = true; } $data->modules[] = $module; $data->hasmodules = true; } } } else { $data->users = array(); $data->hasusers = false; } return $data; } } templates/user_course_navigation.mustache000064400000004531152266351270015066 0ustar00{{! This file is part of Moodle - http://moodle.org/ Moodle is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Moodle is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Moodle. If not, see . }} {{! @template report_competency/user_course_navigation Moodle navigation control allowing to jump to a user or filter to an activity. Example context (json): { "hasusers": false, "hasmodules": false} }}

{{{groupselector}}}

{{#hasusers}} {{/hasusers}} {{#hasmodules}} {{/hasmodules}}
{{#js}} require(['core/form-autocomplete', 'report_competency/user_course_navigation'], function(autocomplete, nav) { (new nav('#user-nav-{{uniqid}}', '#module-nav-{{uniqid}}', '{{baseurl}}', {{userid}}, {{courseid}}, {{moduleid}})); {{#hasusers}} autocomplete.enhance('#user-nav-{{uniqid}}', false, false, {{# quote }}{{# str }}jumptouser, tool_lp{{/ str }}{{/ quote }}); {{/hasusers}} {{#hasmodules}} autocomplete.enhance('#module-nav-{{uniqid}}', false, false, {{# quote }}{{# str }}filterbyactivity, tool_lp{{/ str }}{{/ quote }}); {{/hasmodules}} }); {{/js}} templates/report.mustache000064400000005720152266351270011625 0ustar00{{! This file is part of Moodle - http://moodle.org/ Moodle is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Moodle is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Moodle. If not, see . }} {{! @template report_competency/report Moodle template competency breakdown report. This template includes ajax functionality, so it cannot be shown in the template library. }}
{{#pushratingstouserplans}}

{{#str}}coursecompetencyratingsarepushedtouserplans, tool_lp{{/str}}

{{/pushratingstouserplans}} {{^pushratingstouserplans}}

{{#str}}coursecompetencyratingsarenotpushedtouserplans, tool_lp{{/str}}

{{/pushratingstouserplans}}

{{#str}}coursecompetencybreakdownsummary, report_competency{{/str}}

{{#usercompetencies}} {{#competency}} {{/competency}} {{#usercompetencycourse}} {{/usercompetencycourse}} {{/usercompetencies}}
{{#str}}competency, report_competency{{/str}} {{#str}}rating, report_competency{{/str}}
{{{competency.shortname}}} {{competency.idnumber}} {{> report_competency/user_competency_summary}}
{{#js}} require(['tool_lp/competencydialogue'], function(Compdialogue) { Compdialogue.init(); }); require(['report_competency/grading_popup'], function(Popup) { (new Popup('[data-region=competency-breakdown-report]', '[data-user-competency=true]')); }); {{/js}} templates/user_competency_summary.mustache000064400000000275152266351270015273 0ustar00
{{#grade}}{{gradename}}{{/grade}} {{^grade}}{{#str}}notrated, report_competency{{/str}}{{/grade}} index.php000064400000007570152266351270006406 0ustar00. /** * This page lets users to manage site wide competencies. * * @package report_competency * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ use core\report_helper; require_once(__DIR__ . '/../../config.php'); $id = required_param('id', PARAM_INT); $params = array('id' => $id); $course = $DB->get_record('course', $params, '*', MUST_EXIST); require_login($course); $context = context_course::instance($course->id); $currentuser = optional_param('user', null, PARAM_INT); $currentmodule = optional_param('mod', null, PARAM_INT); if ($currentmodule > 0) { $cm = get_coursemodule_from_id('', $currentmodule, 0, false, MUST_EXIST); $context = context_module::instance($cm->id); } // Fetch current active group. $groupmode = groups_get_course_groupmode($course); $currentgroup = groups_get_course_group($course, true); if (empty($currentuser)) { $gradable = get_enrolled_users($context, 'moodle/competency:coursecompetencygradable', $currentgroup, 'u.id', null, 0, 1); if (empty($gradable)) { $currentuser = 0; } else { $currentuser = array_pop($gradable)->id; } } else { $gradable = get_enrolled_users($context, 'moodle/competency:coursecompetencygradable', $currentgroup, 'u.id'); if (count($gradable) == 0) { $currentuser = 0; } else if (!in_array($currentuser, array_keys($gradable))) { $currentuser = array_shift($gradable)->id; } } $urlparams = array('id' => $id); $navurl = new moodle_url('/report/competency/index.php', $urlparams); $urlparams['user'] = $currentuser; $urlparams['mod'] = $currentmodule; $url = new moodle_url('/report/competency/index.php', $urlparams); $title = get_string('pluginname', 'report_competency'); $coursename = format_string($course->fullname, true, array('context' => $context)); $PAGE->navigation->override_active_url($navurl); $PAGE->set_url($url); $PAGE->set_title($title); $PAGE->set_heading($coursename); $PAGE->set_pagelayout('incourse'); $output = $PAGE->get_renderer('report_competency'); echo $output->header(); $pluginname = get_string('pluginname', 'report_competency'); report_helper::print_report_selector($pluginname); $baseurl = new moodle_url('/report/competency/index.php'); $nav = new \report_competency\output\user_course_navigation($currentuser, $course->id, $baseurl, $currentmodule); $top = $output->render($nav); if ($currentuser > 0) { $user = core_user::get_user($currentuser); $usercontext = context_user::instance($currentuser); $userheading = array( 'heading' => fullname($user, has_capability('moodle/site:viewfullnames', $context)), 'user' => $user, 'usercontext' => $usercontext ); if ($currentmodule > 0) { $title = get_string('filtermodule', 'report_competency', format_string($cm->name)); } $top .= $output->context_header($userheading, 3); } echo $output->container($top, 'clearfix'); if ($currentuser > 0) { $page = new \report_competency\output\report($course->id, $currentuser, $currentmodule); echo $output->render($page); } else { echo $output->container('', 'clearfix'); echo $output->notify_problem(get_string('noparticipants', 'tool_lp')); } echo $output->footer(); amd/src/grading_popup.js000064400000012177152266351270011311 0ustar00// This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . /** * Module to enable inline editing of a comptency grade. * * @module report_competency/grading_popup * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define(['jquery', 'core/notification', 'core/str', 'core/ajax', 'core/log', 'core/templates', 'tool_lp/dialogue'], function($, notification, str, ajax, log, templates, Dialogue) { /** * GradingPopup * * @class report_competency/grading_popup * @param {String} regionSelector The regionSelector * @param {String} userCompetencySelector The userCompetencySelector */ var GradingPopup = function(regionSelector, userCompetencySelector) { this._regionSelector = regionSelector; this._userCompetencySelector = userCompetencySelector; $(this._regionSelector).on('click', this._userCompetencySelector, this._handleClick.bind(this)); }; /** * Get the data from the clicked cell and open the popup. * * @method _handleClick * @param {Event} e The event */ GradingPopup.prototype._handleClick = function(e) { var cell = $(e.target).closest(this._userCompetencySelector); var competencyId = $(cell).data('competencyid'); var courseId = $(cell).data('courseid'); var userId = $(cell).data('userid'); log.debug('Clicked on cell: competencyId=' + competencyId + ', courseId=' + courseId + ', userId=' + userId); var requests = ajax.call([{ methodname: 'tool_lp_data_for_user_competency_summary_in_course', args: {userid: userId, competencyid: competencyId, courseid: courseId}, }, { methodname: 'core_competency_user_competency_viewed_in_course', args: {userid: userId, competencyid: competencyId, courseid: courseId}, }]); $.when(requests[0], requests[1]) .then(this._contextLoaded.bind(this)) .catch(notification.exception); }; /** * We loaded the context, now render the template. * * @method _contextLoaded * @param {Object} context * @returns {Promise} */ GradingPopup.prototype._contextLoaded = function(context) { // We have to display user info in popup. context.displayuser = true; M.util.js_pending('report_competency/grading_popup:_contextLoaded'); return $.when( str.get_string('usercompetencysummary', 'report_competency'), templates.render('tool_lp/user_competency_summary_in_course', context) ) .then(function(title, templateData) { return new Dialogue( title, templateData[0], function() { templates.runTemplateJS(templateData[1]); M.util.js_complete('report_competency/grading_popup:_contextLoaded'); }, this._refresh.bind(this), true ); }.bind(this)); }; /** * Refresh the page. * * @method _refresh * @returns {Promise} */ GradingPopup.prototype._refresh = function() { var region = $(this._regionSelector); var courseId = region.data('courseid'); var moduleId = region.data('moduleid'); var userId = region.data('userid'); // The module id is expected to be an integer, so don't pass empty string. if (moduleId === '') { moduleId = 0; } return ajax.call([{ methodname: 'report_competency_data_for_report', args: {courseid: courseId, userid: userId, moduleid: moduleId}, done: this._pageContextLoaded.bind(this), fail: notification.exception }]); }; /** * We loaded the context, now render the template. * * @method _pageContextLoaded * @param {Object} context */ GradingPopup.prototype._pageContextLoaded = function(context) { templates.render('report_competency/report', context) .then(function(html, js) { templates.replaceNode(this._regionSelector, html, js); return; }.bind(this)) .catch(notification.exception); }; /** @property {String} The selector for the region with the user competencies */ GradingPopup.prototype._regionSelector = null; /** @property {String} The selector for the region with a single user competencies */ GradingPopup.prototype._userCompetencySelector = null; return GradingPopup; }); amd/src/user_course_navigation.js000064400000006564152266351270013233 0ustar00// This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . /** * Module to navigation between users in a course. * * @module report_competency/user_course_navigation * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define(['jquery'], function($) { /** * UserCourseNavigation * * @class report_competency/user_course_navigation * @param {String} userSelector The selector of the user element. * @param {String} moduleSelector The selector of the module element. * @param {String} baseUrl The base url for the page (no params). * @param {Number} userId The course id * @param {Number} courseId The user id * @param {Number} moduleId The activity module (filter) */ var UserCourseNavigation = function(userSelector, moduleSelector, baseUrl, userId, courseId, moduleId) { this._baseUrl = baseUrl; this._userId = userId + ''; this._courseId = courseId; this._moduleId = moduleId; $(userSelector).on('change', this._userChanged.bind(this)); $(moduleSelector).on('change', this._moduleChanged.bind(this)); }; /** * The user was changed in the select list. * * @method _userChanged * @param {Event} e the event */ UserCourseNavigation.prototype._userChanged = function(e) { // Note: This change causes a page reload and is intentionally not paired with a js_complete call. M.util.js_pending('report_competency/user_course_navigation:_userChanged'); var newUserId = $(e.target).val(); var queryStr = '?user=' + newUserId + '&id=' + this._courseId + '&mod=' + this._moduleId; document.location = this._baseUrl + queryStr; }; /** * The module was changed in the select list. * * @method _moduleChanged * @param {Event} e the event */ UserCourseNavigation.prototype._moduleChanged = function(e) { // Note: This change causes a page reload and is intentionally not paired with a js_complete call. M.util.js_pending('report_competency/user_course_navigation:_moduleChanged'); var newModuleId = $(e.target).val(); var queryStr = '?mod=' + newModuleId + '&id=' + this._courseId + '&user=' + this._userId; document.location = this._baseUrl + queryStr; }; /** @property {Number} The id of the user. */ UserCourseNavigation.prototype._userId = null; /** @property {Number} The id of the module. */ UserCourseNavigation.prototype._moduleId = null; /** @property {Number} The id of the course. */ UserCourseNavigation.prototype._courseId = null; /** @property {String} Plugin base url. */ UserCourseNavigation.prototype._baseUrl = null; return UserCourseNavigation; }); amd/build/grading_popup.min.js000064400000005202152266351270012372 0ustar00/** * Module to enable inline editing of a comptency grade. * * @module report_competency/grading_popup * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define("report_competency/grading_popup",["jquery","core/notification","core/str","core/ajax","core/log","core/templates","tool_lp/dialogue"],(function($,notification,str,ajax,log,templates,Dialogue){var GradingPopup=function(regionSelector,userCompetencySelector){this._regionSelector=regionSelector,this._userCompetencySelector=userCompetencySelector,$(this._regionSelector).on("click",this._userCompetencySelector,this._handleClick.bind(this))};return GradingPopup.prototype._handleClick=function(e){var cell=$(e.target).closest(this._userCompetencySelector),competencyId=$(cell).data("competencyid"),courseId=$(cell).data("courseid"),userId=$(cell).data("userid");log.debug("Clicked on cell: competencyId="+competencyId+", courseId="+courseId+", userId="+userId);var requests=ajax.call([{methodname:"tool_lp_data_for_user_competency_summary_in_course",args:{userid:userId,competencyid:competencyId,courseid:courseId}},{methodname:"core_competency_user_competency_viewed_in_course",args:{userid:userId,competencyid:competencyId,courseid:courseId}}]);$.when(requests[0],requests[1]).then(this._contextLoaded.bind(this)).catch(notification.exception)},GradingPopup.prototype._contextLoaded=function(context){return context.displayuser=!0,M.util.js_pending("report_competency/grading_popup:_contextLoaded"),$.when(str.get_string("usercompetencysummary","report_competency"),templates.render("tool_lp/user_competency_summary_in_course",context)).then(function(title,templateData){return new Dialogue(title,templateData[0],(function(){templates.runTemplateJS(templateData[1]),M.util.js_complete("report_competency/grading_popup:_contextLoaded")}),this._refresh.bind(this),!0)}.bind(this))},GradingPopup.prototype._refresh=function(){var region=$(this._regionSelector),courseId=region.data("courseid"),moduleId=region.data("moduleid"),userId=region.data("userid");return""===moduleId&&(moduleId=0),ajax.call([{methodname:"report_competency_data_for_report",args:{courseid:courseId,userid:userId,moduleid:moduleId},done:this._pageContextLoaded.bind(this),fail:notification.exception}])},GradingPopup.prototype._pageContextLoaded=function(context){templates.render("report_competency/report",context).then(function(html,js){templates.replaceNode(this._regionSelector,html,js)}.bind(this)).catch(notification.exception)},GradingPopup.prototype._regionSelector=null,GradingPopup.prototype._userCompetencySelector=null,GradingPopup})); //# sourceMappingURL=grading_popup.min.js.mapamd/build/user_course_navigation.min.js.map000064400000011045152266351270015067 0ustar00{"version":3,"file":"user_course_navigation.min.js","sources":["../src/user_course_navigation.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Module to navigation between users in a course.\n *\n * @module report_competency/user_course_navigation\n * @copyright 2015 Damyon Wiese\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(['jquery'], function($) {\n\n /**\n * UserCourseNavigation\n *\n * @class report_competency/user_course_navigation\n * @param {String} userSelector The selector of the user element.\n * @param {String} moduleSelector The selector of the module element.\n * @param {String} baseUrl The base url for the page (no params).\n * @param {Number} userId The course id\n * @param {Number} courseId The user id\n * @param {Number} moduleId The activity module (filter)\n */\n var UserCourseNavigation = function(userSelector, moduleSelector, baseUrl, userId, courseId, moduleId) {\n this._baseUrl = baseUrl;\n this._userId = userId + '';\n this._courseId = courseId;\n this._moduleId = moduleId;\n\n $(userSelector).on('change', this._userChanged.bind(this));\n $(moduleSelector).on('change', this._moduleChanged.bind(this));\n };\n\n /**\n * The user was changed in the select list.\n *\n * @method _userChanged\n * @param {Event} e the event\n */\n UserCourseNavigation.prototype._userChanged = function(e) {\n // Note: This change causes a page reload and is intentionally not paired with a js_complete call.\n M.util.js_pending('report_competency/user_course_navigation:_userChanged');\n var newUserId = $(e.target).val();\n var queryStr = '?user=' + newUserId + '&id=' + this._courseId + '&mod=' + this._moduleId;\n document.location = this._baseUrl + queryStr;\n };\n\n /**\n * The module was changed in the select list.\n *\n * @method _moduleChanged\n * @param {Event} e the event\n */\n UserCourseNavigation.prototype._moduleChanged = function(e) {\n // Note: This change causes a page reload and is intentionally not paired with a js_complete call.\n M.util.js_pending('report_competency/user_course_navigation:_moduleChanged');\n var newModuleId = $(e.target).val();\n var queryStr = '?mod=' + newModuleId + '&id=' + this._courseId + '&user=' + this._userId;\n document.location = this._baseUrl + queryStr;\n };\n\n /** @property {Number} The id of the user. */\n UserCourseNavigation.prototype._userId = null;\n /** @property {Number} The id of the module. */\n UserCourseNavigation.prototype._moduleId = null;\n /** @property {Number} The id of the course. */\n UserCourseNavigation.prototype._courseId = null;\n /** @property {String} Plugin base url. */\n UserCourseNavigation.prototype._baseUrl = null;\n\n return UserCourseNavigation;\n});\n"],"names":["define","$","UserCourseNavigation","userSelector","moduleSelector","baseUrl","userId","courseId","moduleId","_baseUrl","_userId","_courseId","_moduleId","on","this","_userChanged","bind","_moduleChanged","prototype","e","M","util","js_pending","queryStr","target","val","document","location"],"mappings":";;;;;;;AAuBAA,kDAAO,CAAC,WAAW,SAASC,OAapBC,qBAAuB,SAASC,aAAcC,eAAgBC,QAASC,OAAQC,SAAUC,eACpFC,SAAWJ,aACXK,QAAUJ,OAAS,QACnBK,UAAYJ,cACZK,UAAYJ,SAEjBP,EAAEE,cAAcU,GAAG,SAAUC,KAAKC,aAAaC,KAAKF,OACpDb,EAAEG,gBAAgBS,GAAG,SAAUC,KAAKG,eAAeD,KAAKF,eAS5DZ,qBAAqBgB,UAAUH,aAAe,SAASI,GAEnDC,EAAEC,KAAKC,WAAW,6DAEdC,SAAW,SADCtB,EAAEkB,EAAEK,QAAQC,MACU,OAASX,KAAKH,UAAY,QAAUG,KAAKF,UAC/Ec,SAASC,SAAWb,KAAKL,SAAWc,UASxCrB,qBAAqBgB,UAAUD,eAAiB,SAASE,GAErDC,EAAEC,KAAKC,WAAW,+DAEdC,SAAW,QADGtB,EAAEkB,EAAEK,QAAQC,MACS,OAASX,KAAKH,UAAY,SAAWG,KAAKJ,QACjFgB,SAASC,SAAWb,KAAKL,SAAWc,UAIxCrB,qBAAqBgB,UAAUR,QAAU,KAEzCR,qBAAqBgB,UAAUN,UAAY,KAE3CV,qBAAqBgB,UAAUP,UAAY,KAE3CT,qBAAqBgB,UAAUT,SAAW,KAEnCP"}amd/build/user_course_navigation.min.js000064400000002555152266351270014321 0ustar00/** * Module to navigation between users in a course. * * @module report_competency/user_course_navigation * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define("report_competency/user_course_navigation",["jquery"],(function($){var UserCourseNavigation=function(userSelector,moduleSelector,baseUrl,userId,courseId,moduleId){this._baseUrl=baseUrl,this._userId=userId+"",this._courseId=courseId,this._moduleId=moduleId,$(userSelector).on("change",this._userChanged.bind(this)),$(moduleSelector).on("change",this._moduleChanged.bind(this))};return UserCourseNavigation.prototype._userChanged=function(e){M.util.js_pending("report_competency/user_course_navigation:_userChanged");var queryStr="?user="+$(e.target).val()+"&id="+this._courseId+"&mod="+this._moduleId;document.location=this._baseUrl+queryStr},UserCourseNavigation.prototype._moduleChanged=function(e){M.util.js_pending("report_competency/user_course_navigation:_moduleChanged");var queryStr="?mod="+$(e.target).val()+"&id="+this._courseId+"&user="+this._userId;document.location=this._baseUrl+queryStr},UserCourseNavigation.prototype._userId=null,UserCourseNavigation.prototype._moduleId=null,UserCourseNavigation.prototype._courseId=null,UserCourseNavigation.prototype._baseUrl=null,UserCourseNavigation})); //# sourceMappingURL=user_course_navigation.min.js.mapamd/build/grading_popup.min.js.map000064400000017022152266351270013151 0ustar00{"version":3,"file":"grading_popup.min.js","sources":["../src/grading_popup.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Module to enable inline editing of a comptency grade.\n *\n * @module report_competency/grading_popup\n * @copyright 2015 Damyon Wiese\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(['jquery', 'core/notification', 'core/str', 'core/ajax', 'core/log', 'core/templates', 'tool_lp/dialogue'],\n function($, notification, str, ajax, log, templates, Dialogue) {\n\n /**\n * GradingPopup\n *\n * @class report_competency/grading_popup\n * @param {String} regionSelector The regionSelector\n * @param {String} userCompetencySelector The userCompetencySelector\n */\n var GradingPopup = function(regionSelector, userCompetencySelector) {\n this._regionSelector = regionSelector;\n this._userCompetencySelector = userCompetencySelector;\n\n $(this._regionSelector).on('click', this._userCompetencySelector, this._handleClick.bind(this));\n };\n\n /**\n * Get the data from the clicked cell and open the popup.\n *\n * @method _handleClick\n * @param {Event} e The event\n */\n GradingPopup.prototype._handleClick = function(e) {\n var cell = $(e.target).closest(this._userCompetencySelector);\n var competencyId = $(cell).data('competencyid');\n var courseId = $(cell).data('courseid');\n var userId = $(cell).data('userid');\n\n log.debug('Clicked on cell: competencyId=' + competencyId + ', courseId=' + courseId + ', userId=' + userId);\n\n var requests = ajax.call([{\n methodname: 'tool_lp_data_for_user_competency_summary_in_course',\n args: {userid: userId, competencyid: competencyId, courseid: courseId},\n }, {\n methodname: 'core_competency_user_competency_viewed_in_course',\n args: {userid: userId, competencyid: competencyId, courseid: courseId},\n }]);\n\n $.when(requests[0], requests[1])\n .then(this._contextLoaded.bind(this))\n .catch(notification.exception);\n };\n\n /**\n * We loaded the context, now render the template.\n *\n * @method _contextLoaded\n * @param {Object} context\n * @returns {Promise}\n */\n GradingPopup.prototype._contextLoaded = function(context) {\n // We have to display user info in popup.\n context.displayuser = true;\n\n M.util.js_pending('report_competency/grading_popup:_contextLoaded');\n\n return $.when(\n str.get_string('usercompetencysummary', 'report_competency'),\n templates.render('tool_lp/user_competency_summary_in_course', context)\n )\n .then(function(title, templateData) {\n return new Dialogue(\n title,\n templateData[0],\n function() {\n templates.runTemplateJS(templateData[1]);\n M.util.js_complete('report_competency/grading_popup:_contextLoaded');\n },\n this._refresh.bind(this),\n true\n );\n }.bind(this));\n };\n\n /**\n * Refresh the page.\n *\n * @method _refresh\n * @returns {Promise}\n */\n GradingPopup.prototype._refresh = function() {\n var region = $(this._regionSelector);\n var courseId = region.data('courseid');\n var moduleId = region.data('moduleid');\n var userId = region.data('userid');\n\n // The module id is expected to be an integer, so don't pass empty string.\n if (moduleId === '') {\n moduleId = 0;\n }\n\n return ajax.call([{\n methodname: 'report_competency_data_for_report',\n args: {courseid: courseId, userid: userId, moduleid: moduleId},\n done: this._pageContextLoaded.bind(this),\n fail: notification.exception\n }]);\n };\n\n /**\n * We loaded the context, now render the template.\n *\n * @method _pageContextLoaded\n * @param {Object} context\n */\n GradingPopup.prototype._pageContextLoaded = function(context) {\n templates.render('report_competency/report', context)\n .then(function(html, js) {\n templates.replaceNode(this._regionSelector, html, js);\n\n return;\n }.bind(this))\n .catch(notification.exception);\n };\n\n /** @property {String} The selector for the region with the user competencies */\n GradingPopup.prototype._regionSelector = null;\n /** @property {String} The selector for the region with a single user competencies */\n GradingPopup.prototype._userCompetencySelector = null;\n\n return GradingPopup;\n});\n"],"names":["define","$","notification","str","ajax","log","templates","Dialogue","GradingPopup","regionSelector","userCompetencySelector","_regionSelector","_userCompetencySelector","this","on","_handleClick","bind","prototype","e","cell","target","closest","competencyId","data","courseId","userId","debug","requests","call","methodname","args","userid","competencyid","courseid","when","then","_contextLoaded","catch","exception","context","displayuser","M","util","js_pending","get_string","render","title","templateData","runTemplateJS","js_complete","_refresh","region","moduleId","moduleid","done","_pageContextLoaded","fail","html","js","replaceNode"],"mappings":";;;;;;;AAuBAA,yCAAO,CAAC,SAAU,oBAAqB,WAAY,YAAa,WAAY,iBAAkB,qBACvF,SAASC,EAAGC,aAAcC,IAAKC,KAAMC,IAAKC,UAAWC,cASpDC,aAAe,SAASC,eAAgBC,6BACnCC,gBAAkBF,oBAClBG,wBAA0BF,uBAE/BT,EAAEY,KAAKF,iBAAiBG,GAAG,QAASD,KAAKD,wBAAyBC,KAAKE,aAAaC,KAAKH,eAS7FL,aAAaS,UAAUF,aAAe,SAASG,OACvCC,KAAOlB,EAAEiB,EAAEE,QAAQC,QAAQR,KAAKD,yBAChCU,aAAerB,EAAEkB,MAAMI,KAAK,gBAC5BC,SAAWvB,EAAEkB,MAAMI,KAAK,YACxBE,OAASxB,EAAEkB,MAAMI,KAAK,UAE1BlB,IAAIqB,MAAM,iCAAmCJ,aAAe,cAAgBE,SAAW,YAAcC,YAEjGE,SAAWvB,KAAKwB,KAAK,CAAC,CACtBC,WAAY,qDACZC,KAAM,CAACC,OAAQN,OAAQO,aAAcV,aAAcW,SAAUT,WAC9D,CACCK,WAAY,mDACZC,KAAM,CAACC,OAAQN,OAAQO,aAAcV,aAAcW,SAAUT,aAGjEvB,EAAEiC,KAAKP,SAAS,GAAIA,SAAS,IAC5BQ,KAAKtB,KAAKuB,eAAepB,KAAKH,OAC9BwB,MAAMnC,aAAaoC,YAUxB9B,aAAaS,UAAUmB,eAAiB,SAASG,gBAE7CA,QAAQC,aAAc,EAEtBC,EAAEC,KAAKC,WAAW,kDAEX1C,EAAEiC,KACL/B,IAAIyC,WAAW,wBAAyB,qBACxCtC,UAAUuC,OAAO,4CAA6CN,UAEjEJ,KAAK,SAASW,MAAOC,qBACX,IAAIxC,SACPuC,MACAC,aAAa,IACb,WACIzC,UAAU0C,cAAcD,aAAa,IACrCN,EAAEC,KAAKO,YAAY,oDAEvBpC,KAAKqC,SAASlC,KAAKH,OACnB,IAENG,KAAKH,QASXL,aAAaS,UAAUiC,SAAW,eAC1BC,OAASlD,EAAEY,KAAKF,iBAChBa,SAAW2B,OAAO5B,KAAK,YACvB6B,SAAWD,OAAO5B,KAAK,YACvBE,OAAS0B,OAAO5B,KAAK,gBAGR,KAAb6B,WACAA,SAAW,GAGRhD,KAAKwB,KAAK,CAAC,CACdC,WAAY,oCACZC,KAAM,CAACG,SAAUT,SAAUO,OAAQN,OAAQ4B,SAAUD,UACrDE,KAAMzC,KAAK0C,mBAAmBvC,KAAKH,MACnC2C,KAAMtD,aAAaoC,cAU3B9B,aAAaS,UAAUsC,mBAAqB,SAAShB,SACjDjC,UAAUuC,OAAO,2BAA4BN,SAC5CJ,KAAK,SAASsB,KAAMC,IACjBpD,UAAUqD,YAAY9C,KAAKF,gBAAiB8C,KAAMC,KAGpD1C,KAAKH,OACNwB,MAAMnC,aAAaoC,YAIxB9B,aAAaS,UAAUN,gBAAkB,KAEzCH,aAAaS,UAAUL,wBAA0B,KAE1CJ"}tests/behat/course_report_competency.feature000064400000002142152266351270015477 0ustar00@report @report_competency Feature: In a course administration page, navigate through report page, test for course competency page In order to navigate through report page As an admin Go to course administration -> reports -> competency breackdown Background: Given the following "courses" exist: | fullname | shortname | category | groupmode | | Course 1 | C1 | 0 | 1 | And the following "users" exist: | username | firstname | lastname | email | | student1 | Student | 1 | student1@example.com | And the following "course enrolments" exist: | user | course | role | | admin | C1 | editingteacher | | student1 | C1 | student | @javascript Scenario: Selector should be available in the course competency page Given I log in as "admin" And I am on "Course 1" course homepage When I navigate to "Reports" in current page administration And I click on "Competency breakdown" "link" Then "Report" "field" should exist in the "tertiary-navigation" "region" And I should see "Competency breakdown" in the "tertiary-navigation" "region" tests/behat/breakdown_by_activity.feature000064400000007441152266351270014747 0ustar00@report @javascript @report_competency Feature: See the competencies for an activity As a competency grader In order to perform mark all competencies for an activity I need to see the competencies linked to one activity in the breakdown report. Background: Given the following "core_competency > frameworks" exist: | shortname | idnumber | | Test-Framework | ID-FW1 | And the following "core_competency > competencies" exist: | shortname | competencyframework | | Test-Comp1 | ID-FW1 | | Test-Comp2 | ID-FW1 | Given the following "courses" exist: | shortname | fullname | | C1 | Course 1 | And the following "users" exist: | username | firstname | lastname | email | idnumber | middlename | alternatename | firstnamephonetic | lastnamephonetic | | student1 | Grainne | Beauchamp | student1@example.com | s1 | Ann | Jill | Gronya | Beecham | | student2 | Niamh | Cholmondely | student2@example.com | s2 | Jane | Nina | Nee | Chumlee | And the following "course enrolments" exist: | user | course | role | | student1 | C1 | student | | student2 | C1 | student | And the following "activities" exist: | activity | name | intro | course | idnumber | | page | PageName1 | PageDesc1 | C1 | PAGE1 | And the following config values are set as admin: | fullnamedisplay | firstname | | alternativefullnameformat | middlename, alternatename, firstname, lastname | And I log in as "admin" And I am on site homepage And I follow "Course 1" And I navigate to "Competencies" in current page administration And I press "Add competencies to course" And "Competency picker" "dialogue" should be visible And I select "Test-Comp1" of the competency tree And I click on "Add" "button" in the "Competency picker" "dialogue" And I press "Add competencies to course" And "Competency picker" "dialogue" should be visible And I select "Test-Comp2" of the competency tree And I click on "Add" "button" in the "Competency picker" "dialogue" And I am on the PageName1 "page activity editing" page And I click on "Expand all" "link" in the "region-main" "region" And I set the field "Course competencies" to "Test-Comp1" And I press "Save and return to course" @javascript Scenario: Go to the competency breakdown report When I navigate to "Reports" in current page administration And I click on "Competency breakdown" "link" And I set the competency filter "Filter competencies by resource or activity" to "PageName1" Then I should see "Test-Comp1" And I should not see "Test-Comp2" And I should see "Ann, Jill, Grainne, Beauchamp" And I should see "Ann, Jill, Grainne, Beauchamp" in the ".form-autocomplete-selection" "css_element" And I open the autocomplete suggestions list And I should see "Jane, Nina, Niamh, Cholmondely" in the ".form-autocomplete-suggestions" "css_element" And I click on "Not rated" "link" And I click on "Rate" "button" And I set the field "Rating" to "A" And I click on "Rate" "button" in the ".competency-grader" "css_element" And I click on "Close" "button" in the "User competency summary" "dialogue" And I click on "PageName1" "autocomplete_selection" And I should see "Test-Comp1" And I should see "Test-Comp2" @accessibility Scenario: Evaluate the accessibility of the user competency summary dialogue Given I navigate to "Reports" in current page administration When I click on "Competency breakdown" "link" And I click on "Not rated" "link" And the page should meet accessibility standards tests/behat/behat_report_competency.php000064400000011162152266351270014420 0ustar00. /** * Behat competency report definitions. * * @package report_competency * @category test * @copyright 2022 Noel De Martin * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ require_once(__DIR__ . '/../../../../lib/behat/behat_base.php'); /** * Competency report definitions. * * @package report_competency * @category test * @copyright 2022 Noel De Martin * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class behat_report_competency extends behat_base { /** * Convert page names to URLs for steps like 'When I am on the "[identifier]" "[page type]" page'. * * Recognised page names are: * | pagetype | name meaning | description | * | Breakdown | Course name | The course competencies breakdown page | * * @param string $page identifies which type of page this is, e.g. 'Breakdown'. * @param string $identifier identifies the particular page, e.g. 'C1'. * @return moodle_url the corresponding URL. * @throws Exception with a meaningful error message if the specified page cannot be found. */ protected function resolve_page_instance_url(string $page, string $identifier): moodle_url { switch (strtolower($page)) { case 'breakdown': $courseid = $this->get_course_id($identifier); return new moodle_url('/report/competency/index.php', [ 'id' => $courseid, ]); default: throw new Exception("Unrecognised page type '{$page}'"); } } /** * Return a list of the exact named selectors for the component. * * @return behat_component_named_selector[] */ public static function get_exact_named_selectors(): array { return [ new behat_component_named_selector('breakdown', [ "//*[@data-region='competency-breakdown-report']//table". "//tr[contains(., //a[@data-action='competency-dialogue'][contains(., %locator%)])]", ]), new behat_component_named_selector('breakdown rating', [ "//td[position()=2][contains(., //a[@title='User competency summary'][contains(., %locator%)])]", ]), ]; } /** * Set the value of a competency filter. * * @When /^I set the competency filter "([^"]*)" to "([^"]*)"$/ * @param string $fieldlocator The field locator. * @param string $value The value to set. * @throws Exception */ public function set_competency_filter( string $fieldlocator, string $value, ): void { $field = behat_field_manager::get_form_field_from_label($fieldlocator, $this)->get_node(); $session = $this->getSession(); $value = trim($value); // Click into the field. $field->click(); // Remove any existing text. do { behat_base::type_keys($session, [behat_keys::BACKSPACE, behat_keys::DELETE]); } while (strlen($field->getValue()) > 0); $this->wait_for_pending_js(); // Type in the new value. behat_base::type_keys($session, str_split($value)); $this->wait_for_pending_js(); // If the autocomplete found suggestions, then it will have: // 1) marked itself as expanded; and // 2) have an aria-selected suggestion in the list. $expanded = $field->getAttribute('aria-expanded'); $suggestion = $field->getParent()->getParent()->find('css', '.form-autocomplete-suggestions > [aria-selected="true"]'); if ($expanded && null !== $suggestion) { // A suggestion was found. // Click on the first item in the list. $suggestion->click(); } else { throw new \InvalidArgumentException( "Unable to find '{$value}' in the list of options." ); } $this->wait_for_pending_js(); } }