Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 2 additions & 7 deletions classes/helper/datafield_manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -162,13 +162,8 @@ public function render_datafields($templatestr, $event = null, $stepresults = nu

return $value;
} else {
// If there is no value, return the empty value.
if (preg_match('/\{.*?\}/', $matches[0])) {
return '';
} else {
// No match! Leave the template string in place.
return $matches[0];
}
// No match! Leave the template string in place.
return $matches[0];
}
};

Expand Down
134 changes: 10 additions & 124 deletions classes/steps/actions/webservice_action_step.php
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ private function run_function() {
$params = $this->render_datafields($this->params);

// Execute the provided function name passing with the given parameters.
$response = self::call_external_function($functionname, json_decode($params, true));
$response = external_api::call_external_function($functionname, json_decode($params, true));
return $response;
}

Expand Down Expand Up @@ -150,12 +150,11 @@ public function execute($step, $trigger, $event, $stepresults) {
try {
$response = $this->run_function();
if ($response['error']) {

// Throw an error if step results are not being returned.
$stepresults['error'] = json_encode($response['exception']);

return [false, $stepresults];
// Throw an exception to be propagated for proper error capture.
throw new \coding_exception(json_encode($response['exception']));
}

$status = [true, $response];
} catch (\Throwable $e) {
// Restore the previous user to avoid any side-effects occuring in later steps / code.
\core\session\manager::set_user($previoususer);
Expand All @@ -175,8 +174,8 @@ public function execute($step, $trigger, $event, $stepresults) {
\core\session\manager::set_user($previoususer);
$SESSION = $session;

// Return stepresults.
return [true, $stepresults];
// Return the function call response as is. The shape is already normalised.
return $status;
}

/**
Expand All @@ -202,12 +201,6 @@ public function form_definition_extra($form, $mform, $customdata) {
$mform->addElement('textarea', 'params', get_string('webserviceactionparams', 'tool_trigger'), $attributes);
$mform->setType('params', PARAM_RAW_TRIMMED);
$mform->addHelpButton('params', 'webserviceactionparams', 'tool_trigger');

// Params.
$attributes = ['cols' => '50', 'rows' => '5'];
$mform->addElement('textarea', 'alphaparams', get_string('webserviceactionalphaparmas', 'tool_trigger'), $attributes);
$mform->setType('alphaparams', PARAM_RAW_TRIMMED);
$mform->addHelpButton('alphaparams', 'webserviceactionalphaparmas', 'tool_trigger');
}

/**
Expand Down Expand Up @@ -257,14 +250,9 @@ public function form_validation($data, $files) {

$errorfield = 'params';

$alphaparams = explode(',', $data['alphaparams']);
// Fill template fields with a number. Some params are special and only allow letters.
$transformcallback = function($matches) use($alphaparams) {
if (in_array($matches[1], $alphaparams)) {
return '';
} else {
return 0;
}
// Fill template fields with a number.
$transformcallback = function() {
return 0;
};

// Cannot use redner_datafields since we need to know of the
Expand Down Expand Up @@ -336,106 +324,4 @@ public function transform_form_data($data) {
}
return $data;
}

/**
* Call an external function validating all params/returns correctly.
*
* Note that an external function may modify the state of the current page, so this wrapper
* saves and restores tha PAGE and COURSE global variables before/after calling the external function.
* This is a fork of the external_api::call_external_function method.
* Due the nature of the WS API, without a real user session the function may not work as expected.
*
* @param string $function A webservice function name.
* @param array $args Params array (named params)
* @param boolean $ajaxonly If true, an extra check will be peformed to see if ajax is required.
* @return array containing keys for error (bool), exception and data.
*/
public static function call_external_function($function, $args, $ajaxonly=false) {
global $PAGE, $COURSE, $CFG, $SITE;

require_once($CFG->libdir . "/pagelib.php");

$externalfunctioninfo = \external_api::external_function_info($function);

// Eventually this should shift into the various handlers and not be handled via config.
$readonlysession = $externalfunctioninfo->readonlysession ?? false;

if (!$readonlysession || empty($CFG->enable_read_only_sessions)) {
\core\session\manager::restart_with_write_lock($readonlysession);
}

$currentpage = $PAGE;
$currentcourse = $COURSE;
$response = array();

try {
// Taken straight from from setup.php.
if (!empty($CFG->moodlepageclass)) {
if (!empty($CFG->moodlepageclassfile)) {
require_once($CFG->moodlepageclassfile);
}
$classname = $CFG->moodlepageclass;
} else {
$classname = 'moodle_page';
}

$PAGE = new $classname();
$COURSE = clone($SITE);

// Validate params, this also sorts the params properly, we need the correct order in the next part.
$callable = array($externalfunctioninfo->classname, 'validate_parameters');

$params = call_user_func($callable,
$externalfunctioninfo->parameters_desc,
$args);

$params = array_values($params);
// Allow any Moodle plugin a chance to override this call. This is a convenient spot to
// make arbitrary behaviour customisations. The overriding plugin could call the 'real'
// function first and then modify the results, or it could do a completely separate
// thing.
$callbacks = get_plugins_with_function('override_webservice_execution');
$result = false;

foreach ($callbacks as $plugintype => $plugins) {
foreach ($plugins as $plugin => $callback) {
$result = $callback($externalfunctioninfo, $params);
if ($result !== false) {
break 2;
}
}
}

// If the function was not overridden, call the real one.
if ($result === false) {
$callable = array($externalfunctioninfo->classname, $externalfunctioninfo->methodname);
$result = call_user_func_array($callable, $params);
}

// Validate the return parameters.
if ($externalfunctioninfo->returns_desc !== null) {
$callable = array($externalfunctioninfo->classname, 'clean_returnvalue');
$result = call_user_func($callable, $externalfunctioninfo->returns_desc, $result);
}

$response['error'] = false;
$response['data'] = $result;
} catch (\Throwable $e) {
$exception = get_exception_info($e);
unset($exception->a);
$exception->backtrace = format_backtrace($exception->backtrace, true);
if (!debugging('', DEBUG_DEVELOPER)) {
unset($exception->debuginfo);
unset($exception->backtrace);
}
$response['error'] = true;
$response['exception'] = $exception;
// Do not process the remaining requests.
}

$PAGE = $currentpage;
$COURSE = $currentcourse;

return $response;
}
}
2 changes: 0 additions & 2 deletions lang/en/tool_trigger.php
Original file line number Diff line number Diff line change
Expand Up @@ -292,8 +292,6 @@

$string['webserviceactionfunctionname'] = 'Function';
$string['webserviceactionfunctionname_help'] = 'The webservice function to be called. See the <a href="/admin/webservice/documentation.php">API Documentation</a>';
$string['webserviceactionalphaparmas'] = 'WS alpha parameters';
$string['webserviceactionalphaparmas_help'] = 'The form validation may fail due to some ws parameters type, if you are getting an "ALPHA" type error, add the parameter here.';
$string['webserviceactionusername'] = 'Who';
$string['webserviceactionusername_help'] = 'The user (username) who this step will be performed in the context of. This defaults to the main admin user if not explicitly set';
$string['webserviceactionparams'] = 'Parameters';
Expand Down
47 changes: 47 additions & 0 deletions tests/course_lookup_step_test.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,35 @@ public function test_execute_basic() {
$this->assertEquals($context->id, $stepresults['course_contextid']);
}

/**
* Basic test, but this time with additional custom course field.
*/
public function test_execute_basic_with_custom_profile_fields() {
// Create custom course field.
$customfieldg = $this->getDataGenerator()->get_plugin_generator('core_customfield');
$category = $customfieldg->create_category();
$customfield = $customfieldg->create_field([
'categoryid' => $category->get('id'),
'type' => 'text',
'shortname' => 'testfield1',
'configdata' => [],
]);

// Add data to the customfield_data table.
$this->add_course_custom_course_field_data($customfield->get('id'), $this->course->id, 'CourseFieldValue');
$step = new \tool_trigger\steps\lookups\course_lookup_step(
json_encode([
'courseidfield' => 'objectid',
'outputprefix' => 'course_'
])
);

list($status, $stepresults) = $step->execute(null, null, $this->event, []);
$this->assertTrue($status);
// Check that the custom field data is returned as a step result.
$this->assertEquals('CourseFieldValue', $stepresults['course_testfield1']);
}

/**
* Test for exception if an invalid field name is entered.
*/
Expand Down Expand Up @@ -222,4 +251,22 @@ public function test_execute_course_id_string() {
$this->assertEquals($this->course->fullname, $stepresults['course_fullname']);
$this->assertEquals($context->id, $stepresults['course_contextid']);
}

public function add_course_custom_course_field_data($fieldid, $courseid, $customfielddata) {
global $DB;

// Add data to the customfield_data table.
$data = new \stdClass();
$data->fieldid = $fieldid;
$data->instanceid = $courseid;
$data->value = $customfielddata;
$data->charvalue = $customfielddata;
$data->timecreated = time();
$data->timemodified = time();
$data->valueformat = 0;
$data->valuetrust = 0;
$data->contextid = \context_course::instance($courseid)->id;

return $DB->insert_record('customfield_data', $data);
}
}
Loading