diff --git a/admin/tool/lp/classes/api.php b/admin/tool/lp/classes/api.php index d1b6048bc42..f0ae9f2f166 100644 --- a/admin/tool/lp/classes/api.php +++ b/admin/tool/lp/classes/api.php @@ -1989,41 +1989,66 @@ class api { /** * Count all the competencies in a learning plan template. * - * @param int $templateid The id of the template to check. + * @param template|int $templateorid The template or its ID. * @return int */ - public static function count_competencies_in_template($templateid) { + public static function count_competencies_in_template($templateorid) { static::require_enabled(); // First we do a permissions check. - $template = new template($templateid); - $context = $template->get_context(); + $template = $templateorid; + if (!is_object($template)) { + $template = new template($template); + } if (!$template->can_read()) { throw new required_capability_exception($template->get_context(), 'tool/lp:templateread', 'nopermissions', ''); } // OK - all set. - return template_competency::count_competencies($templateid); + 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(), 'tool/lp:templateread', 'nopermissions', ''); + } + + // OK - all set. + return template_competency::count_competencies_with_no_courses($template->get_id()); } /** * List all the competencies in a template. * - * @param int $templateid The id of the template to check. + * @param template|int $templateorid The template or its ID. * @return array of competencies */ - public static function list_competencies_in_template($templateid) { + public static function list_competencies_in_template($templateorid) { static::require_enabled(); // First we do a permissions check. - $template = new template($templateid); - $context = $template->get_context(); + $template = $templateorid; + if (!is_object($template)) { + $template = new template($template); + } if (!$template->can_read()) { throw new required_capability_exception($template->get_context(), 'tool/lp:templateread', 'nopermissions', ''); } // OK - all set. - return template_competency::list_competencies($templateid); + return template_competency::list_competencies($template->get_id()); } /** @@ -3516,7 +3541,6 @@ class api { // OK - all set. return $template->has_plans(); - } /** @@ -4680,6 +4704,101 @@ class api { return $result; } + /** + * Count the plans in the template, filtered by status. + * + * Requires tool/lp:templateread 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) { + $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(), 'tool/lp:templateread', 'nopermissions', ''); + } + + return plan::count_records_for_template($template->get_id(), $status); + } + + /** + * Count the user-completency-plans in the template, optionally filtered by proficiency. + * + * Requires tool/lp:templateread 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) { + $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(), 'tool/lp:templateread', 'nopermissions', ''); + } + + return user_competency_plan::count_records_for_template($template->get_id(), $proficiency); + } + + /** + * List the plans in the template, filtered by status. + * + * Requires tool/lp:templateread 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(), 'tool/lp:templateread', 'nopermissions', ''); + } + + return plan::get_records_for_template($template->get_id(), $status, $skip, $limit); + } + + /** + * Get the most often not completed competency for this template. + * + * Requires tool/lp:templateread 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) { + $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(), 'tool/lp:templateread', 'nopermissions', ''); + } + + return user_competency_plan::get_least_proficient_competencies_for_template($template->get_id(), $skip, $limit); + } + /** * Template event viewed. * diff --git a/admin/tool/lp/classes/external.php b/admin/tool/lp/classes/external.php index b88739c4311..6a8a5c8e0fb 100644 --- a/admin/tool/lp/classes/external.php +++ b/admin/tool/lp/classes/external.php @@ -45,6 +45,7 @@ use grade_scale; use tool_lp\external\competency_framework_exporter; use tool_lp\external\competency_summary_exporter; use tool_lp\external\cohort_summary_exporter; +use tool_lp\external\template_statistics_exporter; use tool_lp\external\user_summary_exporter; use tool_lp\external\user_competency_exporter; use tool_lp\external\user_competency_plan_exporter; @@ -3203,6 +3204,7 @@ class external extends external_api { competency_summary_exporter::get_read_structure() ), 'manageurl' => new external_value(PARAM_LOCALURL, 'Url to the manage competencies page.'), + 'statistics' => template_statistics_exporter::get_read_structure() )); } diff --git a/admin/tool/lp/classes/external/template_statistics_exporter.php b/admin/tool/lp/classes/external/template_statistics_exporter.php new file mode 100644 index 00000000000..9fdd3f11b45 --- /dev/null +++ b/admin/tool/lp/classes/external/template_statistics_exporter.php @@ -0,0 +1,125 @@ +. + +/** + * Class for exporting a template statistics summary. + * + * @package tool_lp + * @copyright 2016 Damyon Wiese + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace tool_lp\external; +defined('MOODLE_INTERNAL') || die(); + +use renderer_base; +use moodle_url; + +/** + * Class for exporting a cohort summary from an stdClass. + * + * @copyright 2015 Damyon Wiese + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class template_statistics_exporter extends exporter { + + public static function define_properties() { + return array( + 'competencycount' => array( + 'type' => PARAM_INT, + ), + 'unlinkedcompetencycount' => array( + 'type' => PARAM_INT, + ), + 'plancount' => array( + 'type' => PARAM_INT, + ), + 'completedplancount' => array( + 'type' => PARAM_INT, + ), + 'usercompetencyplancount' => array( + 'type' => PARAM_INT, + ), + 'proficientusercompetencyplancount' => array( + 'type' => PARAM_INT, + ) + ); + } + + public static function define_other_properties() { + return array( + 'linkedcompetencypercentage' => array( + 'type' => PARAM_FLOAT + ), + 'linkedcompetencycount' => array( + 'type' => PARAM_INT + ), + 'completedplanpercentage' => array( + 'type' => PARAM_FLOAT + ), + 'proficientusercompetencyplanpercentage' => array( + 'type' => PARAM_FLOAT + ), + 'leastproficient' => array( + 'type' => competency_exporter::read_properties_definition(), + 'multiple' => true + ), + 'leastproficientcount' => array( + 'type' => PARAM_INT + ), + ); + } + + protected function get_other_values(renderer_base $output) { + $linkedcompetencycount = $this->data->competencycount - $this->data->unlinkedcompetencycount; + if ($linkedcompetencycount < 0) { + // Should never happen. + $linkedcompetencycount = 0; + } + $linkedcompetencypercentage = 0; + if ($this->data->competencycount > 0) { + $linkedcompetencypercentage = format_float(((float) $linkedcompetencycount / + (float) $this->data->competencycount) * 100.0); + } + $completedplanpercentage = 0; + if ($this->data->plancount > 0) { + $completedplanpercentage = format_float(((float) $this->data->completedplancount / + (float) $this->data->plancount) * 100.0); + } + $proficientusercompetencyplanpercentage = 0; + if ($this->data->usercompetencyplancount > 0) { + $proficientusercompetencyplanpercentage = format_float(((float) $this->data->proficientusercompetencyplancount / + (float) $this->data->usercompetencyplancount) * 100.0); + } + $competencies = array(); + $contextcache = array(); + foreach ($this->data->leastproficientcompetencies as $competency) { + if (!isset($contextcache[$competency->get_competencyframeworkid()])) { + $contextcache[$competency->get_competencyframeworkid()] = $competency->get_context(); + } + $context = $contextcache[$competency->get_competencyframeworkid()]; + $exporter = new competency_exporter($competency, array('context' => $context)); + $competencies[] = $exporter->export($output); + } + return array( + 'linkedcompetencycount' => $linkedcompetencycount, + 'linkedcompetencypercentage' => $linkedcompetencypercentage, + 'completedplanpercentage' => $completedplanpercentage, + 'proficientusercompetencyplanpercentage' => $proficientusercompetencyplanpercentage, + 'leastproficient' => $competencies, + 'leastproficientcount' => count($competencies) + ); + } +} diff --git a/admin/tool/lp/classes/output/template_competencies_page.php b/admin/tool/lp/classes/output/template_competencies_page.php index 37aecbcdef5..cc7c0123e11 100644 --- a/admin/tool/lp/classes/output/template_competencies_page.php +++ b/admin/tool/lp/classes/output/template_competencies_page.php @@ -33,6 +33,8 @@ use context_system; use moodle_url; use tool_lp\api; use tool_lp\external\competency_summary_exporter; +use tool_lp\template_statistics; +use tool_lp\external\template_statistics_exporter; /** * Class containing data for learning plan template competencies page @@ -60,6 +62,9 @@ class template_competencies_page implements renderable, templatable { /** @var context $pagecontext The page context. */ protected $pagecontext = null; + /** @var template_statistics $templatestatistics The generated summary statistics for this template. */ + protected $templatestatistics = null; + /** * Construct this renderable. * @@ -68,6 +73,7 @@ class template_competencies_page implements renderable, templatable { public function __construct($templateid, context $pagecontext) { $this->pagecontext = $pagecontext; $this->templateid = $templateid; + $this->templatestatistics = new template_statistics($templateid); $this->competencies = api::list_competencies_in_template($templateid); $this->canmanagecompetencyframeworks = has_capability('tool/lp:competencymanage', $this->pagecontext); $this->canmanagetemplatecompetencies = has_capability('tool/lp:templatemanage', $this->pagecontext); @@ -115,6 +121,9 @@ class template_competencies_page implements renderable, templatable { $data->canmanagecompetencyframeworks = $this->canmanagecompetencyframeworks; $data->canmanagetemplatecompetencies = $this->canmanagetemplatecompetencies; $data->manageurl = $this->manageurl->out(true); + $exporter = new template_statistics_exporter($this->templatestatistics); + $data->statistics = $exporter->export($output); + $data->showcompetencylinks = true; return $data; } diff --git a/admin/tool/lp/classes/plan.php b/admin/tool/lp/classes/plan.php index 8b6817cf8a9..63ca1a0eb2b 100644 --- a/admin/tool/lp/classes/plan.php +++ b/admin/tool/lp/classes/plan.php @@ -671,4 +671,36 @@ class plan extends persistent { 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); + } } diff --git a/admin/tool/lp/classes/template_competency.php b/admin/tool/lp/classes/template_competency.php index f3810f6851f..5d603c0a6e5 100644 --- a/admin/tool/lp/classes/template_competency.php +++ b/admin/tool/lp/classes/template_competency.php @@ -138,6 +138,29 @@ class template_competency extends persistent { 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). * diff --git a/admin/tool/lp/classes/template_statistics.php b/admin/tool/lp/classes/template_statistics.php new file mode 100644 index 00000000000..616f35cbde4 --- /dev/null +++ b/admin/tool/lp/classes/template_statistics.php @@ -0,0 +1,76 @@ +. + +/** + * Template statistics class + * + * @package tool_lp + * @copyright 2016 Damyon Wiese + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace tool_lp; +defined('MOODLE_INTERNAL') || die(); + +/** + * Template statistics class. + * + * @package tool_lp + * @copyright 2016 Damyon Wiese + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class template_statistics { + + /** @var $competencycount The number of competencies in the template */ + public $competencycount = 0; + + /** @var $unlinkedcompetencycount The number of unlinked competencies in the template */ + public $unlinkedcompetencycount = 0; + + /** @var $plancount The number of plans for the template */ + public $plancount = 0; + + /** @var $completedplancount The number of completed plans for the template */ + public $completedplancount = 0; + + /** @var $usercompetencyplancount The number of competencies in completed plans for the template */ + public $usercompetencyplancount = 0; + + /** @var $proficientusercompetencyplancount The number of proficient competencies in completed plans for the template */ + public $proficientusercompetencyplancount = 0; + + /** @var $leastproficientcompetencies The competencies in this template that were proficient the least times */ + public $leastproficientcompetencies = array(); + + /** + * Return the custom definition of the properties of this model. + * + * @param int $template The template we want to generate statistics for. + */ + public function __construct($templateid) { + $template = new template($templateid); + $this->competencycount = api::count_competencies_in_template($template); + $this->unlinkedcompetencycount = api::count_competencies_in_template_with_no_courses($template); + + $this->plancount = api::count_plans_for_template($template, 0); + $this->completedplancount = api::count_plans_for_template($template, plan::STATUS_COMPLETE); + + $this->usercompetencyplancount = api::count_user_competency_plans_for_template($template, false); + $this->proficientusercompetencyplancount = api::count_user_competency_plans_for_template($template, true); + + $this->leastproficientcompetencies = api::get_least_proficient_competencies_for_template($template, 0, 3); + } +} diff --git a/admin/tool/lp/classes/user_competency_plan.php b/admin/tool/lp/classes/user_competency_plan.php index 92ded732729..e001232e86d 100644 --- a/admin/tool/lp/classes/user_competency_plan.php +++ b/admin/tool/lp/classes/user_competency_plan.php @@ -280,4 +280,64 @@ class user_competency_plan extends persistent { 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'); + $params = array('templateid' => $templateid, 'notproficient' => false); + $sql = 'SELECT ' . $fields . ', COUNT(c.id) AS timesnotproficient ' . + ' FROM {' . self::TABLE . '} ucp + JOIN {' . plan::TABLE . '} p + ON ucp.planid = p.id + JOIN {' . competency::TABLE . '} c + ON ucp.competencyid = c.id + WHERE p.templateid = :templateid + AND (ucp.proficiency = :notproficient OR ucp.proficiency IS NULL) + GROUP BY c.id + ORDER BY timesnotproficient DESC'; + + $results = $DB->get_records_sql($sql, $params, $skip, $limit); + + $comps = array(); + foreach ($results as $r) { + $c = competency::extract_record($r); + $comps[] = new competency(0, $c); + } + return $comps; + } } diff --git a/admin/tool/lp/lang/en/tool_lp.php b/admin/tool/lp/lang/en/tool_lp.php index ae9b3fc92a5..5f0e0590fac 100644 --- a/admin/tool/lp/lang/en/tool_lp.php +++ b/admin/tool/lp/lang/en/tool_lp.php @@ -38,6 +38,7 @@ $string['allchildrenarecomplete'] = 'All children are complete'; $string['aplanswerecreated'] = '{$a} plans were created.'; $string['aplanswerecreatedmoremayrequiresync'] = '{$a} plans were created, more will be created during the next synchronization.'; $string['assigncohorts'] = 'Assign cohorts'; +$string['averageproficiencyrate'] = 'The average proficiency rate for completed plans based on this template is {$a} %'; $string['cancelreviewrequest'] = 'Cancel review request'; $string['cannotaddrules'] = 'This competency cannot be configured.'; $string['cannotcreateuserplanswhentemplateduedateispassed'] = 'New user plans can not be created: this template\'s due date has, or is about to, expire.'; @@ -47,6 +48,7 @@ $string['cohortssyncedtotemplate'] = 'Cohorts synced to this template'; $string['competencies'] = 'Competencies'; $string['competenciesarenotenabled'] = 'The competencies module is not enabled.'; $string['competenciesforframework'] = 'Competencies for {$a}'; +$string['competenciesmostoftennotproficient'] = 'Competencies most often not proficient in completed plans'; $string['competenciessettings'] = 'Competencies settings'; $string['competencycannotbedeleted'] = 'The competency \'{$a}\' can not be deleted'; $string['competencycreated'] = 'Competency created'; @@ -418,3 +420,5 @@ $string['viewdetails'] = 'View details'; $string['visible'] = 'Visible'; $string['visible_help'] = 'A competency framework can be hidden from teachers. This could be useful if a framework is still in the process of being developed.'; $string['when'] = 'When'; +$string['xcompetencieslinkedoutofy'] = '{$a->x} out of {$a->y} competencies linked to courses'; +$string['xplanscompletedoutofy'] = '{$a->x} out of {$a->y} plans completed for this template'; diff --git a/admin/tool/lp/styles.css b/admin/tool/lp/styles.css index 6e6f6ebde38..6530232968b 100644 --- a/admin/tool/lp/styles.css +++ b/admin/tool/lp/styles.css @@ -4,7 +4,17 @@ .path-admin-tool-lp [data-region="competencylinktree"] ul li { list-style-type: none; } - +.path-admin-tool-lp [data-region="templatestatistics"] .progresstext { + display: inline-block; + height: 40px; + vertical-align: top; +} +.path-admin-tool-lp [data-region="templatestatistics"] .progress { + width: 10em; + display: inline-block; + margin-left: 2em; + margin-right: 2em; +} .path-admin-tool-lp [data-region="managecompetencies"] ul[data-enhance="tree"], .path-admin-tool-lp [data-region="plans"] ul[data-enhance="tree"], .path-admin-tool-lp [data-region="competencylinktree"] ul[data-enhance="linktree"], diff --git a/admin/tool/lp/templates/template_competencies_page.mustache b/admin/tool/lp/templates/template_competencies_page.mustache index f6807dcd233..c974328963d 100644 --- a/admin/tool/lp/templates/template_competencies_page.mustache +++ b/admin/tool/lp/templates/template_competencies_page.mustache @@ -18,6 +18,21 @@ Template competencies template. }}
+ +
+ {{/canmanagetemplatecompetencies}} +{{#str}}competenciesmostoftennotproficient, tool_lp{{/str}}
+{{shortname}} {{idnumber}}