简体   繁体   中英

How to debug custom plugin in Moodle

I've been asked to create a custom plugin displays course overview (course id, course name, enrolled and completed) through API. There is a report_completionoverview plugin that I can refer to and basically wanna retrieve exactly the same list via Moodle API in JSON format.

在此处输入图像描述

I'm trying to create a local plugin based on moodle documentation ( https://docs.moodle.org/dev/Adding_a_web_service_to_a_plugin ) and other default plugins, but having difficulty to debug.

* modified folder name to match plugin name *

I've Created

local/get_completion_overview/db/service.php

local/get_completion_overview/lang/en/local_get_completion_overview.php

local/get_completion_overview/externallib.php

local/get_completion_overview/version.php

Successfully installed the plugin without error in Moodle, but the plugin is not listed in the function.

插件已安装

在此处输入图像描述

I honestly think that my code is not right (and it is messy since I've copied from different sources), but don't know how to debug it.

Can anyone please let me know if you know how?

I also attach local/completionview/externallib.php (I'm sure this is causing the issue I believe). Any help or idea or comment would be appreciated. Thanks a lot!

<?php
require_once($CFG->libdir . "/externallib.php");
require_once("lib.php");

class local_get_completion_overview_external extends external_api {

    public static function get_completion_overview_parameters() {
        return new external_function_parameters(
            array(
                'field' => new external_value(PARAM_ALPHA, 'The field to search can be left empty for all courses or:
                    id: course id
                    ids: comma separated course ids
                    shortname: course short name
                    idnumber: course id number
                    category: category id the course belongs to
                ', VALUE_DEFAULT, ''),
                'value' => new external_value(PARAM_RAW, 'The value to match', VALUE_DEFAULT, '')
            )
        );
    }

    public static function get_completion_overview($field = '', $value = ''){
        global $CFG, $DB;
        require_once($CFG->dirroot . '/course/lib.php');
        require_once($CFG->libdir . '/filterlib.php');

        $params = self::validate_parameters(self::get_completion_overview_parameters(),
            array(
                'field' => $field,
                'value' => $value,
            )
        );

        $sql = "SELECT DISTINCT cr.id AS courseid, cr.fullname AS coursename,
        COUNT(DISTINCT ra.id ) AS enrols,
        COUNT(DISTINCT cc.timecompleted) AS completed
        FROM {course} cr
        JOIN {context} ct ON ( ct.instanceid = cr.id )
        LEFT JOIN {role_assignments} ra ON ( ra.contextid = ct.id ) and ra.roleid = 5
        LEFT JOIN {course_completions} cc ON cc.course = cr.id
        GROUP BY  cr.fullname, cr.id
        ORDER BY coursename";

        $warnings = array();

        if (empty($params['field'])) {
            $courses = $DB->get_records_sql($sql, array());
        } else {
            switch ($params['field']) {
                case 'id':
                case 'category':
                    $value = clean_param($params['value'], PARAM_INT);
                    break;
                case 'ids':
                    $value = clean_param($params['value'], PARAM_SEQUENCE);
                    break;
                case 'shortname':
                    $value = clean_param($params['value'], PARAM_TEXT);
                    break;
                case 'idnumber':
                    $value = clean_param($params['value'], PARAM_RAW);
                    break;
                default:
                    throw new invalid_parameter_exception('Invalid field name');
            }

            if ($params['field'] === 'ids') {
                $courses = $DB->get_records_list('course', 'id', explode(',', $value), 'id ASC');
            } else {
                $courses = $DB->get_records('course', array($params['field'] => $value), 'id ASC');
            }
        }

        if(!empty($courses)){

            $coursesdata = array();
            $currentcourseid = null;
            $course = null;

            foreach($courses as $completion) {
                $context = context_course::instance($course->id);
                $crs = array();
                $crs['courseid'] = $completion->courseid;
                $crs['coursename'] = (string)$completion->coursename;
                $crs['enrols'] = $completion->enrols;
                $crs['completed'] = $completion->completed;

                try {
                    self::validate_context($context);
                } catch (Exception $e) {
                    continue;
                }

                if(is_null($currentcourseid) || ($completion->courseid != $currentcourseid)) {
                    if(!is_null($course)) {
                        $coursesdata[] = $course;
                    }
                    $course = array();
                    $course['courseid'] = $completion->courseid;
                }

                $course['courseid'][] = $crs;

                $currentcourseid = $completion->courseid;
            }

            if(!is_null($course)){
                $coursesdata[] = $course;
            }

            $courses->close();
        }

        $result = array();
        $result['course'] = $coursesdata;

        return $result;
    }

    public static function get_completion_overview_returns() {
        return new external_single_structure(
            array(
                'course' => new external_multiple_structure(self::get_completion_overview(), 'list of courses completion')
            )
            );
    }
}

** service.php

<?php

$functions = array(
    'local_get_completion_overview' =>
        array('classname'   => 'local_get_completion_overview_external',
            'methodname'  => 'get_completion_overview',
            'classpath'   => 'local/get_completion_overview/externallib.php',
            'description' => 'Get course completion overview',
            'type'        => 'read',
            'capabilities'=> array(), //optional, useful to let the administrator know what potential capabilities the user 'could' need
            'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
        ),
    );

$services = array(
    'get completion overview' => array(                                                //the name of the web service
        'functions' => array ('local_get_completion_overview'), //web service functions of this service
        'requiredcapability' => '',                //if set, the web service user need this capability to access 
                                                                            //any function of this service. For example: 'some/capability:specified'                 
        'restrictedusers' =>0,                                             //if enabled, the Moodle administrator must link some user to this service
                                                                            //into the administration
        'enabled'=>1,                                                       //if enabled, the service can be reachable on a default installation
        )
);

service.php should be services.php. After fixing the filename, it appears in Moodle as function, however having an issue to load the function.

Undefined property: stdClass::$id in /Users/lucy/Sites/moodle/local/get_completion_overview/externallib.php on line 84

which is

$context = context_course::instance($completion->id);

in foreach block.

also,

Debug info: SELECT id,category FROM {course} WHERE id IS NULL [array ( )] Error code: invalidrecord

 Stack trace: line 1562 of /lib/dml/moodle_database.php: dml_missing_record_exception thrown line 1538 of /lib/dml/moodle_database.php: call to moodle_database->get_record_select() line 6822 of /lib/accesslib.php: call to moodle_database->get_record() line 84 of /local/get_completion_overview/externallib.php: call to context_course::instance() line 138 of /local/get_completion_overview/externallib.php: call to local_get_completion_overview_external::get_completion_overview() line 124 of /lib/externallib.php: call to local_get_completion_overview_external::get_completion_overview_returns() line 219 of /webservice/renderer.php: call to external_api::external_function_info() line 121 of /admin/webservice/service_functions.php: call to core_webservice_renderer->admin_service_function_list()

Increment version number within version.php file. It will trigger moodle for update. Then you should do few updates in moodle. After that you shoudl see the listing

This works as expected. I've changed the permission to has_capability('moodle/site:config', $context); I'm posting my solution in case someone runs into the same issue.

<?php
require_once('../../config.php');
require_once($CFG->libdir . "/externallib.php");
// require_once('../lib/externallib.php');
// require_once("lib.php");

class local_get_completion_overview_external extends external_api {

    public static function get_completion_overview_parameters() {
        return new external_function_parameters(
            array(
                'field' => new external_value(PARAM_ALPHA, 'The field to search can be left empty for all courses or:
                    id: course id', VALUE_DEFAULT, ''),
                'value' => new external_value(PARAM_RAW, 'The value to match', VALUE_DEFAULT, '')
            )
        );
    }

    public static function get_completion_overview($field='', $value=''){
        global $CFG, $DB;
        require_once($CFG->dirroot . '/course/lib.php');

        $params = self::validate_parameters(self::get_completion_overview_parameters(),
            array(
                'field' => $field,
                'value' => $value,
            )
        );

        $sql = "SELECT DISTINCT cr.id AS courseid,
                cr.fullname AS coursename,
                COUNT(DISTINCT ra.id ) AS enrols,
                COUNT(DISTINCT cc.timecompleted) AS completed
                FROM {course} cr
                JOIN {context} ct ON ( ct.instanceid = cr.id )
                LEFT JOIN {role_assignments} ra ON ( ra.contextid = ct.id ) and ra.roleid = 5
                LEFT JOIN {course_completions} cc ON cc.course = cr.id
                GROUP BY  cr.fullname, cr.id
                ORDER BY coursename";

        $warnings = array();
        $coursesdata = array();
        $requestedcourseids = $params['value'];

        if (empty($params['field'])) {
            $courses = $DB->get_records_sql($sql, array());
        } else {
            $value = clean_param($params['id'], PARAM_INT);

            if (count($value) > 0) {
                $placeholders = array();

                $sql_2 = "SELECT DISTINCT cr.id AS courseid, 
                            cr.fullname AS coursename, 
                            COUNT(DISTINCT ra.id) AS enrols, 
                            COUNT(DISTINCT cc.timecompleted) AS completed 
                            FROM {course} cr JOIN {context} ct ON ( ct.instanceid = cr.id ) 
                            LEFT JOIN {role_assignments} ra ON ( ra.contextid = ct.id ) and ra.roleid = 5 
                            LEFT JOIN {course_completions} cc ON (cc.course = cr.id) 
                            WHERE cr.id = ".$requestedcourseids." GROUP BY cr.fullname, cr.id";

                $courses = $DB->get_records_sql($sql_2, $placeholders);
            }
        }

        if(!empty($courses)) {
            $currentcourseid = null;
            $course = null;

            foreach($courses as $completion) {
                $context = context_system::instance();
                has_capability('moodle/site:config', $context);

                if(is_null($currentcourseid) || ($completion->courseid != $currentcourseid)) {
                    if(!is_null($course)) {
                        $coursesdata[] = $course;
                    }
                    $course = array();
                    $course['courseid'] = $completion->courseid;
                    $course['coursename'] = $completion->coursename;
                    $course['enrols'] = $completion->enrols;
                    $course['completed'] = $completion->completed;
                    $course['totalcourses'] = count($course);

                }

                $currentcourseid = $completion->courseid;
            }

            if(!is_null($course)){
                $coursesdata[] = $course;
            }

        } else {
            $warning = array();
            $warning['item'] = 'course';
            $warning['itemid'] = $requestedcourseids;
            $warning['warningcode'] = '1';
            $warning['message'] = 'No course found';

            $warnings[] = $warning;
        }

        $result['course'] = $coursesdata;
        $result['warnings'] = $warnings;

        return $result;
    }

    public static function get_completion_overview_returns() {
        return new external_single_structure(
            array(
                'course' => new external_multiple_structure(
                    new external_single_structure(
                        array(
                            'courseid' => new external_value(PARAM_INT, 'description'),
                            'coursename' => new external_value(PARAM_TEXT, ''),
                            'enrols' => new external_value(PARAM_INT, '', VALUE_OPTIONAL),
                            'completed' => new external_value(PARAM_INT, '', VALUE_OPTIONAL),
                            'totalcourses' => new external_value(PARAM_INT, '', VALUE_OPTIONAL),
                        )
                    )
                ),
                'warnings' => new external_warnings()
            )
        );
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM