MDL-57273 core: Exporters support custom formatting parameters

This commit is contained in:
Frederic Massart 2016-12-15 18:03:40 +08:00 committed by Damyon Wiese
parent 268beda8e9
commit 4bc68a416e
16 changed files with 334 additions and 30 deletions

View File

@ -72,7 +72,7 @@ class competency_path_exporter extends \core\external\exporter {
'type' => path_node_exporter::read_properties_definition()
],
'pluginbaseurl' => [
'type' => PARAM_TEXT
'type' => PARAM_URL
],
'pagecontextid' => [
'type' => PARAM_INT

View File

@ -24,6 +24,7 @@
namespace tool_lp\external;
defined('MOODLE_INTERNAL') || die();
use context_system;
/**
* Class for exporting path_node data.
@ -33,6 +34,34 @@ defined('MOODLE_INTERNAL') || die();
*/
class path_node_exporter extends \core\external\exporter {
/**
* Constructor - saves the persistent object, and the related objects.
*
* @param mixed $data The data.
* @param array $related Array of relateds.
*/
public function __construct($data, $related = array()) {
if (!isset($related['context'])) {
// Previous code was automatically using the system context which was not always correct.
// We let developers know that they must fix their code without breaking anything,
// and fallback on the previous behaviour. This should be removed at a later stage: Moodle 3.5.
debugging('Missing related context in path_node_exporter.', DEBUG_DEVELOPER);
$related['context'] = context_system::instance();
}
parent::__construct($data, $related);
}
/**
* Return the list of properties.
*
* @return array
*/
protected static function define_related() {
return [
'context' => 'context'
];
}
/**
* Return the list of properties.
*

View File

@ -154,6 +154,7 @@ class user_competency_summary_exporter extends \core\external\exporter {
'scale' => $scale,
'usercompetency' => ($this->related['usercompetency'] ? $this->related['usercompetency'] : null),
'usercompetencyplan' => ($this->related['usercompetencyplan'] ? $this->related['usercompetencyplan'] : null),
'context' => $evidence->get_context()
);
$related['actionuser'] = !empty($actionuserid) ? $usercache[$actionuserid] : null;
$exporter = new evidence_exporter($evidence, $related);

View File

@ -443,6 +443,24 @@ class tool_lp_external_testcase extends externallib_advanced_testcase {
$this->assertEquals('A', $summary->usercompetencysummary->evidence[1]->gradename);
}
public function test_data_for_user_competency_summary() {
$this->setUser($this->creator);
$dg = $this->getDataGenerator();
$lpg = $dg->get_plugin_generator('core_competency');
$f1 = $lpg->create_framework();
$c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get_id()));
$evidence = \core_competency\external::grade_competency($this->user->id, $c1->get_id(), 1, true);
$evidence = \core_competency\external::grade_competency($this->user->id, $c1->get_id(), 2, true);
$summary = external::data_for_user_competency_summary($this->user->id, $c1->get_id());
$this->assertTrue($summary->cangrade);
$this->assertEquals('B', $summary->usercompetency->gradename);
$this->assertEquals('B', $summary->evidence[0]->gradename);
$this->assertEquals('A', $summary->evidence[1]->gradename);
}
/**
* Search cohorts.
*/

View File

@ -110,6 +110,15 @@ class evidence extends persistent {
return user_competency::get_competency_by_usercompetencyid($this->get_usercompetencyid());
}
/**
* Return the evidence's context.
*
* @return context
*/
public function get_context() {
return context::instance_by_id($this->get('contextid'));
}
/**
* Convenience method to get the description $a.
*

View File

@ -3709,6 +3709,7 @@ class external extends external_api {
'scale' => $scale,
'usercompetency' => $uc,
'usercompetencyplan' => null,
'context' => $evidence->get_context()
]);
return $exporter->export($output);
}
@ -3795,6 +3796,7 @@ class external extends external_api {
'scale' => $scale,
'usercompetency' => null,
'usercompetencyplan' => null,
'context' => $evidence->get_context()
]);
return $exporter->export($output);
}
@ -4107,6 +4109,7 @@ class external extends external_api {
'scale' => $scale,
'usercompetency' => null,
'usercompetencyplan' => null,
'context' => $evidence->get_context(),
));
return $exporter->export($output);
}

View File

@ -75,10 +75,10 @@ class competency_framework_exporter extends \core\external\persistent_exporter {
'type' => PARAM_INT
),
'contextname' => array(
'type' => PARAM_TEXT
'type' => PARAM_RAW
),
'contextnamenoprefix' => array(
'type' => PARAM_TEXT
'type' => PARAM_RAW
)
);
}

View File

@ -24,6 +24,7 @@
namespace core_competency\external;
defined('MOODLE_INTERNAL') || die();
use context_system;
use renderer_base;
use core_competency\evidence;
use core_competency\user_competency;
@ -37,9 +38,27 @@ use core_user\external\user_summary_exporter;
*/
class evidence_exporter extends \core\external\persistent_exporter {
/**
* Constructor.
*
* @param mixed $data The data.
* @param array $related Array of relateds.
*/
public function __construct($data, $related = array()) {
if (!isset($related['context'])) {
// Previous code was automatically using the system context which was not correct.
// We let developers know that they must fix their code without breaking anything, and
// fallback on the previous behaviour. This should be removed at a later stage: Moodle 3.5.
debugging('Missing related context in evidence_exporter.', DEBUG_DEVELOPER);
$related['context'] = context_system::instance();
}
parent::__construct($data, $related);
}
protected static function define_related() {
return array(
'actionuser' => 'stdClass?',
'context' => 'context',
'scale' => 'grade_scale',
'usercompetency' => 'core_competency\\user_competency?',
'usercompetencyplan' => 'core_competency\\user_competency_plan?',
@ -85,6 +104,17 @@ class evidence_exporter extends \core\external\persistent_exporter {
return $other;
}
/**
* Get the format parameters for gradename.
*
* @return array
*/
protected function get_format_parameters_for_gradename() {
return [
'context' => context_system::instance(), // The system context is cached, so we can get it right away.
];
}
public static function define_other_properties() {
return array(
'actionuser' => array(
@ -92,13 +122,13 @@ class evidence_exporter extends \core\external\persistent_exporter {
'optional' => true
),
'description' => array(
'type' => PARAM_TEXT,
'type' => PARAM_TEXT, // The description may contain course names, etc.. which may need filtering.
),
'gradename' => array(
'type' => PARAM_TEXT,
),
'userdate' => array(
'type' => PARAM_TEXT
'type' => PARAM_NOTAGS
),
'candelete' => array(
'type' => PARAM_BOOL

View File

@ -24,7 +24,7 @@
namespace core_competency\external;
defined('MOODLE_INTERNAL') || die();
use core_user;
use context_system;
use renderer_base;
use stdClass;
@ -65,6 +65,17 @@ class user_competency_course_exporter extends \core\external\persistent_exporter
return (array) $result;
}
/**
* Get the format parameters for gradename.
*
* @return array
*/
protected function get_format_parameters_for_gradename() {
return [
'context' => context_system::instance(), // The system context is cached, so we can get it right away.
];
}
protected static function define_other_properties() {
return array(
'gradename' => array(

View File

@ -24,6 +24,7 @@
namespace core_competency\external;
defined('MOODLE_INTERNAL') || die();
use context_system;
use core_user;
use renderer_base;
use stdClass;
@ -94,6 +95,17 @@ class user_competency_exporter extends \core\external\persistent_exporter {
return (array) $result;
}
/**
* Get the format parameters for gradename.
*
* @return array
*/
protected function get_format_parameters_for_gradename() {
return [
'context' => context_system::instance(), // The system context is cached, so we can get it right away.
];
}
protected static function define_other_properties() {
return array(
'canrequestreview' => array(

View File

@ -24,6 +24,7 @@
namespace core_competency\external;
defined('MOODLE_INTERNAL') || die();
use context_system;
use renderer_base;
use stdClass;
@ -64,6 +65,17 @@ class user_competency_plan_exporter extends \core\external\persistent_exporter {
return (array) $result;
}
/**
* Get the format parameters for gradename.
*
* @return array
*/
protected function get_format_parameters_for_gradename() {
return [
'context' => context_system::instance(), // The system context is cached, so we can get it right away.
];
}
protected static function define_other_properties() {
return array(
'gradename' => array(

View File

@ -2681,6 +2681,53 @@ class core_competency_external_testcase extends externallib_advanced_testcase {
$this->assertEquals(2, $c2a->get_sortorder());
}
public function test_grade_competency() {
global $CFG;
$this->setUser($this->creator);
$dg = $this->getDataGenerator();
$lpg = $dg->get_plugin_generator('core_competency');
$f1 = $lpg->create_framework();
$c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get_id()));
$evidence = external::grade_competency($this->user->id, $c1->get_id(), 1, 'Evil note');
$this->assertEquals('The competency rating was manually set.', $evidence->description);
$this->assertEquals('A', $evidence->gradename);
$this->assertEquals('Evil note', $evidence->note);
$this->setUser($this->user);
$this->expectException('required_capability_exception');
$evidence = external::grade_competency($this->user->id, $c1->get_id(), 1);
}
public function test_grade_competency_in_course() {
global $CFG;
$this->setUser($this->creator);
$dg = $this->getDataGenerator();
$lpg = $dg->get_plugin_generator('core_competency');
$course = $dg->create_course(['fullname' => 'Evil course']);
$dg->enrol_user($this->creator->id, $course->id, 'editingteacher');
$dg->enrol_user($this->user->id, $course->id, 'student');
$f1 = $lpg->create_framework();
$c1 = $lpg->create_competency(['competencyframeworkid' => $f1->get_id()]);
$lpg->create_course_competency(['courseid' => $course->id, 'competencyid' => $c1->get_id()]);
$evidence = external::grade_competency_in_course($course->id, $this->user->id, $c1->get_id(), 1, 'Evil note');
$this->assertEquals('The competency rating was manually set in the course \'Course: Evil course\'.', $evidence->description);
$this->assertEquals('A', $evidence->gradename);
$this->assertEquals('Evil note', $evidence->note);
$this->setUser($this->user);
$this->expectException('required_capability_exception');
$evidence = external::grade_competency_in_course($course->id, $this->user->id, $c1->get_id(), 1);
}
public function test_grade_competency_in_plan() {
global $CFG;

View File

@ -116,7 +116,6 @@ abstract class exporter {
final public function export(renderer_base $output) {
$data = new stdClass();
$properties = self::read_properties_definition();
$context = $this->get_context();
$values = (array) $this->data;
$othervalues = $this->get_other_values($output);
@ -150,18 +149,27 @@ abstract class exporter {
// Whoops, we got something that wasn't defined.
throw new coding_exception('Unexpected property ' . $propertyformat);
}
$formatparams = $this->get_format_parameters($property);
$format = $record->$propertyformat;
list($text, $format) = external_format_text($data->$property, $format, $context->id, 'core', '', 0);
list($text, $format) = external_format_text($data->$property, $format, $formatparams['context']->id,
$formatparams['component'], $formatparams['filearea'], $formatparams['itemid'], $formatparams['options']);
$data->$property = $text;
$data->$propertyformat = $format;
} else if ($definition['type'] === PARAM_TEXT) {
$formatparams = $this->get_format_parameters($property);
if (!empty($definition['multiple'])) {
foreach ($data->$property as $key => $value) {
$data->{$property}[$key] = external_format_string($value, $context->id);
$data->{$property}[$key] = external_format_string($value, $formatparams['context']->id,
$formatparams['striplinks'], $formatparams['options']);
}
} else {
$data->$property = external_format_string($data->$property, $context->id);
$data->$property = external_format_string($data->$property, $formatparams['context']->id,
$formatparams['striplinks'], $formatparams['options']);
}
}
}
@ -170,18 +178,52 @@ abstract class exporter {
}
/**
* Function to guess the correct context, falling back to system context.
* Get the format parameters.
*
* @return context
* This method returns the parameters to use with the functions external_format_text(), and
* external_format_string(). To override the default parameters, you can define a protected method
* called 'get_format_parameters_for_<propertyName>'. For example, 'get_format_parameters_for_description',
* if your property is 'description'.
*
* Your method must return an array containing any of the following keys:
* - context: The context to use. Defaults to $this->related['context'] if defined, else throws an exception.
* - component: The component to use with external_format_text(). Defaults to null.
* - filearea: The filearea to use with external_format_text(). Defaults to null.
* - itemid: The itemid to use with external_format_text(). Defaults to null.
* - options: An array of options accepted by external_format_text() or external_format_string(). Defaults to [].
* - striplinks: Whether to strip the links with external_format_string(). Defaults to true.
*
* @param string $property The property to get the parameters for.
* @return array
*/
protected function get_context() {
$context = null;
if (isset($this->related['context']) && $this->related['context'] instanceof context) {
$context = $this->related['context'];
} else {
$context = context_system::instance();
final protected function get_format_parameters($property) {
$parameters = [
'component' => null,
'filearea' => null,
'itemid' => null,
'options' => [],
'striplinks' => true,
];
$candidate = 'get_format_parameters_for_' . $property;
if (method_exists($this, $candidate)) {
$parameters = array_merge($parameters, $this->{$candidate}());
}
return $context;
if (!isset($parameters['context'])) {
if (!isset($this->related['context']) || !($this->related['context'] instanceof context)) {
throw new coding_exception("Unknown context to use for formatting the property '$property' in the " .
"exporter '" . get_class($this) . "'. You either need to add 'context' to your related objects, " .
"or create the method '$candidate' and return the context from there.");
}
$parameters['context'] = $this->related['context'];
} else if (!($parameters['context'] instanceof context)) {
throw new coding_exception("The context given to format the property '$property' in the exporter '" .
get_class($this) . "' is invalid.");
}
return $parameters;
}
/**
@ -291,6 +333,13 @@ abstract class exporter {
* The format of the array returned by this method has to match the structure
* defined in {@link \core\persistent::define_properties()}.
*
* Note that the type PARAM_TEXT should ONLY be used for strings which need to
* go through filters (multilang, etc...) and do not have a FORMAT_* associated
* to them. Typically strings passed through to format_string().
*
* Other filtered strings which use a FORMAT_* constant (hear used with format_text)
* must be defined as PARAM_RAW.
*
* @return array
*/
protected static function define_properties() {

View File

@ -47,7 +47,7 @@ abstract class persistent_exporter extends exporter {
* @param \core\persistent $persistent The persistent object to export.
* @param array $related - An optional list of pre-loaded objects related to this persistent.
*/
public final function __construct(\core\persistent $persistent, $related = array()) {
public function __construct(\core\persistent $persistent, $related = array()) {
$classname = static::define_class();
if (!$persistent instanceof $classname) {
throw new coding_exception('Invalid type for persistent. ' .

View File

@ -41,8 +41,8 @@ class core_exporter_testcase extends advanced_testcase {
public function setUp() {
$s = new stdClass();
$this->validrelated = array('simplestdClass' => $s, 'arrayofstdClass' => array($s, $s));
$this->invalidrelated = array('simplestdClass' => 'a string', 'arrayofstdClass' => 5);
$this->validrelated = array('simplestdClass' => $s, 'arrayofstdClass' => array($s, $s), 'context' => null);
$this->invalidrelated = array('simplestdClass' => 'a string', 'arrayofstdClass' => 5, 'context' => null);
$this->validdata = array('stringA' => 'A string', 'stringAformat' => FORMAT_HTML, 'intB' => 4);
@ -88,7 +88,7 @@ class core_exporter_testcase extends advanced_testcase {
public function test_invalid_data() {
global $PAGE;
$exporter = new core_testable_exporter($this->invaliddata, $this->validrelated);
$output = $PAGE->get_renderer('tool_lp');
$output = $PAGE->get_renderer('core');
$result = $exporter->export($output);
}
@ -99,21 +99,59 @@ class core_exporter_testcase extends advanced_testcase {
public function test_invalid_related() {
global $PAGE;
$exporter = new core_testable_exporter($this->validdata, $this->invalidrelated);
$output = $PAGE->get_renderer('tool_lp');
$output = $PAGE->get_renderer('core');
$result = $exporter->export($output);
}
public function test_valid_data_and_related() {
global $PAGE;
$output = $PAGE->get_renderer('core');
$exporter = new core_testable_exporter($this->validdata, $this->validrelated);
$result = $exporter->export($output);
$this->assertSame('>Another string', $result->otherstring);
$this->assertSame(array('String &gt;a', 'String b'), $result->otherstrings);
}
$output = $PAGE->get_renderer('tool_lp');
public function test_format_text() {
global $PAGE;
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$syscontext = context_system::instance();
$coursecontext = context_course::instance($course->id);
external_settings::get_instance()->set_filter(true);
filter_set_global_state('urltolink', TEXTFILTER_OFF);
filter_set_local_state('urltolink', $coursecontext->id, TEXTFILTER_ON);
set_config('formats', FORMAT_MARKDOWN, 'filter_urltolink');
filter_manager::reset_caches();
$data = [
'stringA' => '__Watch out:__ https://moodle.org @@PLUGINFILE@@/test.pdf',
'stringAformat' => FORMAT_MARKDOWN,
'intB' => 1
];
// Export simulated in the system context.
$output = $PAGE->get_renderer('core');
$exporter = new core_testable_exporter($data, ['context' => $syscontext] + $this->validrelated);
$result = $exporter->export($output);
$this->assertSame('Another string', $result->otherstring);
$this->assertSame(array('String a', 'String b'), $result->otherstrings);
$youtube = 'https://moodle.org';
$fileurl = (new moodle_url('/webservice/pluginfile.php/' . $syscontext->id . '/test/area/9/test.pdf'))->out(false);
$expected = "<p><strong>Watch out:</strong> $youtube $fileurl</p>\n";
$this->assertEquals($expected, $result->stringA);
$this->assertEquals(FORMAT_HTML, $result->stringAformat);
// Export simulated in the course context where the filter is enabled.
$exporter = new core_testable_exporter($data, ['context' => $coursecontext] + $this->validrelated);
$result = $exporter->export($output);
$youtube = '<a href="https://moodle.org" class="_blanktarget">https://moodle.org</a>';
$fileurl = (new moodle_url('/webservice/pluginfile.php/' . $coursecontext->id . '/test/area/9/test.pdf'))->out(false);
$expected = "<p><strong>Watch out:</strong> $youtube <a href=\"$fileurl\" class=\"_blanktarget\">$fileurl</a></p>\n";
$this->assertEquals($expected, $result->stringA);
$this->assertEquals(FORMAT_HTML, $result->stringAformat);
}
}
@ -128,13 +166,13 @@ class core_testable_exporter extends \core\external\exporter {
protected static function define_related() {
// We cache the context so it does not need to be retrieved from the course.
return array('simplestdClass' => 'stdClass', 'arrayofstdClass' => 'stdClass[]');
return array('simplestdClass' => 'stdClass', 'arrayofstdClass' => 'stdClass[]', 'context' => 'context?');
}
protected function get_other_values(renderer_base $output) {
return array(
'otherstring' => 'Another <strong>string</strong>',
'otherstrings' => array('String a', 'String <strong>b</strong>')
'otherstring' => '>Another <strong>string</strong>',
'otherstrings' => array('String >a', 'String <strong>b</strong>')
);
}
@ -164,5 +202,26 @@ class core_testable_exporter extends \core\external\exporter {
);
}
protected function get_format_parameters_for_stringA() {
return [
// For testing use the passed context if any.
'context' => isset($this->related['context']) ? $this->related['context'] : context_system::instance(),
'component' => 'test',
'filearea' => 'area',
'itemid' => 9,
];
}
protected function get_format_parameters_for_otherstring() {
return [
'context' => context_system::instance(),
'options' => ['escape' => false]
];
}
protected function get_format_parameters_for_otherstrings() {
return [
'context' => context_system::instance(),
];
}
}

View File

@ -24,6 +24,7 @@
namespace core_user\external;
defined('MOODLE_INTERNAL') || die();
use context_system;
use renderer_base;
use moodle_url;
@ -66,6 +67,29 @@ class user_summary_exporter extends \core\external\exporter {
);
}
/**
* Get the format parameters for department.
*
* @return array
*/
protected function get_format_parameters_for_department() {
return [
'context' => context_system::instance(), // The system context is cached, so we can get it right away.
];
}
/**
* Get the format parameters for institution.
*
* @return array
*/
protected function get_format_parameters_for_institution() {
return [
'context' => context_system::instance(), // The system context is cached, so we can get it right away.
];
}
public static function define_properties() {
return array(
'id' => array(