416 lines
		
	
	
		
			No EOL
		
	
	
		
			15 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			416 lines
		
	
	
		
			No EOL
		
	
	
		
			15 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?php
 | 
						|
// This file is part of the Studyplan plugin for Moodle
 | 
						|
//
 | 
						|
// 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 <https://www.gnu.org/licenses/>.
 | 
						|
/**
 | 
						|
 *
 | 
						|
 * @package    local_treestudyplan
 | 
						|
 * @copyright  2023 P.M. Kuipers
 | 
						|
 * @license    https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 | 
						|
 */
 | 
						|
 | 
						|
namespace local_treestudyplan;
 | 
						|
require_once($CFG->libdir.'/externallib.php');
 | 
						|
 | 
						|
use \local_treestudyplan\courseinfo;
 | 
						|
use \local_treestudyplan\associationservice;
 | 
						|
use \local_treestudyplan\local\helpers\webservicehelper;
 | 
						|
use \local_treestudyplan\completionscanner;
 | 
						|
use \local_treestudyplan\gradingscanner;
 | 
						|
 | 
						|
class courseservice extends \external_api
 | 
						|
{
 | 
						|
    const CAP_EDIT = "local/treestudyplan:editstudyplan";
 | 
						|
    const CAP_VIEW = "local/treestudyplan:viewuserreports";
 | 
						|
 | 
						|
    /************************
 | 
						|
     *                      *
 | 
						|
     * list_courses         *
 | 
						|
     *                      *
 | 
						|
     ************************/
 | 
						|
 | 
						|
    public static function map_categories_parameters()
 | 
						|
	{
 | 
						|
        return new \external_function_parameters( [
 | 
						|
            "root_id" => new \external_value(PARAM_INT, 'root category to use as base', VALUE_DEFAULT),
 | 
						|
         ] );
 | 
						|
    }
 | 
						|
 | 
						|
	public static function map_categories_returns()
 | 
						|
	{
 | 
						|
        return new \external_multiple_structure(static::map_category_structure(false));
 | 
						|
    }
 | 
						|
 | 
						|
    protected static function map_category_structure($lazy=false, $value=VALUE_REQUIRED) {
 | 
						|
        $s = [
 | 
						|
            "id" => new \external_value(PARAM_INT, 'course category id'),
 | 
						|
            "context_id" => new \external_value(PARAM_INT, 'course category context id'),
 | 
						|
            "category" =>  contextinfo::structure(VALUE_OPTIONAL),
 | 
						|
            "haschildren" => new \external_value(PARAM_BOOL, 'True if the category has child categories'),
 | 
						|
            "hascourses" => new \external_value(PARAM_BOOL, 'True if the category contains courses'),
 | 
						|
            "studyplancount" =>  new \external_value(PARAM_INT, 'number of linked studyplans', VALUE_OPTIONAL),
 | 
						|
        ];
 | 
						|
 | 
						|
        if (!$lazy > 0) {
 | 
						|
            $s["courses"] = new \external_multiple_structure( courseinfo::editor_structure() );
 | 
						|
            $s["children"] = new \external_multiple_structure( static::map_category_structure(true));
 | 
						|
        }
 | 
						|
        return  new \external_single_structure($s, "CourseCat info", $value);
 | 
						|
    }
 | 
						|
 | 
						|
    public static function map_categories($root_id = 0) {
 | 
						|
        global $CFG, $DB;
 | 
						|
 | 
						|
        $root = \core_course_category::get($root_id);
 | 
						|
        $context = $root->get_context();
 | 
						|
        // Make sure the user has access to the context for editing purposes.
 | 
						|
        webservicehelper::require_capabilities(self::CAP_EDIT, $context);
 | 
						|
 | 
						|
        // Determine top categories from provided context.
 | 
						|
 | 
						|
        if ($root->id == 0) {
 | 
						|
            // on the system level, determine the user's topmost allowed catecories.
 | 
						|
            $user_top = \core_course_category::user_top();
 | 
						|
            if ($user_top->id == 0) { // top category..
 | 
						|
                $children = $root->get_children(); // returns a list of çore_course_category, let it overwrite $children.
 | 
						|
            } else {
 | 
						|
                $children = [$user_top];
 | 
						|
            }
 | 
						|
        } else if ($root->is_uservisible()) {
 | 
						|
            $children = [$root];
 | 
						|
        }
 | 
						|
 | 
						|
        foreach ($children as $cat) {
 | 
						|
            $list[] = static::map_category($cat, false);
 | 
						|
        }
 | 
						|
        return $list;
 | 
						|
    }
 | 
						|
 | 
						|
    public static function get_category_parameters()
 | 
						|
	{
 | 
						|
        return new \external_function_parameters( [
 | 
						|
            "id" => new \external_value(PARAM_INT, 'id of category'),
 | 
						|
        ] );
 | 
						|
    }
 | 
						|
 | 
						|
	public static function get_category_returns()
 | 
						|
	{
 | 
						|
        return static::map_category_structure(false);
 | 
						|
    }
 | 
						|
 | 
						|
    public static function get_category($id) {
 | 
						|
        $cat = \core_course_category::get($id);
 | 
						|
        return static::map_category($cat);
 | 
						|
    }
 | 
						|
 | 
						|
    protected static function map_category(\core_course_category $cat, $lazy=false) {
 | 
						|
        global $DB;
 | 
						|
        $catcontext = $cat->get_context();
 | 
						|
        $ctx_info = new contextinfo($catcontext);
 | 
						|
        $children = $cat->get_children(); // only shows children visible to the current user.
 | 
						|
        $courses = $cat->get_courses();
 | 
						|
        $model = [
 | 
						|
            "id" => $cat->id,
 | 
						|
            "context_id" => $catcontext->id,
 | 
						|
            "category" => $ctx_info->model(),
 | 
						|
            "haschildren" => !empty($children),
 | 
						|
            "hascourses" => !empty($courses),
 | 
						|
        ];
 | 
						|
 | 
						|
        if (!$lazy) {
 | 
						|
            $model["courses"] = [];
 | 
						|
            foreach ($courses as $course) {
 | 
						|
                $courseinfo = new courseinfo($course->id);
 | 
						|
                $model["courses"][] = $courseinfo->editor_model();
 | 
						|
            }
 | 
						|
 | 
						|
            $model["children"] = [];
 | 
						|
            foreach ($children as $child) {
 | 
						|
                $model["children"][] = static::map_category($child, true);
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        return $model;
 | 
						|
    }
 | 
						|
 | 
						|
    public static function list_accessible_categories_parameters()
 | 
						|
	{
 | 
						|
        return new \external_function_parameters( [
 | 
						|
            "operation" => new \external_value(PARAM_TEXT, 'type of operation ["view"|"edit"]', VALUE_DEFAULT), ]
 | 
						|
        );
 | 
						|
    }
 | 
						|
 | 
						|
	public static function list_accessible_categories_returns()
 | 
						|
	{
 | 
						|
        return new \external_multiple_structure(static::map_category_structure(true));
 | 
						|
    }
 | 
						|
 | 
						|
    public static function list_accessible_categories($operation="edit")
 | 
						|
	{
 | 
						|
        if ($operation == "edit") {
 | 
						|
            $capability = self::CAP_EDIT;
 | 
						|
        } else { // $operation == "view" || default.
 | 
						|
            $capability = self::CAP_VIEW;
 | 
						|
        }
 | 
						|
 | 
						|
        $cats = static::categories_by_capability($capability);
 | 
						|
 | 
						|
        $list = [];
 | 
						|
        /* @var $cat \core_course_category */
 | 
						|
        foreach ($cats as $cat) {
 | 
						|
            $list[] = static::map_category($cat, true);
 | 
						|
        }
 | 
						|
        return $list;
 | 
						|
 | 
						|
    }
 | 
						|
 | 
						|
    public static function categories_by_capability($capability, \core_course_category $parent=null) {
 | 
						|
        // List the categories in which the user has a specific capability.
 | 
						|
        $list = [];
 | 
						|
        // initialize parent if needed.
 | 
						|
        if ($parent == null) {
 | 
						|
            $parent = \core_course_category::user_top();
 | 
						|
            if (has_capability($capability, $parent->get_context())) {
 | 
						|
                $list[] = $parent;
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        $children = $parent->get_children();
 | 
						|
        foreach ($children as $child) {
 | 
						|
            // Check if we should add this category.
 | 
						|
            if (has_capability($capability, $child->get_context())) {
 | 
						|
                $list[] = $child;
 | 
						|
                // For optimization purposes, we include all its children now, since they will have inherited the permission.
 | 
						|
                // #PREMATURE_OPTIMIZATION  ???.
 | 
						|
                $list = array_merge($list, self::recursive_child_categories($child));
 | 
						|
            } else {
 | 
						|
                if ($child->get_children_count() > 0) {
 | 
						|
                    $list = array_merge($list, self::categories_by_capability($capability, $child));
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        return $list;
 | 
						|
    }
 | 
						|
 | 
						|
    protected static function recursive_child_categories(\core_course_category $parent) {
 | 
						|
        $list = [];
 | 
						|
        $children = $parent->get_children();
 | 
						|
        foreach ($children as $child) {
 | 
						|
            $list[] = $child;
 | 
						|
            if ($child->get_children_count() > 0) {
 | 
						|
                $list = array_merge($list, self::recursive_child_categories($child));
 | 
						|
            }
 | 
						|
        }
 | 
						|
        return $list;
 | 
						|
    }
 | 
						|
 | 
						|
    public static function list_used_categories_parameters()
 | 
						|
	{
 | 
						|
        return new \external_function_parameters( [
 | 
						|
            "operation" => new \external_value(PARAM_TEXT, 'type of operation ["view"|"edit"]', VALUE_DEFAULT),
 | 
						|
        ]);
 | 
						|
    }
 | 
						|
 | 
						|
	public static function list_used_categories_returns()
 | 
						|
	{
 | 
						|
        return new \external_multiple_structure(static::map_category_structure(true));
 | 
						|
    }
 | 
						|
 | 
						|
    public static function list_used_categories($operation='edit')
 | 
						|
	{
 | 
						|
        global $DB;
 | 
						|
        if ($operation == "edit") {
 | 
						|
            $capability = self::CAP_EDIT;
 | 
						|
        } else { // $operation == "view" || default.
 | 
						|
            $capability = self::CAP_VIEW;
 | 
						|
        }
 | 
						|
        $context_ids = [];
 | 
						|
        $rs = $DB->get_recordset_sql("SELECT DISTINCT context_id, COUNT(*) as num FROM {local_treestudyplan}
 | 
						|
                                         GROUP BY context_id");
 | 
						|
        foreach ($rs as $r) {
 | 
						|
            $context_ids[$r->context_id] = $r->num;
 | 
						|
        }
 | 
						|
        $rs->close();
 | 
						|
 | 
						|
 | 
						|
        // Now filter the categories that the user has acces to by the used context id's.
 | 
						|
        // (That should filter out irrelevant stuff).
 | 
						|
        $cats = static::categories_by_capability($capability);
 | 
						|
 | 
						|
        $list = [];
 | 
						|
        foreach ($cats as $cat) {
 | 
						|
            $count = 0;
 | 
						|
            $ctxid = $cat->get_context()->id;
 | 
						|
            if (array_key_exists($ctxid, $context_ids)) {
 | 
						|
                $count = $context_ids[$ctxid];
 | 
						|
            }
 | 
						|
 | 
						|
            $o = static::map_category($cat, true);
 | 
						|
            $o["studyplancount"] = $count;
 | 
						|
            $list[] = $o;
 | 
						|
        }
 | 
						|
        return $list;
 | 
						|
    }
 | 
						|
 | 
						|
    public static function list_accessible_categories_with_usage($operation='edit') {
 | 
						|
        global $DB;
 | 
						|
        if ($operation == "edit") {
 | 
						|
            $capability = self::CAP_EDIT;
 | 
						|
        } else { // $operation == "view" || default.
 | 
						|
            $capability = self::CAP_VIEW;
 | 
						|
        }
 | 
						|
        // retrieve context ids used.
 | 
						|
        $context_ids = [];
 | 
						|
        $rs = $DB->get_recordset_sql("SELECT DISTINCT context_id, COUNT(*) as num FROM {local_treestudyplan}
 | 
						|
                                         GROUP BY context_id");
 | 
						|
        foreach ($rs as $r) {
 | 
						|
            $context_ids[$r->context_id] = $r->num;
 | 
						|
        }
 | 
						|
        $rs->close();
 | 
						|
 | 
						|
        // Now filter the categories that the user has acces to by the used context id's.
 | 
						|
        // (That should filter out irrelevant stuff).
 | 
						|
        $cats = static::categories_by_capability($capability);
 | 
						|
 | 
						|
        $list = [];
 | 
						|
        foreach ($cats as $cat) {
 | 
						|
            $count = 0;
 | 
						|
            $ctxid = $cat->get_context()->id;
 | 
						|
            if (array_key_exists($ctxid, $context_ids)) {
 | 
						|
                $count = $context_ids[$ctxid];
 | 
						|
            }
 | 
						|
            $o = new \stdClass();
 | 
						|
            $o->cat = $cat;
 | 
						|
            $o->ctxid = $ctxid;
 | 
						|
            $o->count = $count;
 | 
						|
            $list[] = $o;
 | 
						|
        }
 | 
						|
        return $list;
 | 
						|
    }
 | 
						|
 | 
						|
 | 
						|
    /**************************************
 | 
						|
     *
 | 
						|
     * Progress scanners for teacherview
 | 
						|
     *
 | 
						|
     **************************************/
 | 
						|
 | 
						|
    public static function scan_grade_progress_parameters() {
 | 
						|
        return new \external_function_parameters( [
 | 
						|
            "gradeitemid" => new \external_value(PARAM_INT, 'Grade item ID to scan progress for', VALUE_DEFAULT),
 | 
						|
            "studyplanid" => new \external_value(PARAM_INT, 'Study plan id to check progress in', VALUE_DEFAULT),
 | 
						|
 | 
						|
        ]);
 | 
						|
    }
 | 
						|
 | 
						|
    public static function scan_grade_progress_returns() {
 | 
						|
    return gradingscanner::structure(VALUE_REQUIRED);
 | 
						|
    }
 | 
						|
 | 
						|
    public static function scan_grade_progress($gradeitemid, $studyplanid) {
 | 
						|
        global $DB;
 | 
						|
        // Verify access to the study plan.
 | 
						|
        $o = studyplan::findById($studyplanid);
 | 
						|
        webservicehelper::require_capabilities(self::CAP_VIEW, $o->context());
 | 
						|
 | 
						|
        // Retrieve grade item.
 | 
						|
        $gi = \grade_item::fetch(["id" => $gradeitemid]);
 | 
						|
 | 
						|
        // Validate course is linked to studyplan.
 | 
						|
        $courseid = $gi->courseid;
 | 
						|
        if (!$o->course_linked($courseid)) {
 | 
						|
            throw new \webservice_access_exception("Course {$courseid} linked to grade item {$gradeitemid} is not linked to studyplan {$o->id()}");
 | 
						|
        }
 | 
						|
 | 
						|
        $scanner = new gradingscanner($gi);
 | 
						|
        return $scanner->model();
 | 
						|
    }
 | 
						|
 | 
						|
 | 
						|
    public static function scan_completion_progress_parameters()
 | 
						|
	{
 | 
						|
        return new \external_function_parameters( [
 | 
						|
            "criteriaid" => new \external_value(PARAM_INT, 'CriteriaID to scan progress for', VALUE_DEFAULT),
 | 
						|
            "studyplanid" => new \external_value(PARAM_INT, 'Study plan id to check progress in', VALUE_DEFAULT),
 | 
						|
            "courseid" => new \external_value(PARAM_INT, 'Course id of criteria', VALUE_DEFAULT),
 | 
						|
        ]);
 | 
						|
    }
 | 
						|
 | 
						|
	public static function scan_completion_progress_returns()
 | 
						|
	{
 | 
						|
        return completionscanner::structure(VALUE_REQUIRED);
 | 
						|
    }
 | 
						|
 | 
						|
    public static function scan_completion_progress($criteriaid, $studyplanid, $courseid)
 | 
						|
	{
 | 
						|
        global $DB;
 | 
						|
        // Verify access to the study plan.
 | 
						|
        $o = studyplan::findById($studyplanid);
 | 
						|
        webservicehelper::require_capabilities(self::CAP_VIEW, $o->context());
 | 
						|
 | 
						|
        $crit = \completion_criteria::fetch(["id" => $criteriaid]);
 | 
						|
        if (!$o->course_linked($courseid)) {
 | 
						|
            throw new \webservice_access_exception("Course {$courseid} linked to criteria {$criteriaid} is not linked to studyplan {$o->id()}");
 | 
						|
        }
 | 
						|
        $scanner = new completionscanner($crit, \get_course($courseid));
 | 
						|
        return $scanner->model();
 | 
						|
    }
 | 
						|
 | 
						|
    public static function scan_badge_progress_parameters()
 | 
						|
	{
 | 
						|
        return new \external_function_parameters( [
 | 
						|
            "badgeid" => new \external_value(PARAM_INT, 'Badge to scan progress for', VALUE_DEFAULT),
 | 
						|
            "studyplanid" => new \external_value(PARAM_INT, 'Study plan id to limit progress search to (to determine which students to scan)', VALUE_DEFAULT),
 | 
						|
        ]);
 | 
						|
    }
 | 
						|
 | 
						|
	public static function scan_badge_progress_returns()
 | 
						|
	{
 | 
						|
        return new \external_single_structure([
 | 
						|
            "total" => new \external_value(PARAM_INT, 'Total number of students scanned'),
 | 
						|
            "issued" => new \external_value(PARAM_INT, 'Number of issued badges'),
 | 
						|
        ]);
 | 
						|
    }
 | 
						|
 | 
						|
    public static function scan_badge_progress($badgeid, $studyplanid)
 | 
						|
	{
 | 
						|
        global $DB;
 | 
						|
        // Check access to the study plan.
 | 
						|
        $o = studyplan::findById($studyplanid);
 | 
						|
        webservicehelper::require_capabilities(self::CAP_VIEW, $o->context());
 | 
						|
 | 
						|
        // Validate that badge is linked to studyplan.
 | 
						|
        if (!$o->badge_linked($badgeid)) {
 | 
						|
            throw new \webservice_access_exception("Badge {$badgeid} is not linked to studyplan {$o->id()}");
 | 
						|
        }
 | 
						|
 | 
						|
        // Get badge info.
 | 
						|
        $badge = new \core_badges\badge($badgeid);
 | 
						|
        $badgeinfo = new badgeinfo($badge);
 | 
						|
 | 
						|
        // get the connected users.
 | 
						|
        $students = associationservice::all_associated($studyplanid);
 | 
						|
        // Just get the user ids.
 | 
						|
        $studentids = array_map(function ($a) { return $a["id"];}, $students);
 | 
						|
 | 
						|
        return [
 | 
						|
            "total" => count($studentids),
 | 
						|
            "issued" => $badgeinfo->count_issued($studentids),
 | 
						|
        ];
 | 
						|
 | 
						|
    }
 | 
						|
 | 
						|
 | 
						|
} |