| {{#str}} customfield, core_customfield {{/str}} | +{{#str}} shortname, core_customfield {{/str}} | +{{#str}} type, core_customfield {{/str}} | +{{#str}} action, core_customfield {{/str}} | +
|---|---|---|---|
| {{> core/drag_handle}}{{{name}}} | +{{{shortname}}} | +{{{type}}} | ++ {{#pix}} + t/edit, core, {{#str}} edit, moodle {{/str}} {{/pix}} + {{#pix}} + t/delete, core, {{#str}} delete, moodle {{/str}} {{/pix}} + | +
Hi there
', 'format' => FORMAT_HTML]); + + $handler = $c1->get_handler(); + list($data1, $data2, $data3, $data4, $data5) = array_values($handler->get_instance_data($course1->id)); + $this->assertNotEmpty($data1->get('id')); + $this->assertEquals(1, $data1->get_value()); + $this->assertNotEmpty($data2->get('id')); + $this->assertEquals(1546300800, $data2->get_value()); + $this->assertNotEmpty($data3->get('id')); + $this->assertEquals(2, $data3->get_value()); + $this->assertNotEmpty($data4->get('id')); + $this->assertEquals('Hello', $data4->get_value()); + $this->assertNotEmpty($data5->get('id')); + $this->assertEquals('Hi there
', $data5->get_value()); + } +} diff --git a/tests/privacy/provider_test.php b/tests/privacy/provider_test.php new file mode 100644 index 0000000..8ee9c41 --- /dev/null +++ b/tests/privacy/provider_test.php @@ -0,0 +1,290 @@ +. + +/** + * Class provider_test + * + * @package core_customfield + * @copyright 2019 Marina Glancy + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace core_customfield\privacy; + +defined('MOODLE_INTERNAL') || die(); + +use core_privacy\tests\provider_testcase; +use core_privacy\local\request\approved_contextlist; +use core_privacy\local\request\writer; +use core_customfield\privacy\provider; + +/** + * Class provider_test + * + * @package core_customfield + * @copyright 2019 Marina Glancy + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class provider_test extends provider_testcase { + + /** + * Generate data. + * + * @return array + */ + protected function generate_test_data(): array { + $this->resetAfterTest(); + + $generator = $this->getDataGenerator()->get_plugin_generator('core_customfield'); + $cfcats[1] = $generator->create_category(); + $cfcats[2] = $generator->create_category(); + $cffields[11] = $generator->create_field( + ['categoryid' => $cfcats[1]->get('id'), 'type' => 'checkbox']); + $cffields[12] = $generator->create_field( + ['categoryid' => $cfcats[1]->get('id'), 'type' => 'date']); + $cffields[13] = $generator->create_field( + ['categoryid' => $cfcats[1]->get('id'), + 'type' => 'select', 'configdata' => ['options' => "a\nb\nc"]]); + $cffields[14] = $generator->create_field( + ['categoryid' => $cfcats[1]->get('id'), 'type' => 'text']); + $cffields[15] = $generator->create_field( + ['categoryid' => $cfcats[1]->get('id'), 'type' => 'textarea']); + $cffields[21] = $generator->create_field( + ['categoryid' => $cfcats[2]->get('id')]); + $cffields[22] = $generator->create_field( + ['categoryid' => $cfcats[2]->get('id')]); + + $courses[1] = $this->getDataGenerator()->create_course(); + $courses[2] = $this->getDataGenerator()->create_course(); + $courses[3] = $this->getDataGenerator()->create_course(); + + $generator->add_instance_data($cffields[11], $courses[1]->id, 1); + $generator->add_instance_data($cffields[12], $courses[1]->id, 1546300800); + $generator->add_instance_data($cffields[13], $courses[1]->id, 2); + $generator->add_instance_data($cffields[14], $courses[1]->id, 'Hello1'); + $generator->add_instance_data($cffields[15], $courses[1]->id, + ['text' => 'Hi there
', 'format' => FORMAT_HTML]); + + $generator->add_instance_data($cffields[21], $courses[1]->id, 'hihi1'); + + $generator->add_instance_data($cffields[14], $courses[2]->id, 'Hello2'); + + $generator->add_instance_data($cffields[21], $courses[2]->id, 'hihi2'); + + $user = $this->getDataGenerator()->create_user(); + $this->setUser($user); + + return [ + 'user' => $user, + 'cfcats' => $cfcats, + 'cffields' => $cffields, + 'courses' => $courses, + ]; + } + + /** + * Test for provider::get_metadata() + */ + public function test_get_metadata() { + $collection = new \core_privacy\local\metadata\collection('core_customfield'); + $collection = provider::get_metadata($collection); + $this->assertNotEmpty($collection); + } + + /** + * Test for provider::get_customfields_data_contexts + */ + public function test_get_customfields_data_contexts() { + global $DB; + [ + 'cffields' => $cffields, + 'cfcats' => $cfcats, + 'courses' => $courses, + ] = $this->generate_test_data(); + + list($sql, $params) = $DB->get_in_or_equal([$courses[1]->id, $courses[2]->id], SQL_PARAMS_NAMED); + $r = provider::get_customfields_data_contexts('core_course', 'course', '=0', + $sql, $params); + $this->assertEqualsCanonicalizing([\context_course::instance($courses[1]->id)->id, + \context_course::instance($courses[2]->id)->id], + $r->get_contextids()); + } + + /** + * Test for provider::get_customfields_configuration_contexts() + */ + public function test_get_customfields_configuration_contexts() { + $this->generate_test_data(); + + $r = provider::get_customfields_configuration_contexts('core_course', 'course'); + $this->assertEquals([\context_system::instance()->id], $r->get_contextids()); + } + + /** + * Test for provider::export_customfields_data() + */ + public function test_export_customfields_data() { + global $USER, $DB; + $this->resetAfterTest(); + [ + 'cffields' => $cffields, + 'cfcats' => $cfcats, + 'courses' => $courses, + ] = $this->generate_test_data(); + + // Hack one of the fields so it has an invalid field type. + $invalidfieldid = $cffields[21]->get('id'); + $DB->update_record('customfield_field', ['id' => $invalidfieldid, 'type' => 'invalid']); + + $context = \context_course::instance($courses[1]->id); + $contextlist = new approved_contextlist($USER, 'core_customfield', [$context->id]); + provider::export_customfields_data($contextlist, 'core_course', 'course', '=0', '=:i', ['i' => $courses[1]->id]); + /** @var core_privacy\tests\request\content_writer $writer */ + $writer = writer::with_context($context); + + // Make sure that all and only data for the course1 was exported. + // There is no way to fetch all data from writer as array so we need to fetch one-by-one for each data id. + $invaldfieldischecked = false; + foreach ($DB->get_records('customfield_data', []) as $dbrecord) { + $data = $writer->get_data(['Custom fields data', $dbrecord->id]); + if ($dbrecord->instanceid == $courses[1]->id) { + $this->assertEquals($dbrecord->fieldid, $data->fieldid); + $this->assertNotEmpty($data->fieldtype); + $this->assertNotEmpty($data->fieldshortname); + $this->assertNotEmpty($data->fieldname); + $invaldfieldischecked = $invaldfieldischecked ?: ($data->fieldid == $invalidfieldid); + } else { + $this->assertEmpty($data); + } + } + + // Make sure field with was checked in this test. + $this->assertTrue($invaldfieldischecked); + } + + /** + * Test for provider::delete_customfields_data() + */ + public function test_delete_customfields_data() { + global $USER, $DB; + $this->resetAfterTest(); + [ + 'cffields' => $cffields, + 'cfcats' => $cfcats, + 'courses' => $courses, + ] = $this->generate_test_data(); + + $approvedcontexts = new approved_contextlist($USER, 'core_course', [\context_course::instance($courses[1]->id)->id]); + provider::delete_customfields_data($approvedcontexts, 'core_course', 'course'); + $this->assertEmpty($DB->get_records('customfield_data', ['instanceid' => $courses[1]->id])); + $this->assertNotEmpty($DB->get_records('customfield_data', ['instanceid' => $courses[2]->id])); + } + + /** + * Test for provider::delete_customfields_configuration() + */ + public function test_delete_customfields_configuration() { + global $USER, $DB; + $this->resetAfterTest(); + [ + 'cffields' => $cffields, + 'cfcats' => $cfcats, + 'courses' => $courses, + ] = $this->generate_test_data(); + + // Remember the list of fields in the category 2 before we delete it. + $catid1 = $cfcats[1]->get('id'); + $catid2 = $cfcats[2]->get('id'); + $fids2 = $DB->get_fieldset_select('customfield_field', 'id', 'categoryid=?', [$catid2]); + $this->assertNotEmpty($fids2); + list($fsql, $fparams) = $DB->get_in_or_equal($fids2, SQL_PARAMS_NAMED); + $this->assertNotEmpty($DB->get_records_select('customfield_data', 'fieldid ' . $fsql, $fparams)); + + // A little hack here, modify customfields configuration so they have different itemids. + $DB->update_record('customfield_category', ['id' => $catid2, 'itemid' => 1]); + $contextlist = new approved_contextlist($USER, 'core_course', [\context_system::instance()->id]); + provider::delete_customfields_configuration($contextlist, 'core_course', 'course', '=:i', ['i' => 1]); + + // Make sure everything for category $catid2 is gone but present for $catid1. + $this->assertEmpty($DB->get_records('customfield_category', ['id' => $catid2])); + $this->assertEmpty($DB->get_records_select('customfield_field', 'id ' . $fsql, $fparams)); + $this->assertEmpty($DB->get_records_select('customfield_data', 'fieldid ' . $fsql, $fparams)); + + $this->assertNotEmpty($DB->get_records('customfield_category', ['id' => $catid1])); + $fids1 = $DB->get_fieldset_select('customfield_field', 'id', 'categoryid=?', [$catid1]); + list($fsql1, $fparams1) = $DB->get_in_or_equal($fids1, SQL_PARAMS_NAMED); + $this->assertNotEmpty($DB->get_records_select('customfield_field', 'id ' . $fsql1, $fparams1)); + $this->assertNotEmpty($DB->get_records_select('customfield_data', 'fieldid ' . $fsql1, $fparams1)); + } + + /** + * Test for provider::delete_customfields_configuration_for_context() + */ + public function test_delete_customfields_configuration_for_context() { + global $USER, $DB; + $this->resetAfterTest(); + [ + 'cffields' => $cffields, + 'cfcats' => $cfcats, + 'courses' => $courses, + ] = $this->generate_test_data(); + + // Remember the list of fields in the category 2 before we delete it. + $catid1 = $cfcats[1]->get('id'); + $catid2 = $cfcats[2]->get('id'); + $fids2 = $DB->get_fieldset_select('customfield_field', 'id', 'categoryid=?', [$catid2]); + $this->assertNotEmpty($fids2); + list($fsql, $fparams) = $DB->get_in_or_equal($fids2, SQL_PARAMS_NAMED); + $this->assertNotEmpty($DB->get_records_select('customfield_data', 'fieldid ' . $fsql, $fparams)); + + // A little hack here, modify customfields configuration so they have different contexts. + $context = \context_user::instance($USER->id); + $DB->update_record('customfield_category', ['id' => $catid2, 'contextid' => $context->id]); + provider::delete_customfields_configuration_for_context('core_course', 'course', $context); + + // Make sure everything for category $catid2 is gone but present for $catid1. + $this->assertEmpty($DB->get_records('customfield_category', ['id' => $catid2])); + $this->assertEmpty($DB->get_records_select('customfield_field', 'id ' . $fsql, $fparams)); + $this->assertEmpty($DB->get_records_select('customfield_data', 'fieldid ' . $fsql, $fparams)); + + $this->assertNotEmpty($DB->get_records('customfield_category', ['id' => $catid1])); + $fids1 = $DB->get_fieldset_select('customfield_field', 'id', 'categoryid=?', [$catid1]); + list($fsql1, $fparams1) = $DB->get_in_or_equal($fids1, SQL_PARAMS_NAMED); + $this->assertNotEmpty($DB->get_records_select('customfield_field', 'id ' . $fsql1, $fparams1)); + $this->assertNotEmpty($DB->get_records_select('customfield_data', 'fieldid ' . $fsql1, $fparams1)); + } + + /** + * Test for provider::delete_customfields_data_for_context() + */ + public function test_delete_customfields_data_for_context() { + global $DB; + $this->resetAfterTest(); + [ + 'cffields' => $cffields, + 'cfcats' => $cfcats, + 'courses' => $courses, + ] = $this->generate_test_data(); + + provider::delete_customfields_data_for_context('core_course', 'course', + \context_course::instance($courses[1]->id)); + $fids2 = $DB->get_fieldset_select('customfield_field', 'id', '1=1', []); + list($fsql, $fparams) = $DB->get_in_or_equal($fids2, SQL_PARAMS_NAMED); + $fparams['course1'] = $courses[1]->id; + $fparams['course2'] = $courses[2]->id; + $this->assertEmpty($DB->get_records_select('customfield_data', 'instanceid = :course1 AND fieldid ' . $fsql, $fparams)); + $this->assertNotEmpty($DB->get_records_select('customfield_data', 'instanceid = :course2 AND fieldid ' . $fsql, $fparams)); + } +} diff --git a/upgrade.txt b/upgrade.txt new file mode 100644 index 0000000..8e61c85 --- /dev/null +++ b/upgrade.txt @@ -0,0 +1,13 @@ +This files describes API changes in /customfield/*, +Information provided here is intended especially for developers. + +=== 3.11 === +* Methods \core_customfield\handler::get_field_config_form() and \core_customfield\handler::setup_edit_page() are no + longer used. Components that define custom fields areas do not need to implement them. Field edit form opens in + the modal now. + +=== 4.0 === +The way the method customfield_multiselect\data_controller::get_value() was returning an array was causing multiple issues with core AJAX functionality returning course data. +Examples of somewhere this error was occuring is when trying to duplicate a course via 'manage courses and categories', or when trying to fetch a list of courses when adding a meta link enrolment method. +Issues also occured when using Edwiser Bridge. +The solution was to return a string of values using get_value, but preparing the data as an array when using the course edit form.