diff --git a/customfield/classes/privacy/customfield_provider.php b/customfield/classes/privacy/customfield_provider.php new file mode 100644 index 0000000000000..8d05a5b2bd0e6 --- /dev/null +++ b/customfield/classes/privacy/customfield_provider.php @@ -0,0 +1,84 @@ +. + +/** + * Contains interface customfield_provider + * + * @package core_customfield + * @copyright 2018 Marina Glancy + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace core_customfield\privacy; + +use core_customfield\data_controller; + +defined('MOODLE_INTERNAL') || die(); + +/** + * Interface customfield_provider, all customfield plugins need to implement it + * + * @package core_customfield + * @copyright 2018 Marina Glancy + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +interface customfield_provider extends + \core_privacy\local\request\plugin\subplugin_provider, + + // The customfield plugins do not need to do anything themselves for the shared_userlist. + // This is all handled by the component core_customfield. + \core_privacy\local\request\shared_userlist_provider + { + + /** + * Preprocesses data object that is going to be exported + * + * Minimum implementation: + * writer::with_context($data->get_context())->export_data($subcontext, $exportdata); + * + * @param data_controller $data + * @param \stdClass $exportdata generated object to be exported + * @param array $subcontext subcontext to use when exporting + * @return mixed + */ + public static function export_customfield_data(data_controller $data, \stdClass $exportdata, array $subcontext); + + /** + * Allows plugins to delete everything they store related to the data (usually files) + * + * If plugin does not store any related files or other information, implement as an empty function + * + * @param string $dataidstest select query for data id (note that it may also return data for other field types) + * @param array $params named parameters for the select query + * @param array $contextids list of affected data contexts + * @return mixed + */ + public static function before_delete_data(string $dataidstest, array $params, array $contextids); + + /** + * Allows plugins to delete everything they store related to the field configuration (usually files) + * + * The implementation should not delete data or anything related to the data, since "before_delete_data" is + * invoked separately. + * + * If plugin does not store any related files or other information, implement as an empty function + * + * @param string $fieldidstest select query for field id (note that it may also return fields of other types) + * @param array $params named parameters for the select query + * @param int[] $contextids list of affected configuration contexts + */ + public static function before_delete_fields(string $fieldidstest, array $params, array $contextids); +} diff --git a/customfield/field/checkbox/tests/plugin_test.php b/customfield/field/checkbox/tests/plugin_test.php new file mode 100644 index 0000000000000..6d1175001127a --- /dev/null +++ b/customfield/field/checkbox/tests/plugin_test.php @@ -0,0 +1,177 @@ +. + +/** + * Tests for class customfield_checkbox + * + * @package customfield_checkbox + * @copyright 2019 Marina Glancy + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +use customfield_checkbox\field_controller; +use customfield_checkbox\data_controller; + +/** + * Functional test for customfield_checkbox + * + * @package customfield_checkbox + * @copyright 2019 Marina Glancy + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class customfield_checkbox_plugin_testcase extends advanced_testcase { + + /** @var stdClass[] */ + private $courses = []; + /** @var \core_customfield\category_controller */ + private $cfcat; + /** @var \core_customfield\field_controller[] */ + private $cfields; + /** @var \core_customfield\data_controller[] */ + private $cfdata; + + /** + * Tests set up. + */ + public function setUp() { + $this->resetAfterTest(); + + $this->cfcat = $this->get_generator()->create_category(); + + $this->cfields[1] = $this->get_generator()->create_field( + ['categoryid' => $this->cfcat->get('id'), 'shortname' => 'myfield1', 'type' => 'checkbox']); + $this->cfields[2] = $this->get_generator()->create_field( + ['categoryid' => $this->cfcat->get('id'), 'shortname' => 'myfield2', 'type' => 'checkbox', + 'configdata' => ['required' => 1]]); + $this->cfields[3] = $this->get_generator()->create_field( + ['categoryid' => $this->cfcat->get('id'), 'shortname' => 'myfield3', 'type' => 'checkbox', + 'configdata' => ['checkbydefault' => 1]]); + + $this->courses[1] = $this->getDataGenerator()->create_course(); + $this->courses[2] = $this->getDataGenerator()->create_course(); + $this->courses[3] = $this->getDataGenerator()->create_course(); + + $this->cfdata[1] = $this->get_generator()->add_instance_data($this->cfields[1], $this->courses[1]->id, 1); + $this->cfdata[2] = $this->get_generator()->add_instance_data($this->cfields[1], $this->courses[2]->id, 1); + + $this->setUser($this->getDataGenerator()->create_user()); + } + + /** + * Get generator + * @return core_customfield_generator + */ + protected function get_generator(): core_customfield_generator { + return $this->getDataGenerator()->get_plugin_generator('core_customfield'); + } + + /** + * Test for initialising field and data controllers + */ + public function test_initialise() { + $f = \core_customfield\field_controller::create($this->cfields[1]->get('id')); + $this->assertTrue($f instanceof field_controller); + + $f = \core_customfield\field_controller::create(0, (object)['type' => 'checkbox'], $this->cfcat); + $this->assertTrue($f instanceof field_controller); + + $d = \core_customfield\data_controller::create($this->cfdata[1]->get('id')); + $this->assertTrue($d instanceof data_controller); + + $d = \core_customfield\data_controller::create(0, null, $this->cfields[1]); + $this->assertTrue($d instanceof data_controller); + } + + /** + * Test for configuration form functions + * + * Create a configuration form and submit it with the same values as in the field + */ + public function test_config_form() { + $submitdata = (array)$this->cfields[1]->to_record(); + $submitdata['configdata'] = $this->cfields[1]->get('configdata'); + + \core_customfield\field_config_form::mock_submit($submitdata, []); + $handler = $this->cfcat->get_handler(); + $form = $handler->get_field_config_form($this->cfields[1]); + $this->assertTrue($form->is_validated()); + $data = $form->get_data(); + $handler->save_field_configuration($this->cfields[1], $data); + + // Try submitting with 'unique values' checked. + $submitdata['configdata']['uniquevalues'] = 1; + \core_customfield\field_config_form::mock_submit($submitdata, []); + $handler = $this->cfcat->get_handler(); + $form = $handler->get_field_config_form($this->cfields[1]); + $this->assertFalse($form->is_validated()); + } + + /** + * Test for instance form functions + */ + public function test_instance_form() { + global $CFG; + require_once($CFG->dirroot . '/customfield/tests/fixtures/test_instance_form.php'); + $this->setAdminUser(); + $handler = $this->cfcat->get_handler(); + + // First try to submit without required field. + $submitdata = (array)$this->courses[1]; + core_customfield_test_instance_form::mock_submit($submitdata, []); + $form = new core_customfield_test_instance_form('POST', + ['handler' => $handler, 'instance' => $this->courses[1]]); + $this->assertFalse($form->is_validated()); + + // Now with required field. + $submitdata['customfield_myfield2'] = 1; + core_customfield_test_instance_form::mock_submit($submitdata, []); + $form = new core_customfield_test_instance_form('POST', + ['handler' => $handler, 'instance' => $this->courses[1]]); + $this->assertTrue($form->is_validated()); + + $data = $form->get_data(); + $this->assertNotEmpty($data->customfield_myfield1); + $this->assertNotEmpty($data->customfield_myfield2); + $handler->instance_form_save($data); + } + + /** + * Test for data_controller::get_value and export_value + */ + public function test_get_export_value() { + $this->assertEquals(1, $this->cfdata[1]->get_value()); + $this->assertEquals('Yes', $this->cfdata[1]->export_value()); + + // Field without data. + $d = core_customfield\data_controller::create(0, null, $this->cfields[2]); + $this->assertEquals(0, $d->get_value()); + $this->assertEquals('No', $d->export_value()); + + // Field without data that is checked by default. + $d = core_customfield\data_controller::create(0, null, $this->cfields[3]); + $this->assertEquals(1, $d->get_value()); + $this->assertEquals('Yes', $d->export_value()); + } + + /** + * Deleting fields and data + */ + public function test_delete() { + $this->cfcat->get_handler()->delete_all(); + } +} diff --git a/customfield/field/date/tests/plugin_test.php b/customfield/field/date/tests/plugin_test.php new file mode 100644 index 0000000000000..95b4876c7a5a3 --- /dev/null +++ b/customfield/field/date/tests/plugin_test.php @@ -0,0 +1,180 @@ +. + +/** + * Tests for class customfield_date + * + * @package customfield_date + * @copyright 2019 Marina Glancy + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +use customfield_date\field_controller; +use customfield_date\data_controller; + +/** + * Functional test for customfield_date + * + * @package customfield_date + * @copyright 2019 Marina Glancy + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class customfield_date_plugin_testcase extends advanced_testcase { + + /** @var stdClass[] */ + private $courses = []; + /** @var \core_customfield\category_controller */ + private $cfcat; + /** @var \core_customfield\field_controller[] */ + private $cfields; + /** @var \core_customfield\data_controller[] */ + private $cfdata; + + /** + * Tests set up. + */ + public function setUp() { + $this->resetAfterTest(); + + $this->cfcat = $this->get_generator()->create_category(); + + $this->cfields[1] = $this->get_generator()->create_field( + ['categoryid' => $this->cfcat->get('id'), 'shortname' => 'myfield1', 'type' => 'date']); + $this->cfields[2] = $this->get_generator()->create_field( + ['categoryid' => $this->cfcat->get('id'), 'shortname' => 'myfield2', 'type' => 'date', + 'configdata' => ['required' => 1, 'includetime' => 0, 'mindate' => 946684800, 'maxdate' => 1893456000]]); + + $this->courses[1] = $this->getDataGenerator()->create_course(); + $this->courses[2] = $this->getDataGenerator()->create_course(); + $this->courses[3] = $this->getDataGenerator()->create_course(); + + $this->cfdata[1] = $this->get_generator()->add_instance_data($this->cfields[1], $this->courses[1]->id, 1546300800); + $this->cfdata[2] = $this->get_generator()->add_instance_data($this->cfields[1], $this->courses[2]->id, 1546300800); + + $this->setUser($this->getDataGenerator()->create_user()); + } + + /** + * Get generator + * @return core_customfield_generator + */ + protected function get_generator(): core_customfield_generator { + return $this->getDataGenerator()->get_plugin_generator('core_customfield'); + } + + /** + * Test for initialising field and data controllers + */ + public function test_initialise() { + $f = \core_customfield\field_controller::create($this->cfields[1]->get('id')); + $this->assertTrue($f instanceof field_controller); + + $f = \core_customfield\field_controller::create(0, (object)['type' => 'date'], $this->cfcat); + $this->assertTrue($f instanceof field_controller); + + $d = \core_customfield\data_controller::create($this->cfdata[1]->get('id')); + $this->assertTrue($d instanceof data_controller); + + $d = \core_customfield\data_controller::create(0, null, $this->cfields[1]); + $this->assertTrue($d instanceof data_controller); + } + + /** + * Test for configuration form functions + * + * Create a configuration form and submit it with the same values as in the field + */ + public function test_config_form() { + $submitdata = (array)$this->cfields[1]->to_record(); + $submitdata['configdata'] = $this->cfields[1]->get('configdata'); + + \core_customfield\field_config_form::mock_submit($submitdata, []); + $handler = $this->cfcat->get_handler(); + $form = $handler->get_field_config_form($this->cfields[1]); + $this->assertTrue($form->is_validated()); + $data = $form->get_data(); + $handler->save_field_configuration($this->cfields[1], $data); + } + + /** + * Test for instance form functions + */ + public function test_instance_form() { + global $CFG; + require_once($CFG->dirroot . '/customfield/tests/fixtures/test_instance_form.php'); + $this->setAdminUser(); + $handler = $this->cfcat->get_handler(); + + // First try to submit without required field. + $submitdata = (array)$this->courses[1]; + core_customfield_test_instance_form::mock_submit($submitdata, []); + $form = new core_customfield_test_instance_form('POST', + ['handler' => $handler, 'instance' => $this->courses[1]]); + $this->assertFalse($form->is_validated()); + + // Now with required field. + $submitdata['customfield_myfield2'] = time(); + core_customfield_test_instance_form::mock_submit($submitdata, []); + $form = new core_customfield_test_instance_form('POST', + ['handler' => $handler, 'instance' => $this->courses[1]]); + $this->assertTrue($form->is_validated()); + + $data = $form->get_data(); + $this->assertEmpty($data->customfield_myfield1); + $this->assertNotEmpty($data->customfield_myfield2); + $handler->instance_form_save($data); + } + + /** + * Test for min/max date validation + */ + public function test_instance_form_validation() { + $this->setAdminUser(); + $handler = $this->cfcat->get_handler(); + $submitdata = (array)$this->courses[1]; + $data = data_controller::create(0, null, $this->cfields[2]); + + // Submit with date less than mindate. + $submitdata['customfield_myfield2'] = 915148800; + $this->assertNotEmpty($data->instance_form_validation($submitdata, [])); + + // Submit with date more than maxdate. + $submitdata['customfield_myfield2'] = 1893557000; + $this->assertNotEmpty($data->instance_form_validation($submitdata, [])); + } + + /** + * Test for data_controller::get_value and export_value + */ + public function test_get_export_value() { + $this->assertEquals(1546300800, $this->cfdata[1]->get_value()); + $this->assertStringMatchesFormat('%a 1 January 2019%a', $this->cfdata[1]->export_value()); + + // Field without data. + $d = core_customfield\data_controller::create(0, null, $this->cfields[2]); + $this->assertEquals(0, $d->get_value()); + $this->assertEquals(null, $d->export_value()); + } + + /** + * Deleting fields and data + */ + public function test_delete() { + $this->cfcat->get_handler()->delete_all(); + } +} diff --git a/customfield/field/select/tests/plugin_test.php b/customfield/field/select/tests/plugin_test.php new file mode 100644 index 0000000000000..de630d5e1d4c8 --- /dev/null +++ b/customfield/field/select/tests/plugin_test.php @@ -0,0 +1,166 @@ +. + +/** + * Tests for class customfield_select + * + * @package customfield_select + * @copyright 2019 Marina Glancy + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +use customfield_select\field_controller; +use customfield_select\data_controller; + +/** + * Functional test for customfield_select + * + * @package customfield_select + * @copyright 2019 Marina Glancy + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class customfield_select_plugin_testcase extends advanced_testcase { + + /** @var stdClass[] */ + private $courses = []; + /** @var \core_customfield\category_controller */ + private $cfcat; + /** @var \core_customfield\field_controller[] */ + private $cfields; + /** @var \core_customfield\data_controller[] */ + private $cfdata; + + /** + * Tests set up. + */ + public function setUp() { + $this->resetAfterTest(); + + $this->cfcat = $this->get_generator()->create_category(); + + $this->cfields[1] = $this->get_generator()->create_field( + ['categoryid' => $this->cfcat->get('id'), 'shortname' => 'myfield1', 'type' => 'select', + 'configdata' => ['options' => "a\nb\nc"]]); + $this->cfields[2] = $this->get_generator()->create_field( + ['categoryid' => $this->cfcat->get('id'), 'shortname' => 'myfield2', 'type' => 'select', + 'configdata' => ['required' => 1, 'options' => "a\nb\nc"]]); + $this->cfields[3] = $this->get_generator()->create_field( + ['categoryid' => $this->cfcat->get('id'), 'shortname' => 'myfield3', 'type' => 'select', + 'configdata' => ['defaultvalue' => 'b', 'options' => "a\nb\nc"]]); + + $this->courses[1] = $this->getDataGenerator()->create_course(); + $this->courses[2] = $this->getDataGenerator()->create_course(); + $this->courses[3] = $this->getDataGenerator()->create_course(); + + $this->cfdata[1] = $this->get_generator()->add_instance_data($this->cfields[1], $this->courses[1]->id, 1); + $this->cfdata[2] = $this->get_generator()->add_instance_data($this->cfields[1], $this->courses[2]->id, 1); + + $this->setUser($this->getDataGenerator()->create_user()); + } + + /** + * Get generator + * @return core_customfield_generator + */ + protected function get_generator(): core_customfield_generator { + return $this->getDataGenerator()->get_plugin_generator('core_customfield'); + } + + /** + * Test for initialising field and data controllers + */ + public function test_initialise() { + $f = \core_customfield\field_controller::create($this->cfields[1]->get('id')); + $this->assertTrue($f instanceof field_controller); + + $f = \core_customfield\field_controller::create(0, (object)['type' => 'select'], $this->cfcat); + $this->assertTrue($f instanceof field_controller); + + $d = \core_customfield\data_controller::create($this->cfdata[1]->get('id')); + $this->assertTrue($d instanceof data_controller); + + $d = \core_customfield\data_controller::create(0, null, $this->cfields[1]); + $this->assertTrue($d instanceof data_controller); + } + + /** + * Test for configuration form functions + * + * Create a configuration form and submit it with the same values as in the field + */ + public function test_config_form() { + $submitdata = (array)$this->cfields[1]->to_record(); + $submitdata['configdata'] = $this->cfields[1]->get('configdata'); + + \core_customfield\field_config_form::mock_submit($submitdata, []); + $handler = $this->cfcat->get_handler(); + $form = $handler->get_field_config_form($this->cfields[1]); + $this->assertTrue($form->is_validated()); + $data = $form->get_data(); + $handler->save_field_configuration($this->cfields[1], $data); + } + + /** + * Test for instance form functions + */ + public function test_instance_form() { + global $CFG; + require_once($CFG->dirroot . '/customfield/tests/fixtures/test_instance_form.php'); + $this->setAdminUser(); + $handler = $this->cfcat->get_handler(); + + // First try to submit without required field. + $submitdata = (array)$this->courses[1]; + core_customfield_test_instance_form::mock_submit($submitdata, []); + $form = new core_customfield_test_instance_form('POST', + ['handler' => $handler, 'instance' => $this->courses[1]]); + $this->assertFalse($form->is_validated()); + + // Now with required field. + $submitdata['customfield_myfield2'] = 1; + core_customfield_test_instance_form::mock_submit($submitdata, []); + $form = new core_customfield_test_instance_form('POST', + ['handler' => $handler, 'instance' => $this->courses[1]]); + $this->assertTrue($form->is_validated()); + + $data = $form->get_data(); + $this->assertNotEmpty($data->customfield_myfield1); + $this->assertNotEmpty($data->customfield_myfield2); + $handler->instance_form_save($data); + } + + /** + * Test for data_controller::get_value and export_value + */ + public function test_get_export_value() { + $this->assertEquals(1, $this->cfdata[1]->get_value()); + $this->assertEquals('a', $this->cfdata[1]->export_value()); + + // Field without data but with a default value. + $d = core_customfield\data_controller::create(0, null, $this->cfields[3]); + $this->assertEquals(2, $d->get_value()); + $this->assertEquals('b', $d->export_value()); + } + + /** + * Deleting fields and data + */ + public function test_delete() { + $this->cfcat->get_handler()->delete_all(); + } +} diff --git a/customfield/field/text/tests/plugin_test.php b/customfield/field/text/tests/plugin_test.php new file mode 100644 index 0000000000000..7a2379fd6e6d6 --- /dev/null +++ b/customfield/field/text/tests/plugin_test.php @@ -0,0 +1,176 @@ +. + +/** + * Tests for class customfield_text + * + * @package customfield_text + * @copyright 2019 Marina Glancy + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +use customfield_text\field_controller; +use customfield_text\data_controller; + +/** + * Functional test for customfield_text + * + * @package customfield_text + * @copyright 2019 Marina Glancy + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class customfield_text_plugin_testcase extends advanced_testcase { + + /** @var stdClass[] */ + private $courses = []; + /** @var \core_customfield\category_controller */ + private $cfcat; + /** @var \core_customfield\field_controller[] */ + private $cfields; + /** @var \core_customfield\data_controller[] */ + private $cfdata; + + /** + * Tests set up. + */ + public function setUp() { + $this->resetAfterTest(); + + $this->cfcat = $this->get_generator()->create_category(); + + $this->cfields[1] = $this->get_generator()->create_field( + ['categoryid' => $this->cfcat->get('id'), 'shortname' => 'myfield1', 'type' => 'text', + 'configdata' => ['maxlength' => 30, 'displaysize' => 50]]); + $this->cfields[2] = $this->get_generator()->create_field( + ['categoryid' => $this->cfcat->get('id'), 'shortname' => 'myfield2', 'type' => 'text', + 'configdata' => ['required' => 1, 'maxlength' => 30, 'displaysize' => 50]]); + $this->cfields[3] = $this->get_generator()->create_field( + ['categoryid' => $this->cfcat->get('id'), 'shortname' => 'myfield3', 'type' => 'text', + 'configdata' => ['defaultvalue' => 'Defvalue', 'maxlength' => 30, 'displaysize' => 50]]); + $this->cfields[4] = $this->get_generator()->create_field( + ['categoryid' => $this->cfcat->get('id'), 'shortname' => 'myfield4', 'type' => 'text', + 'configdata' => ['link' => 'https://twitter.com/$$', 'maxlength' => 30, 'displaysize' => 50]]); + + $this->courses[1] = $this->getDataGenerator()->create_course(); + $this->courses[2] = $this->getDataGenerator()->create_course(); + $this->courses[3] = $this->getDataGenerator()->create_course(); + + $this->cfdata[1] = $this->get_generator()->add_instance_data($this->cfields[1], $this->courses[1]->id, + 'Value1'); + $this->cfdata[2] = $this->get_generator()->add_instance_data($this->cfields[1], $this->courses[2]->id, + 'Value2'); + + $this->setUser($this->getDataGenerator()->create_user()); + } + + /** + * Get generator + * @return core_customfield_generator + */ + protected function get_generator(): core_customfield_generator { + return $this->getDataGenerator()->get_plugin_generator('core_customfield'); + } + + /** + * Test for initialising field and data controllers + */ + public function test_initialise() { + $f = \core_customfield\field_controller::create($this->cfields[1]->get('id')); + $this->assertTrue($f instanceof field_controller); + + $f = \core_customfield\field_controller::create(0, (object)['type' => 'text'], $this->cfcat); + $this->assertTrue($f instanceof field_controller); + + $d = \core_customfield\data_controller::create($this->cfdata[1]->get('id')); + $this->assertTrue($d instanceof data_controller); + + $d = \core_customfield\data_controller::create(0, null, $this->cfields[1]); + $this->assertTrue($d instanceof data_controller); + } + + /** + * Test for configuration form functions + * + * Create a configuration form and submit it with the same values as in the field + */ + public function test_config_form() { + $submitdata = (array)$this->cfields[1]->to_record(); + $submitdata['configdata'] = $this->cfields[1]->get('configdata'); + + \core_customfield\field_config_form::mock_submit($submitdata, []); + $handler = $this->cfcat->get_handler(); + $form = $handler->get_field_config_form($this->cfields[1]); + $this->assertTrue($form->is_validated()); + $data = $form->get_data(); + $handler->save_field_configuration($this->cfields[1], $data); + } + + /** + * Test for instance form functions + */ + public function test_instance_form() { + global $CFG; + require_once($CFG->dirroot . '/customfield/tests/fixtures/test_instance_form.php'); + $this->setAdminUser(); + $handler = $this->cfcat->get_handler(); + + // First try to submit without required field. + $submitdata = (array)$this->courses[1]; + core_customfield_test_instance_form::mock_submit($submitdata, []); + $form = new core_customfield_test_instance_form('POST', + ['handler' => $handler, 'instance' => $this->courses[1]]); + $this->assertFalse($form->is_validated()); + + // Now with required field. + $submitdata['customfield_myfield2'] = 'Some text'; + core_customfield_test_instance_form::mock_submit($submitdata, []); + $form = new core_customfield_test_instance_form('POST', + ['handler' => $handler, 'instance' => $this->courses[1]]); + $this->assertTrue($form->is_validated()); + + $data = $form->get_data(); + $this->assertNotEmpty($data->customfield_myfield1); + $this->assertNotEmpty($data->customfield_myfield2); + $handler->instance_form_save($data); + } + + /** + * Test for data_controller::get_value and export_value + */ + public function test_get_export_value() { + $this->assertEquals('Value1', $this->cfdata[1]->get_value()); + $this->assertEquals('Value1', $this->cfdata[1]->export_value()); + + // Field without data but with a default value. + $d = core_customfield\data_controller::create(0, null, $this->cfields[3]); + $this->assertEquals('Defvalue', $d->get_value()); + $this->assertEquals('Defvalue', $d->export_value()); + + // Field with a link. + $d = $this->get_generator()->add_instance_data($this->cfields[4], $this->courses[1]->id, 'mynickname'); + $this->assertEquals('mynickname', $d->get_value()); + $this->assertEquals('mynickname', $d->export_value()); + } + + /** + * Deleting fields and data + */ + public function test_delete() { + $this->cfcat->get_handler()->delete_all(); + } +} diff --git a/customfield/field/textarea/lib.php b/customfield/field/textarea/lib.php new file mode 100644 index 0000000000000..83389f37dad36 --- /dev/null +++ b/customfield/field/textarea/lib.php @@ -0,0 +1,76 @@ +. + +/** + * Callbacks + * + * @package customfield_textarea + * @copyright 2018 Marina Glancy + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die; + +/** + * Serve the files from the customfield_textarea file areas + * + * @param stdClass $course the course object + * @param stdClass $cm the course module object + * @param context $context the context + * @param string $filearea the name of the file area + * @param array $args extra arguments (itemid, path) + * @param bool $forcedownload whether or not force download + * @param array $options additional options affecting the file serving + * @return bool false if the file not found, just send the file otherwise and do not return + */ +function customfield_textarea_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) { + global $DB; + + $itemid = array_shift($args); + if ($filearea === 'value') { + // Value of the data, itemid = id in data table. + $datarecord = $DB->get_record(\core_customfield\data::TABLE, ['id' => $itemid], '*', MUST_EXIST); + $field = \core_customfield\field_controller::create($datarecord->fieldid); + $data = \core_customfield\data_controller::create(0, $datarecord, $field); + $handler = $field->get_handler(); + if ($field->get('type') !== 'textarea' || !$handler->can_view($field, $data->get('instanceid')) + || $data->get_context()->id != $context->id) { + send_file_not_found(); + } + } else if ($filearea === 'defaultvalue') { + // Default value of the field, itemid = id in the field table. + $field = \core_customfield\field_controller::create($itemid); + $handler = $field->get_handler(); + if ($field->get('type') !== 'textarea' || $handler->get_configuration_context()->id != $context->id) { + send_file_not_found(); + } + } else { + send_file_not_found(); + } + + $filename = array_pop($args); // The last item in the $args array. + $filepath = '/' . ($args ? implode('/', $args) . '/' : ''); + + // Retrieve the file from the Files API. + $fs = get_file_storage(); + $file = $fs->get_file($context->id, 'customfield_textarea', $filearea, $itemid, $filepath, $filename); + if (!$file) { + send_file_not_found(); + } + + // We can now send the file back to the browser - in this case with a cache lifetime of 1 day and no filtering. + send_file($file, 86400, 0, $forcedownload, $options); +} diff --git a/customfield/field/textarea/tests/plugin_test.php b/customfield/field/textarea/tests/plugin_test.php new file mode 100644 index 0000000000000..2e8619d7ce2cb --- /dev/null +++ b/customfield/field/textarea/tests/plugin_test.php @@ -0,0 +1,167 @@ +. + +/** + * Tests for class customfield_textarea + * + * @package customfield_textarea + * @copyright 2019 Marina Glancy + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +use customfield_textarea\field_controller; +use customfield_textarea\data_controller; + +/** + * Functional test for customfield_textarea + * + * @package customfield_textarea + * @copyright 2019 Marina Glancy + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class customfield_textarea_plugin_testcase extends advanced_testcase { + + /** @var stdClass[] */ + private $courses = []; + /** @var \core_customfield\category_controller */ + private $cfcat; + /** @var \core_customfield\field_controller[] */ + private $cfields; + /** @var \core_customfield\data_controller[] */ + private $cfdata; + + /** + * Tests set up. + */ + public function setUp() { + $this->resetAfterTest(); + + $this->cfcat = $this->get_generator()->create_category(); + + $this->cfields[1] = $this->get_generator()->create_field( + ['categoryid' => $this->cfcat->get('id'), 'shortname' => 'myfield1', 'type' => 'textarea']); + $this->cfields[2] = $this->get_generator()->create_field( + ['categoryid' => $this->cfcat->get('id'), 'shortname' => 'myfield2', 'type' => 'textarea', + 'configdata' => ['required' => 1]]); + $this->cfields[3] = $this->get_generator()->create_field( + ['categoryid' => $this->cfcat->get('id'), 'shortname' => 'myfield3', 'type' => 'textarea', + 'configdata' => ['defaultvalue' => 'Value3', 'defaultvalueformat' => FORMAT_MOODLE]]); + + $this->courses[1] = $this->getDataGenerator()->create_course(); + $this->courses[2] = $this->getDataGenerator()->create_course(); + $this->courses[3] = $this->getDataGenerator()->create_course(); + + $this->cfdata[1] = $this->get_generator()->add_instance_data($this->cfields[1], $this->courses[1]->id, + ['text' => 'Value1', 'format' => FORMAT_MOODLE]); + $this->cfdata[2] = $this->get_generator()->add_instance_data($this->cfields[1], $this->courses[2]->id, + ['text' => 'Value2', 'format' => FORMAT_MOODLE]); + + $this->setUser($this->getDataGenerator()->create_user()); + } + + /** + * Get generator + * @return core_customfield_generator + */ + protected function get_generator(): core_customfield_generator { + return $this->getDataGenerator()->get_plugin_generator('core_customfield'); + } + + /** + * Test for initialising field and data controllers + */ + public function test_initialise() { + $f = \core_customfield\field_controller::create($this->cfields[1]->get('id')); + $this->assertTrue($f instanceof field_controller); + + $f = \core_customfield\field_controller::create(0, (object)['type' => 'textarea'], $this->cfcat); + $this->assertTrue($f instanceof field_controller); + + $d = \core_customfield\data_controller::create($this->cfdata[1]->get('id')); + $this->assertTrue($d instanceof data_controller); + + $d = \core_customfield\data_controller::create(0, null, $this->cfields[1]); + $this->assertTrue($d instanceof data_controller); + } + + /** + * Test for configuration form functions + * + * Create a configuration form and submit it with the same values as in the field + */ + public function test_config_form() { + $submitdata = (array)$this->cfields[3]->to_record(); + $submitdata['configdata'] = $this->cfields[3]->get('configdata'); + + \core_customfield\field_config_form::mock_submit($submitdata, []); + $handler = $this->cfcat->get_handler(); + $form = $handler->get_field_config_form($this->cfields[3]); + $this->assertTrue($form->is_validated()); + $data = $form->get_data(); + $handler->save_field_configuration($this->cfields[3], $data); + } + + /** + * Test for instance form functions + */ + public function test_instance_form() { + global $CFG; + require_once($CFG->dirroot . '/customfield/tests/fixtures/test_instance_form.php'); + $this->setAdminUser(); + $handler = $this->cfcat->get_handler(); + + // First try to submit without required field. + $submitdata = (array)$this->courses[1]; + core_customfield_test_instance_form::mock_submit($submitdata, []); + $form = new core_customfield_test_instance_form('POST', + ['handler' => $handler, 'instance' => $this->courses[1]]); + $this->assertFalse($form->is_validated()); + + // Now with required field. + $submitdata['customfield_myfield2_editor'] = ['text' => 'Some text', 'format' => FORMAT_HTML]; + core_customfield_test_instance_form::mock_submit($submitdata, []); + $form = new core_customfield_test_instance_form('POST', + ['handler' => $handler, 'instance' => $this->courses[1]]); + $this->assertTrue($form->is_validated()); + + $data = $form->get_data(); + $this->assertNotEmpty($data->customfield_myfield1_editor); + $this->assertNotEmpty($data->customfield_myfield2_editor); + $handler->instance_form_save($data); + } + + /** + * Test for data_controller::get_value and export_value + */ + public function test_get_export_value() { + $this->assertEquals('Value1', $this->cfdata[1]->get_value()); + $this->assertEquals('
Value1
', $this->cfdata[1]->export_value()); + + // Field without data but with a default value. + $d = core_customfield\data_controller::create(0, null, $this->cfields[3]); + $this->assertEquals('Value3', $d->get_value()); + $this->assertEquals('
Value3
', $d->export_value()); + } + + /** + * Deleting fields and data + */ + public function test_delete() { + $this->cfcat->get_handler()->delete_all(); + } +} diff --git a/customfield/lib.php b/customfield/lib.php new file mode 100644 index 0000000000000..b3ec2d66adf9a --- /dev/null +++ b/customfield/lib.php @@ -0,0 +1,85 @@ +. + +/** + * Callbacks + * + * @package core_customfield + * @copyright 2018 Marina Glancy + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die; + +/** + * Edit customfield elements inplace + * + * @param string $itemtype + * @param int $itemid + * @param string $newvalue + * @return \core\output\inplace_editable + */ +function core_customfield_inplace_editable($itemtype, $itemid, $newvalue) { + if ($itemtype === 'category') { + $category = core_customfield\category_controller::create($itemid); + $handler = $category->get_handler(); + \external_api::validate_context($handler->get_configuration_context()); + if (!$handler->can_configure()) { + throw new moodle_exception('nopermissionconfigure', 'core_customfield'); + } + $newvalue = clean_param($newvalue, PARAM_NOTAGS); + $handler->rename_category($category, $newvalue); + return \core_customfield\api::get_category_inplace_editable($category, true); + } +} + +/** + * Serve the files from the core_customfield file areas + * + * @param stdClass $course the course object + * @param stdClass $cm the course module object + * @param context $context the context + * @param string $filearea the name of the file area + * @param array $args extra arguments (itemid, path) + * @param bool $forcedownload whether or not force download + * @param array $options additional options affecting the file serving + * @return bool false if the file not found, just send the file otherwise and do not return + */ +function core_customfield_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) { + if ($filearea !== 'description') { + return false; + } + + $itemid = array_shift($args); + $filename = array_pop($args); // The last item in the $args array. + + $field = \core_customfield\field_controller::create($itemid); + $handler = $field->get_handler(); + if ($handler->get_configuration_context()->id != $context->id) { + return false; + } + + // Retrieve the file from the Files API. + $fs = get_file_storage(); + $file = $fs->get_file($context->id, 'core_customfield', $filearea, $itemid, '/', $filename); + if (!$file) { + return false; // The file does not exist. + } + + // We can now send the file back to the browser - in this case with a cache lifetime of 1 day and no filtering. + // From Moodle 2.3, use send_stored_file instead. + send_file($file, 86400, 0, $forcedownload, $options); +} diff --git a/customfield/tests/fixtures/test_instance_form.php b/customfield/tests/fixtures/test_instance_form.php new file mode 100644 index 0000000000000..97fce043a16be --- /dev/null +++ b/customfield/tests/fixtures/test_instance_form.php @@ -0,0 +1,79 @@ +. + +/** + * Class core_customfield_test_instance_form + * + * @package core_customfield + * @copyright 2019 Marina Glancy + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->libdir . '/formslib.php'); + +/** + * Class core_customfield_test_instance_form + * + * @package core_customfield + * @copyright 2019 Marina Glancy + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class core_customfield_test_instance_form extends moodleform { + /** @var \core_customfield\handler */ + protected $handler; + + /** @var stdClass */ + protected $instance; + + /** + * Form definition + */ + public function definition() { + $this->handler = $this->_customdata['handler']; + $this->instance = $this->_customdata['instance']; + + $this->_form->addElement('hidden', 'id'); + $this->_form->setType('id', PARAM_INT); + + $this->handler->instance_form_definition($this->_form, $this->instance->id); + + $this->add_action_buttons(); + + $this->handler->instance_form_before_set_data($this->instance); + $this->set_data($this->instance); + } + + /** + * Definition after data + */ + public function definition_after_data() { + $this->handler->instance_form_definition_after_data($this->_form, $this->instance->id); + } + + /** + * Form validation + * + * @param array $data + * @param array $files + * @return array + */ + public function validation($data, $files) { + return $this->handler->instance_form_validation($data, $files); + } +} diff --git a/customfield/tests/privacy_test.php b/customfield/tests/privacy_test.php new file mode 100644 index 0000000000000..8dfdb416d1f07 --- /dev/null +++ b/customfield/tests/privacy_test.php @@ -0,0 +1,250 @@ +. + +/** + * Class provider_test + * + * @package core_customfield + * @copyright 2019 Marina Glancy + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +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 core_customfield_privacy_testcase extends provider_testcase { + + /** @var stdClass[] */ + private $courses = []; + /** @var \core_customfield\category_controller[] */ + private $cfcats = []; + /** @var \core_customfield\field_controller[] */ + private $cffields = []; + + /** + * Set up + */ + public function setUp() { + $this->resetAfterTest(); + + $this->cfcats[1] = $this->get_generator()->create_category(); + $this->cfcats[2] = $this->get_generator()->create_category(); + $this->cffields[11] = $this->get_generator()->create_field( + ['categoryid' => $this->cfcats[1]->get('id'), 'type' => 'checkbox']); + $this->cffields[12] = $this->get_generator()->create_field( + ['categoryid' => $this->cfcats[1]->get('id'), 'type' => 'date']); + $this->cffields[13] = $this->get_generator()->create_field( + ['categoryid' => $this->cfcats[1]->get('id'), + 'type' => 'select', 'configdata' => ['options' => "a\nb\nc"]]); + $this->cffields[14] = $this->get_generator()->create_field( + ['categoryid' => $this->cfcats[1]->get('id'), 'type' => 'text']); + $this->cffields[15] = $this->get_generator()->create_field( + ['categoryid' => $this->cfcats[1]->get('id'), 'type' => 'textarea']); + $this->cffields[21] = $this->get_generator()->create_field( + ['categoryid' => $this->cfcats[2]->get('id')]); + $this->cffields[22] = $this->get_generator()->create_field( + ['categoryid' => $this->cfcats[2]->get('id')]); + + $this->courses[1] = $this->getDataGenerator()->create_course(); + $this->courses[2] = $this->getDataGenerator()->create_course(); + $this->courses[3] = $this->getDataGenerator()->create_course(); + + $this->get_generator()->add_instance_data($this->cffields[11], $this->courses[1]->id, 1); + $this->get_generator()->add_instance_data($this->cffields[12], $this->courses[1]->id, 1546300800); + $this->get_generator()->add_instance_data($this->cffields[13], $this->courses[1]->id, 2); + $this->get_generator()->add_instance_data($this->cffields[14], $this->courses[1]->id, 'Hello1'); + $this->get_generator()->add_instance_data($this->cffields[15], $this->courses[1]->id, + ['text' => '

Hi there

', 'format' => FORMAT_HTML]); + + $this->get_generator()->add_instance_data($this->cffields[21], $this->courses[1]->id, 'hihi1'); + + $this->get_generator()->add_instance_data($this->cffields[14], $this->courses[2]->id, 'Hello2'); + + $this->get_generator()->add_instance_data($this->cffields[21], $this->courses[2]->id, 'hihi2'); + + $this->setUser($this->getDataGenerator()->create_user()); + } + + /** + * Get generator + * @return core_customfield_generator + */ + protected function get_generator(): core_customfield_generator { + return $this->getDataGenerator()->get_plugin_generator('core_customfield'); + } + + /** + * 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; + list($sql, $params) = $DB->get_in_or_equal([$this->courses[1]->id, $this->courses[2]->id], SQL_PARAMS_NAMED); + $r = provider::get_customfields_data_contexts('core_course', 'course', '=0', + $sql, $params); + $this->assertEquals([context_course::instance($this->courses[1]->id)->id, + context_course::instance($this->courses[2]->id)->id], + $r->get_contextids(), '', 0, 10, true); + } + + /** + * Test for provider::get_customfields_configuration_contexts() + */ + public function test_get_customfields_configuration_contexts() { + $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; + // Hack one of the fields so it has an invalid field type. + $invalidfieldid = $this->cffields[21]->get('id'); + $DB->update_record('customfield_field', ['id' => $invalidfieldid, 'type' => 'invalid']); + + $context = context_course::instance($this->courses[1]->id); + $contextlist = new approved_contextlist($USER, 'core_customfield', [$context->id]); + provider::export_customfields_data($contextlist, 'core_course', 'course', '=0', '=:i', ['i' => $this->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 == $this->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; + $approvedcontexts = new approved_contextlist($USER, 'core_course', [context_course::instance($this->courses[1]->id)->id]); + provider::delete_customfields_data($approvedcontexts, 'core_course', 'course'); + $this->assertEmpty($DB->get_records('customfield_data', ['instanceid' => $this->courses[1]->id])); + $this->assertNotEmpty($DB->get_records('customfield_data', ['instanceid' => $this->courses[2]->id])); + } + + /** + * Test for provider::delete_customfields_configuration() + */ + public function test_delete_customfields_configuration() { + global $USER, $DB; + // Remember the list of fields in the category 2 before we delete it. + $catid1 = $this->cfcats[1]->get('id'); + $catid2 = $this->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; + // Remember the list of fields in the category 2 before we delete it. + $catid1 = $this->cfcats[1]->get('id'); + $catid2 = $this->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; + provider::delete_customfields_data_for_context('core_course', 'course', + context_course::instance($this->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'] = $this->courses[1]->id; + $fparams['course2'] = $this->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/lang/en/admin.php b/lang/en/admin.php index dbb7577c9c7b2..c2e304a748624 100644 --- a/lang/en/admin.php +++ b/lang/en/admin.php @@ -729,6 +729,7 @@ $string['managecontextlocklocked'] = '{$a->contextname} and any lower contexts are now frozen.'; $string['managecontextlockunlocked'] = '{$a->contextname} and any lower contexts are now unfrozen.'; $string['managecontextunlock'] = 'Unfreeze this context'; +$string['managecustomfields'] = 'Manage custom field types'; $string['manageformats'] = 'Manage course formats'; $string['manageformatsgotosettings'] = 'Default format can be changed in {$a}'; $string['managelang'] = 'Manage'; @@ -1384,3 +1385,4 @@ $string['hubs'] = 'Hubs'; $string['configloginhttps'] = 'Turning this on will make Moodle use a secure https connection just for the login page (providing a secure login), and then afterwards revert back to the normal http URL for general speed. CAUTION: this setting REQUIRES https to be specifically enabled on the web server - if it is not then YOU COULD LOCK YOURSELF OUT OF YOUR SITE.'; $string['loginhttps'] = 'Use HTTPS for logins'; +$string['course_customfield'] = 'Course custom fields'; diff --git a/lib/setuplib.php b/lib/setuplib.php index 68c283a2832b4..b9ab72f8683db 100644 --- a/lib/setuplib.php +++ b/lib/setuplib.php @@ -1407,7 +1407,7 @@ function disable_output_buffering() { */ function is_major_upgrade_required() { global $CFG; - $lastmajordbchanges = 2018111301.00; + $lastmajordbchanges = 2019011101.00; $required = empty($CFG->version); $required = $required || (float)$CFG->version < $lastmajordbchanges; diff --git a/lib/tests/behat/behat_general.php b/lib/tests/behat/behat_general.php index 13ac090a8a17c..7adcfd9e1c8dd 100644 --- a/lib/tests/behat/behat_general.php +++ b/lib/tests/behat/behat_general.php @@ -1446,6 +1446,46 @@ function($context, $args) { } } + /** + * Checks that the image on the page is the same as one of the fixture files + * + * @Then /^the image at "(?P(?:[^"]|\\")*)" "(?P[^"]*)" should be identical to "(?P(?:[^"]|\\")*)"$/ + * @throws ExpectationException + * @param string $element The locator of the image + * @param string $selectortype The selector type + * @param string $filepath path to the fixture file + */ + public function the_image_at_should_be_identical_to($element, $selectortype, $filepath) { + global $CFG; + + // Get the container node (exception if it doesn't exist). + $containernode = $this->get_selected_node($selectortype, $element); + $url = $containernode->getAttribute('src'); + if ($url == null) { + throw new ExpectationException('Element does not have src attribute', + $this->getSession()); + } + $session = $this->getSession()->getCookie('MoodleSession'); + $content = download_file_content($url, array('Cookie' => 'MoodleSession=' . $session)); + + // Get the content of the fixture file. + // Replace 'admin/' if it is in start of path with $CFG->admin . + if (substr($filepath, 0, 6) === 'admin/') { + $filepath = $CFG->admin . DIRECTORY_SEPARATOR . substr($filepath, 6); + } + $filepath = str_replace('/', DIRECTORY_SEPARATOR, $filepath); + $filepath = $CFG->dirroot . DIRECTORY_SEPARATOR . $filepath; + if (!is_readable($filepath)) { + throw new ExpectationException('The file to compare to does not exist.', $this->getSession()); + } + $expectedcontent = file_get_contents($filepath); + + if ($content !== $expectedcontent) { + throw new ExpectationException('Image is not identical to the fixture. Received ' . + strlen($content) . ' bytes and expected ' . strlen($expectedcontent) . ' bytes'); + } + } + /** * Prepare to detect whether or not a new page has loaded (or the same page reloaded) some time in the future. * diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 60af5a0d630be..075bd95a433d2 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -92,6 +92,9 @@ blog/tests + + customfield/tests + iplookup/tests