General developer forum

calling a webservice function (in externallib) internally

Picture of tim st.clair
Re: calling a webservice function (in externallib) internally
Plugin developers

The problem seems to be that I'm calling this from within a block. Not a local plugin or a functional test harness.

I want my block to work for admins on the dashboard page. I took the code from the test_duplicate_course() code an dropped it into my block, which is on the /my/ page. I'm passing in the id of a course that exists for it to clone.

< ? php

global $CFG;

require_once($CFG----->dirroot . '/course/externallib.php');

class block_foo_admin extends block_base {

	// ... block init, instance_allow_multiple, etc ...

	public function get_content() {
		global $DB;

		if ($this->content !== null) {
			return $this->content;

		$this->content = new stdClass;
		$this->content->text = "";

		if (!is_siteadmin()) return null;

		// good enough for testing
        $param_action = optional_param('action', 'view', PARAM_ALPHANUM);
        $param_courseid = optional_param('id', 0, PARAM_INT); 
        $param_identifier = optional_param('identifier', '', PARAM_ALPHANUM);

        switch ($param_action) {
        	case "clone":
				$curr = $DB->get_record("course", ["id"=>$param_courseid]);
				$newname = urlencode(strftime('%Y', time()) . ' ' . $curr->fullname);

		        $newcourse['fullname'] = $newname;
		        $newcourse['shortname'] = $newname;
		        $newcourse['categoryid'] = $curr->category;
		        $newcourse['visible'] = 0;
		        $newcourse['options'][] = array('name' => 'users', 'value' => true);

		        $clone = core_course_external::duplicate_course($curr->id, $newcourse['fullname'],
		                $newcourse['shortname'], $newcourse['categoryid'], $newcourse['visible'], $newcourse['options']);

		// ... rest of the block ...

yields this error

		        Coding error detected, it must be fixed by a programmer: Cannot call moodle_page::add_body_class after output has been started.
Debug info: 
Error code: codingerror
Stack trace:
line 1141 of /lib/pagelib.php: coding_exception thrown
line 963 of /lib/pagelib.php: call to moodle_page->add_body_class()
line 2643 of /lib/moodlelib.php: call to moodle_page->set_course()
line 480 of /lib/externallib.php: call to require_login()
line 1099 of /course/externallib.php: call to external_api::validate_context()
line 70 of /blocks/foo_admin/block_foo_admin.php: call to core_course_external::duplicate_course()
line 288 of /blocks/moodleblock.class.php: call to block_foo_admin->get_content()
line 230 of /blocks/moodleblock.class.php: call to block_base->formatted_contents()
line 976 of /lib/blocklib.php: call to block_base->get_content_for_output()
line 1028 of /lib/blocklib.php: call to block_manager->create_block_contents()
line 476 of /lib/outputrenderers.php: call to block_manager->ensure_content_created()
line 39 of /theme/bootstrapbase/renderers/core_renderer.php: call to core_renderer->standard_head_html()
line 46 of /theme/gourmet/layout/admin.php: call to theme_bootstrapbase_core_renderer->standard_head_html()
line 1028 of /lib/outputrenderers.php: call to include()
line 958 of /lib/outputrenderers.php: call to core_renderer->render_page_layout()
line 164 of /my/index.php: call to core_renderer->header()
Stepping through, it's having an issue when doing external_api::validate_context() because, i believe, the context is reset inside that.

So validate_context() is passed the course. This performs require_login(). This checks that you're an admin and performs a $PAGE->set_course($course) which eventually performs add_body_class('format-'. $courseformat->get_format()) when it realises you have a course set. This throws the error, because block rendering happens after the page is begun. 

So I could re-do the plugin as local plugin then have my admin "block" on the dashboard just have a button you have to press that redirects you to the local plugin page or something. Ugh.UGHHH.

When you call the same using an external web service, e.g.

$token = $DB->get_record_select("external_tokens", "externalserviceid in (select externalserviceid from {external_services_functions} where functionname = 'core_course_duplicate_course') and userid in (select value from {config} where name = 'siteadmins')",null,'token',MUST_EXIST)->token; // premade admin user token
$url = $CFG->wwwroot . "/webservice/rest/server.php?wstoken=$token&wsfunction=core_course_duplicate_course&moodlewsrestformat=json";
$params = "courseid={$curr->id}&fullname={$newname}&shortname={$newname}&categoryid={$curr->category}&visible=0&options[0][name]=users&options[0][value]=1";
$resp = (new curl)->post($url, $params);
$clone = json_decode($resp);

then the process works because you're not in the middle of a page render: but this seems pretty janky when those function calls are right there ... 

Average of ratings: -