lib/babel-polyfill/
lib/polyfills/
lib/emoji-data/
+lib/plist/
media/player/videojs/amd/src/video-lazy.js
media/player/videojs/amd/src/Youtube-lazy.js
media/player/videojs/videojs/
lib/babel-polyfill/
lib/polyfills/
lib/emoji-data/
+lib/plist/
media/player/videojs/amd/src/video-lazy.js
media/player/videojs/amd/src/Youtube-lazy.js
media/player/videojs/videojs/
}
}
- $updateinfo .= $this->container_start('checkforupdates');
+ $updateinfo .= $this->container_start('checkforupdates mt-1');
$fetchurl = new moodle_url('/admin/index.php', array('fetchupdates' => 1, 'sesskey' => sesskey(), 'cache' => 0));
$updateinfo .= $this->single_button($fetchurl, get_string('checkforupdates', 'core_plugin'));
if ($fetch) {
*/
protected function moodle_available_update_info(\core\update\info $updateinfo) {
- $boxclasses = 'moodleupdateinfo';
+ $boxclasses = 'moodleupdateinfo mb-2';
$info = array();
if (isset($updateinfo->release)) {
}
if (isset($updateinfo->download)) {
- $info[] = html_writer::link($updateinfo->download, get_string('download'), array('class' => 'info download'));
+ $info[] = html_writer::link($updateinfo->download, get_string('download'),
+ array('class' => 'info download btn btn-secondary'));
}
if (isset($updateinfo->url)) {
array('class' => 'info more'));
}
- $box = $this->output->box_start($boxclasses);
- $box .= $this->output->box(implode(html_writer::tag('span', ' ', array('class' => 'separator')), $info), '');
- $box .= $this->output->box_end();
+ $box = $this->output->container_start($boxclasses);
+ $box .= $this->output->container(implode(html_writer::tag('span', ' | ', array('class' => 'separator')), $info), '');
+ $box .= $this->output->container_end();
return $box;
}
$temp = new admin_settingpage('experimentalsettings', new lang_string('experimentalsettings', 'admin'));
//TODO: Re-enable cc-import once re-implemented in 2.0.x
//$temp->add(new admin_setting_configcheckbox('enableimsccimport', new lang_string('enable_cc_import', 'imscc'), new lang_string('enable_cc_import_description', 'imscc'), 0));
- $temp->add(new admin_setting_configcheckbox('enablesafebrowserintegration', new lang_string('enablesafebrowserintegration', 'admin'), new lang_string('configenablesafebrowserintegration', 'admin'), 0));
$temp->add(new admin_setting_configcheckbox('dndallowtextandlinks', new lang_string('dndallowtextandlinks', 'admin'), new lang_string('configdndallowtextandlinks', 'admin'), 0));
$string['classname'] = 'Class name';
$string['component'] = 'Component';
-$string['configmessageinboundhost'] = 'The address of the server that Moodle should check mail against. To specify a non-default port, use [server]:[port], for example mail.example.com:587. If a port isn\'t specified, the default port for the type of mail server will be used.';
+$string['configmessageinboundhost'] = 'The address of the server that Moodle should check mail against. To specify a non-default port, use [server]:[port], for example mail.example.com:993. If a port isn\'t specified, the default port for the type of mail server will be used.';
$string['defaultexpiration'] = 'Default address expiry period';
$string['defaultexpiration_help'] = 'When an email address is generated by the handler, it can be set to automatically expire after a period of time, so that it can no longer be used. It is advisable to set an expiry period.';
$string['description'] = 'Description';
And I press "Yes"
And I should see "Recycle bin has been emptied"
And I should see "There are no items in the recycle bin."
+
+ @javascript
+ Scenario: Show recycle bin on category action menu
+ Given I log in as "admin"
+ And I navigate to "Courses > Manage courses and categories" in site administration
+ And I click on "Actions menu" "link"
+ And I click on "Recycle bin" "link"
+ Then I should see "There are no items in the recycle bin."
+
+ @javascript
+ Scenario: Not show recycle bin empty on category action menu whit autohide enable
+ Given I log in as "admin"
+ And the following config values are set as admin:
+ | categorybinenable | 0 | tool_recyclebin |
+ And I navigate to "Courses > Manage courses and categories" in site administration
+ And I click on "Actions menu" "link"
+ Then I should not see "Recycle bin"
+
+ @javascript
+ Scenario: Show recycle bin not empty on category action menu whit autohide enable
+ Given I log in as "admin"
+ And the following config values are set as admin:
+ | autohide | 1 | tool_recyclebin |
+ And I navigate to "Courses > Manage courses and categories" in site administration
+ And I click on "Actions menu" "link"
+ Then I should not see "Recycle bin"
+ And I click on "delete" action for "Course 2" in management course listing
+ And I press "Delete"
+ And I should see "Deleting C2"
+ And I should see "C2 has been completely deleted"
+ And I press "Continue"
+ When I click on "Actions menu" "link"
+ Then I should see "Recycle bin"
*/
$string['asap'] = 'ASAP';
-$string['adhocempty'] = 'Adhoc task queue is empty';
-$string['adhocqueuesize'] = 'Adhoc task queue has {$a} tasks';
+$string['adhocempty'] = 'Ad hoc task queue is empty';
+$string['adhocqueuesize'] = 'Ad hoc task queue has {$a} tasks';
$string['adhocqueueold'] = 'Oldest task is {$a->age} which is more than {$a->max}';
$string['backtoscheduledtasks'] = 'Back to scheduled tasks';
$string['blocking'] = 'Blocking';
$string['cannotfindthepathtothecli'] = 'Cannot find the path to the PHP CLI executable so task execution aborted. Set the \'Path to PHP CLI\' setting in Site administration / Server / System paths.';
-$string['checkadhocqueue'] = 'Adhoc task queue';
+$string['checkadhocqueue'] = 'Ad hoc task queue';
$string['checkcronrunning'] = 'Cron running';
$string['checkmaxfaildelay'] = 'Tasks max fail delay';
$string['clearfaildelay_confirm'] = 'Are you sure you want to clear the fail delay for task \'{$a}\'? After clearing the delay, the task will run according to its normal schedule.';
$string['scheduledtasks'] = 'Scheduled tasks';
$string['scheduledtaskchangesdisabled'] = 'Modifications to the list of scheduled tasks have been prevented in Moodle configuration';
$string['taskdisabled'] = 'Task disabled';
-$string['taskfailures'] = 'There are {$a} task(s) failing';
+$string['taskfailures'] = '{$a} task(s) failing';
$string['tasklogs'] = 'Task logs';
$string['tasknofailures'] = 'There are no tasks failing';
$string['taskscheduleday'] = 'Day';
return $this->errors;
}
+ /**
+ * Return array of valid fields for default values
+ *
+ * @return array
+ */
+ protected function get_valid_fields() {
+ return array_merge(self::$validfields, \tool_uploadcourse_helper::get_custom_course_field_names());
+ }
+
/**
* Assemble the course data based on defaults.
*
* @return array
*/
protected function get_final_create_data($data) {
- foreach (self::$validfields as $field) {
+ foreach ($this->get_valid_fields() as $field) {
if (!isset($data[$field]) && isset($this->defaults[$field])) {
$data[$field] = $this->defaults[$field];
}
global $DB;
$newdata = array();
$existingdata = $DB->get_record('course', array('shortname' => $this->shortname));
- foreach (self::$validfields as $field) {
+ foreach ($this->get_valid_fields() as $field) {
if ($missingonly) {
- if (!is_null($existingdata->$field) and $existingdata->$field !== '') {
+ if (isset($existingdata->$field) and $existingdata->$field !== '') {
continue;
}
}
$coursedata[$rolekey] = $rolename;
}
+ // Custom fields. If the course already exists and mode isn't set to force creation, we can use its context.
+ if ($exists && $mode !== tool_uploadcourse_processor::MODE_CREATE_ALL) {
+ $context = context_course::instance($coursedata['id']);
+ } else {
+ // The category ID is taken from the defaults if it exists, otherwise from course data.
+ $context = context_coursecat::instance($this->defaults['category'] ?? $coursedata['category']);
+ }
+ $customfielddata = tool_uploadcourse_helper::get_custom_course_field_data($this->rawdata, $this->defaults, $context,
+ $errors);
+ if (!empty($errors)) {
+ foreach ($errors as $key => $message) {
+ $this->error($key, $message);
+ }
+
+ return false;
+ }
+
+ foreach ($customfielddata as $name => $value) {
+ $coursedata[$name] = $value;
+ }
+
// Some validation.
if (!empty($coursedata['format']) && !in_array($coursedata['format'], tool_uploadcourse_helper::get_course_formats())) {
$this->error('invalidcourseformat', new lang_string('invalidcourseformat', 'tool_uploadcourse'));
return $rolenames;
}
+ /**
+ * Return array of all custom course fields indexed by their shortname
+ *
+ * @return \core_customfield\field_controller[]
+ */
+ public static function get_custom_course_fields(): array {
+ $result = [];
+
+ $fields = \core_course\customfield\course_handler::create()->get_fields();
+ foreach ($fields as $field) {
+ $result[$field->get('shortname')] = $field;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Return array of custom field element names
+ *
+ * @return string[]
+ */
+ public static function get_custom_course_field_names(): array {
+ $result = [];
+
+ $fields = self::get_custom_course_fields();
+ foreach ($fields as $field) {
+ $controller = \core_customfield\data_controller::create(0, null, $field);
+ $result[] = $controller->get_form_element_name();
+ }
+
+ return $result;
+ }
+
+ /**
+ * Return any elements from passed $data whose key matches one of the custom course fields defined for the site
+ *
+ * @param array $data
+ * @param array $defaults
+ * @param context $context
+ * @param array $errors Will be populated with any errors
+ * @return array
+ */
+ public static function get_custom_course_field_data(array $data, array $defaults, context $context,
+ array &$errors = []): array {
+
+ $fields = self::get_custom_course_fields();
+ $result = [];
+
+ $canchangelockedfields = guess_if_creator_will_have_course_capability('moodle/course:changelockedcustomfields', $context);
+
+ foreach ($data as $name => $originalvalue) {
+ if (preg_match('/^customfield_(?<name>.*)?$/', $name, $matches)
+ && isset($fields[$matches['name']])) {
+
+ $fieldname = $matches['name'];
+ $field = $fields[$fieldname];
+
+ // Skip field if it's locked and user doesn't have capability to change locked fields.
+ if ($field->get_configdata_property('locked') && !$canchangelockedfields) {
+ continue;
+ }
+
+ // Create field data controller.
+ $controller = \core_customfield\data_controller::create(0, null, $field);
+ $controller->set('id', 1);
+
+ $defaultvalue = $defaults["customfield_{$fieldname}"] ?? $controller->get_default_value();
+ $value = (empty($originalvalue) ? $defaultvalue : $field->parse_value($originalvalue));
+
+ // If we initially had a value, but now don't, then reset it to the default.
+ if (!empty($originalvalue) && empty($value)) {
+ $value = $defaultvalue;
+ }
+
+ // Validate data with controller.
+ $fieldformdata = [$controller->get_form_element_name() => $value];
+ $validationerrors = $controller->instance_form_validation($fieldformdata, []);
+ if (count($validationerrors) > 0) {
+ $errors['customfieldinvalid'] = new lang_string('customfieldinvalid', 'tool_uploadcourse',
+ $field->get_formatted_name());
+
+ continue;
+ }
+
+ $controller->set($controller->datafield(), $value);
+
+ // Pass an empty object to the data controller, which will transform it to a correct name/value pair.
+ $instance = new stdClass();
+ $controller->instance_form_before_set_data($instance);
+
+ $result = array_merge($result, (array) $instance);
+ }
+ }
+
+ return $result;
+ }
+
/**
* Helper to increment an ID number.
*
}
return $id;
}
-
-}
+}
\ No newline at end of file
$mform->addHelpButton('defaults[enablecompletion]', 'enablecompletion', 'completion');
}
+ // Add custom fields to the form.
+ $handler = \core_course\customfield\course_handler::create();
+ $handler->instance_form_definition($mform, 0, 'defaultvaluescustomfieldcategory', 'tool_uploadcourse');
+
// Hidden fields.
$mform->addElement('hidden', 'importid');
$mform->setType('importid', PARAM_INT);
$this->add_action_buttons(true, get_string('uploadcourses', 'tool_uploadcourse'));
+ // Prepare custom fields data.
+ $data = (object) $data;
+ $handler->instance_form_before_set_data($data);
+
$this->set_data($data);
}
$enddate = $format->get_default_course_enddate($mform, array('startdate' => 'defaults[startdate]'));
$mform->setDefault('defaults[enddate]', $enddate);
}
+
+ // Tweak the form with values provided by custom fields in use.
+ \core_course\customfield\course_handler::create()->instance_form_definition_after_data($mform);
}
/**
$errors['defaults[enddate]'] = get_string($errorcode, 'error');
}
+ // Custom fields validation.
+ array_merge($errors, \core_course\customfield\course_handler::create()->instance_form_validation($data, $files));
+
return $errors;
}
}
$options = (array) $form2data->options;
$defaults = (array) $form2data->defaults;
+ // Custom field defaults.
+ $customfields = tool_uploadcourse_helper::get_custom_course_field_names();
+ foreach ($customfields as $customfield) {
+ $defaults[$customfield] = $form2data->{$customfield};
+ }
+
// Restorefile deserves its own logic because formslib does not really appreciate
// when the name of a filepicker is an array...
$options['restorefile'] = '';
$string['csvfileerror'] = 'There is something wrong with the format of the CSV file. Please check the number of headings and columns match, and that the delimiter and file encoding are correct: {$a}';
$string['csvline'] = 'Line';
$string['defaultvalues'] = 'Default course values';
+$string['defaultvaluescustomfieldcategory'] = 'Default values for \'{$a}\'';
$string['encoding'] = 'Encoding';
$string['encoding_help'] = 'Encoding of the CSV file.';
$string['errorwhilerestoringcourse'] = 'Error while restoring the course';
$string['nochanges'] = 'No changes';
$string['pluginname'] = 'Course upload';
$string['preview'] = 'Preview';
+$string['customfieldinvalid'] = 'Custom field \'{$a}\' is empty or contains invalid data';
$string['reset'] = 'Reset course after upload';
$string['reset_help'] = 'Whether to reset the course after creating/updating it.';
$string['result'] = 'Result';
And I should see "Course 1"
And I should see "Course 2"
And I should see "Course 3"
+
+ @javascript
+ Scenario: Creation of new courses with custom fields
+ Given the following "custom field categories" exist:
+ | name | component | area | itemid |
+ | Other | core_course | course | 0 |
+ And the following "custom fields" exist:
+ | name | category | type | shortname | configdata |
+ | Field 1 | Other | checkbox | checkbox | |
+ | Field 2 | Other | date | date | |
+ | Field 3 | Other | select | select | {"options":"a\nb\nc"} |
+ | Field 4 | Other | text | text | |
+ | Field 5 | Other | textarea | textarea | |
+ When I upload "admin/tool/uploadcourse/tests/fixtures/courses_custom_fields.csv" file to "File" filemanager
+ And I set the field "Upload mode" to "Create new courses only, skip existing ones"
+ And I click on "Preview" "button"
+ And I click on "Upload courses" "button"
+ Then I should see "Course created"
+ And I should see "Courses created: 1"
+ And I am on site homepage
+ And I should see "Course fields 1"
+ And I should see "Field 1: Yes"
+ And I should see "Field 2: Tuesday, 1 October 2019, 2:00"
+ And I should see "Field 3: b"
+ And I should see "Field 4: Hello"
+ And I should see "Field 5: Goodbye"
+
+ @javascript
+ Scenario: Creation of new courses with custom fields using defaults
+ Given the following "custom field categories" exist:
+ | name | component | area | itemid |
+ | Other | core_course | course | 0 |
+ And the following "custom fields" exist:
+ | name | category | type | shortname | configdata |
+ | Field 1 | Other | checkbox | checkbox | {"checkbydefault":1} |
+ | Field 2 | Other | date | date | {"includetime":0} |
+ | Field 3 | Other | select | select | {"options":"a\nb\nc","defaultvalue":"b"} |
+ | Field 4 | Other | text | text | {"defaultvalue":"Hello"} |
+ | Field 5 | Other | textarea | textarea | {"defaultvalue":"Some text","defaultvalueformat":1} |
+ When I upload "admin/tool/uploadcourse/tests/fixtures/courses.csv" file to "File" filemanager
+ And I set the field "Upload mode" to "Create all, increment shortname if needed"
+ And I click on "Preview" "button"
+ And I expand all fieldsets
+ And the field "Field 1" matches value "1"
+ And the field "Field 3" matches value "b"
+ And the field "Field 4" matches value "Hello"
+ And the field "Field 5" matches value "Some text"
+ # We have to enable the date field manually.
+ And I set the following fields to these values:
+ | customfield_date[enabled] | 1 |
+ | customfield_date[day] | 1 |
+ | customfield_date[month] | June |
+ | customfield_date[year] | 2020 |
+ And I click on "Upload courses" "button"
+ Then I should see "Course created"
+ And I should see "Courses created: 3"
+ And I am on site homepage
+ And I should see "Course 1"
+ And I should see "Field 1: Yes"
+ And I should see "Field 2: 1 June 2020"
+ And I should see "Field 3: b"
+ And I should see "Field 4: Hello"
+ And I should see "Field 5: Some text"
\ No newline at end of file
Background:
Given the following "courses" exist:
| fullname | shortname | category |
- | Some random name | C1 | 0 |
+ | Some random name | C1 | 0 |
+ | Another course | CF1 | 0 |
And I log in as "admin"
And I navigate to "Courses > Upload courses" in site administration
And I should see "Course 1"
And I should not see "Course 2"
And I should not see "Course 3"
+
+ @javascript
+ Scenario: Updating a course with custom fields
+ Given the following "custom field categories" exist:
+ | name | component | area | itemid |
+ | Other | core_course | course | 0 |
+ And the following "custom fields" exist:
+ | name | category | type | shortname | configdata |
+ | Field 1 | Other | checkbox | checkbox | |
+ | Field 2 | Other | date | date | |
+ | Field 3 | Other | select | select | {"options":"a\nb\nc"} |
+ | Field 4 | Other | text | text | |
+ | Field 5 | Other | textarea | textarea | |
+ When I upload "admin/tool/uploadcourse/tests/fixtures/courses_custom_fields.csv" file to "File" filemanager
+ And I set the following fields to these values:
+ | Upload mode | Only update existing courses |
+ | Update mode | Update with CSV data only |
+ And I click on "Preview" "button"
+ And I click on "Upload courses" "button"
+ Then I should see "Course updated"
+ And I should see "Courses updated: 1"
+ And I am on site homepage
+ And I should see "Course fields 1"
+ And I should see "Field 1: Yes"
+ And I should see "Field 2: Tuesday, 1 October 2019, 2:00"
+ And I should see "Field 3: b"
+ And I should see "Field 4: Hello"
+ And I should see "Field 5: Goodbye"
\ No newline at end of file
$this->assertEquals(strtotime('12th July 2013'), $enroldata['manual']->enrolenddate);
}
+ /**
+ * Test upload processing of course custom fields
+ */
+ public function test_custom_fields_data() {
+ $this->resetAfterTest();
+ $this->setAdminUser();
+
+ $course = $this->getDataGenerator()->create_course(['shortname' => 'C1']);
+
+ // Create our custom fields.
+ $category = $this->get_customfield_generator()->create_category();
+ $this->create_custom_field($category, 'date', 'mydatefield');
+ $this->create_custom_field($category, 'text', 'mytextfield');
+ $this->create_custom_field($category, 'textarea', 'mytextareafield');
+
+ // Perform upload.
+ $mode = tool_uploadcourse_processor::MODE_UPDATE_ONLY;
+ $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
+ $dataupload = [
+ 'shortname' => $course->shortname,
+ 'customfield_mydatefield' => '2020-04-01 16:00',
+ 'customfield_mytextfield' => 'Hello',
+ 'customfield_mytextareafield' => 'Is it me you\'re looking for?',
+ ];
+
+ $uploader = new tool_uploadcourse_course($mode, $updatemode, $dataupload);
+ $this->assertTrue($uploader->prepare());
+ $uploader->proceed();
+
+ // Confirm presence of course custom fields.
+ $data = \core_course\customfield\course_handler::create()->export_instance_data_object($course->id);
+ $this->assertEquals('Wednesday, 1 April 2020, 4:00 PM', $data->mydatefield, '', 0.0, 10, false, true);
+ $this->assertEquals($dataupload['customfield_mytextfield'], $data->mytextfield);
+ $this->assertContains($dataupload['customfield_mytextareafield'], $data->mytextareafield);
+ }
+
+ /**
+ * Test upload processing of course custom field that is required but empty
+ */
+ public function test_custom_fields_data_required() {
+ $this->resetAfterTest();
+ $this->setAdminUser();
+
+ $course = $this->getDataGenerator()->create_course(['shortname' => 'C1']);
+
+ // Create our custom field.
+ $category = $this->get_customfield_generator()->create_category();
+ $this->create_custom_field($category, 'select', 'myselect', ['required' => true, 'options' => "Cat\nDog"]);
+
+ $mode = tool_uploadcourse_processor::MODE_UPDATE_ONLY;
+ $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
+ $dataupload = [
+ 'shortname' => $course->shortname,
+ 'customfield_myselect' => null,
+ ];
+
+ $uploader = new tool_uploadcourse_course($mode, $updatemode, $dataupload);
+ $this->assertFalse($uploader->prepare());
+ $this->assertArrayHasKey('customfieldinvalid', $uploader->get_errors());
+
+ // Try again with a default value.
+ $defaults = [
+ 'customfield_myselect' => 2, // Our second option: Dog.
+ ];
+
+ $uploader = new tool_uploadcourse_course($mode, $updatemode, $dataupload, $defaults);
+ $this->assertTrue($uploader->prepare());
+ $uploader->proceed();
+
+ // Confirm presence of course custom fields.
+ $data = \core_course\customfield\course_handler::create()->export_instance_data_object($course->id);
+ $this->assertEquals('Dog', $data->myselect);
+ }
+
+ /**
+ * Test upload processing of course custom field with an invalid select option
+ */
+ public function test_custom_fields_data_invalid_select_option() {
+ $this->resetAfterTest();
+ $this->setAdminUser();
+
+ $course = $this->getDataGenerator()->create_course(['shortname' => 'C1']);
+
+ // Create our custom field.
+ $category = $this->get_customfield_generator()->create_category();
+ $this->create_custom_field($category, 'select', 'myselect',
+ ['required' => true, 'options' => "Cat\nDog", 'defaultvalue' => 'Cat']);
+
+ $mode = tool_uploadcourse_processor::MODE_UPDATE_ONLY;
+ $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
+ $dataupload = [
+ 'shortname' => $course->shortname,
+ 'customfield_myselect' => 'Fish', // No, invalid.
+ ];
+
+ $uploader = new tool_uploadcourse_course($mode, $updatemode, $dataupload);
+ $this->assertTrue($uploader->prepare());
+ $uploader->proceed();
+
+ // Confirm presence of course custom fields.
+ $data = \core_course\customfield\course_handler::create()->export_instance_data_object($course->id);
+ $this->assertEquals('Cat', $data->myselect);
+ }
+
+ /**
+ * Test upload processing of course custom field with an out of range date
+ */
+ public function test_custom_fields_data_invalid_date() {
+ $this->resetAfterTest();
+ $this->setAdminUser();
+
+ $course = $this->getDataGenerator()->create_course(['shortname' => 'C1']);
+
+ // Create our custom field.
+ $category = $this->get_customfield_generator()->create_category();
+ $this->create_custom_field($category, 'date', 'mydate',
+ ['mindate' => strtotime('2020-04-01'), 'maxdate' => '2020-04-30']);
+
+ $mode = tool_uploadcourse_processor::MODE_UPDATE_ONLY;
+ $updatemode = tool_uploadcourse_processor::UPDATE_ALL_WITH_DATA_ONLY;
+ $dataupload = [
+ 'shortname' => $course->shortname,
+ 'customfield_mydate' => '2020-05-06', // Out of range.
+ ];
+
+ $uploader = new tool_uploadcourse_course($mode, $updatemode, $dataupload);
+ $this->assertFalse($uploader->prepare());
+ $this->assertArrayHasKey('customfieldinvalid', $uploader->get_errors());
+ }
+
public function test_idnumber_problems() {
$this->resetAfterTest(true);
$co = new tool_uploadcourse_course($mode, $updatemode, $data, array(), $importoptions);
$this->assertFalse($co->prepare());
$this->assertArrayHasKey('cannotrenameshortnamealreadyinuse', $co->get_errors());
+ }
+ /**
+ * Get custom field plugin generator
+ *
+ * @return core_customfield_generator
+ */
+ protected function get_customfield_generator() : core_customfield_generator {
+ return $this->getDataGenerator()->get_plugin_generator('core_customfield');
}
-}
+ /**
+ * Helper method to create custom course field
+ *
+ * @param \core_customfield\category_controller $category
+ * @param string $type
+ * @param string $shortname
+ * @param array $configdata
+ * @return \core_customfield\field_controller
+ */
+ protected function create_custom_field(\core_customfield\category_controller $category, string $type, string $shortname,
+ array $configdata = []) : \core_customfield\field_controller {
+
+ return $this->get_customfield_generator()->create_field([
+ 'categoryid' => $category->get('id'),
+ 'type' => $type,
+ 'shortname' => $shortname,
+ 'configdata' => $configdata,
+ ]);
+ }
+}
\ No newline at end of file
--- /dev/null
+shortname,fullname,summary,category,customfield_checkbox,customfield_date,customfield_select,customfield_text,customfield_textarea
+CF1,Course fields 1,Testing course fields,1,1,2019-10-01 14:00,b,Hello,Goodbye
\ No newline at end of file
$this->assertArrayHasKey('invalidroles', $errors);
}
+ /**
+ * Test custom field data processing
+ */
+ public function test_get_custom_course_field_data() {
+ global $DB;
+
+ $this->resetAfterTest();
+
+ // Create all the fields!
+ $category = $this->get_customfield_generator()->create_category();
+
+ $checkboxfield = $this->create_custom_field($category, 'checkbox', 'mycheckbox');
+ $datefield = $this->create_custom_field($category, 'date', 'mydate');
+ $selectfield = $this->create_custom_field($category, 'select', 'myselect', ['options' => "Red\nGreen\nBlue"]);
+ $textfield = $this->create_custom_field($category, 'text', 'mytext', ['locked' => 1]);
+ $textareafield = $this->create_custom_field($category, 'textarea', 'mytextarea');
+
+ $fields = tool_uploadcourse_helper::get_custom_course_fields();
+ $this->assertCount(5, $fields);
+
+ $this->assertArrayHasKey($checkboxfield->get('shortname'), $fields);
+ $this->assertInstanceOf(customfield_checkbox\field_controller::class, $fields[$checkboxfield->get('shortname')]);
+
+ $this->assertArrayHasKey($datefield->get('shortname'), $fields);
+ $this->assertInstanceOf(customfield_date\field_controller::class, $fields[$datefield->get('shortname')]);
+
+ $this->assertArrayHasKey($selectfield->get('shortname'), $fields);
+ $this->assertInstanceOf(customfield_select\field_controller::class, $fields[$selectfield->get('shortname')]);
+
+ $this->assertArrayHasKey($textfield->get('shortname'), $fields);
+ $this->assertInstanceOf(customfield_text\field_controller::class, $fields[$textfield->get('shortname')]);
+
+ $this->assertArrayHasKey($textareafield->get('shortname'), $fields);
+ $this->assertInstanceOf(customfield_textarea\field_controller::class, $fields[$textareafield->get('shortname')]);
+
+ $data = [
+ 'customfield_mycheckbox' => '1',
+ 'customfield_mydate' => '2019-10-01',
+ 'customfield_myselect' => 'Green',
+ 'customfield_mytext' => 'Hello',
+ 'customfield_myunknownfield' => 'Goodbye',
+ ];
+
+ $expected = [
+ 'customfield_mycheckbox' => '1',
+ 'customfield_mydate' => strtotime('2019-10-01'),
+ 'customfield_myselect' => 2,
+ 'customfield_mytext' => 'Hello',
+ ];
+
+ $course = $this->getDataGenerator()->create_course();
+ $user = $this->getDataGenerator()->create_and_enrol($course, 'manager');
+ $this->setUser($user);
+
+ $context = context_course::instance($course->id);
+
+ $this->assertEquals($expected, tool_uploadcourse_helper::get_custom_course_field_data($data, [], $context));
+
+ // Now add our custom textarea field (separately because the value of it's 'itemid' element is unknown).
+ $data['customfield_mytextarea'] = 'Something';
+ $fields = tool_uploadcourse_helper::get_custom_course_field_data($data, [], $context);
+ $this->assertArrayHasKey('customfield_mytextarea_editor', $fields);
+ $this->assertArrayHasKey('text', $fields['customfield_mytextarea_editor']);
+ $this->assertEquals('Something', $fields['customfield_mytextarea_editor']['text']);
+
+ // Now prohibit the capability to change locked fields for the manager role.
+ $managerrole = $DB->get_record('role', ['shortname' => 'manager']);
+ role_change_permission($managerrole->id, $context, 'moodle/course:changelockedcustomfields', CAP_PROHIBIT);
+
+ // The locked 'mytext' custom field should not be returned.
+ $fields = tool_uploadcourse_helper::get_custom_course_field_data($data, [], $context);
+ $this->assertCount(4, $fields);
+ $this->assertArrayNotHasKey('customfield_mytext', $fields);
+ }
+
public function test_increment_idnumber() {
$this->resetAfterTest(true);
$this->assertEquals($cat3_fakedouble->id, tool_uploadcourse_helper::resolve_category_by_path($path));
$this->assertEquals($cat3_fakedouble->id, tool_uploadcourse_helper::resolve_category_by_path($path));
}
-}
+
+ /**
+ * Get custom field plugin generator
+ *
+ * @return core_customfield_generator
+ */
+ protected function get_customfield_generator() : core_customfield_generator {
+ return $this->getDataGenerator()->get_plugin_generator('core_customfield');
+ }
+
+ /**
+ * Helper method to create custom course field
+ *
+ * @param \core_customfield\category_controller $category
+ * @param string $type
+ * @param string $shortname
+ * @param array $configdata
+ * @return \core_customfield\field_controller
+ */
+ protected function create_custom_field(\core_customfield\category_controller $category, string $type, string $shortname,
+ array $configdata = []) : \core_customfield\field_controller {
+
+ return $this->get_customfield_generator()->create_field([
+ 'categoryid' => $category->get('id'),
+ 'type' => $type,
+ 'shortname' => $shortname,
+ 'configdata' => $configdata,
+ ]);
+ }
+}
\ No newline at end of file
$string['new_table_from_mysql'] = 'New table from MySQL';
$string['nofieldsspecified'] = 'No fields specified';
$string['nomasterprimaryuniquefound'] = 'The column(s) that your foreign key references must be included in a primary or unique KEY in the referenced table. Note that the column being in a UNIQUE INDEX is not good enough.';
-$string['nomissingorextraindexesfound'] = 'No missing or extra indexes have been found, your DB doesn\'t need further actions.';
+$string['nomissingorextraindexesfound'] = 'No missing or extra indexes have been found, so no further action is required.';
$string['noreffieldsspecified'] = 'No reference fields specified';
$string['noreftablespecified'] = 'Specified reference table not found';
$string['noviolatedforeignkeysfound'] = 'No violated foreign keys found';
| External URL | http://www.google.com |
| id_display | In pop-up |
Then "google" "link" should exist in the "Main menu" "block"
- And "Add an activity or resource" "button" should exist in the "Main menu" "block"
+ And "Add an activity" "button" should exist in the "Main menu" "block"
/**
* Ensure that the stats array is ready to collect information for the given store and definition.
* @param string $store
+ * @param string $storeclass
* @param string $definition A string that identifies the definition.
* @param int $mode One of cache_store::MODE_*. Since 2.9.
*/
- protected static function ensure_ready_for_stats($store, $definition, $mode = cache_store::MODE_APPLICATION) {
+ protected static function ensure_ready_for_stats($store, $storeclass, $definition, $mode = cache_store::MODE_APPLICATION) {
// This function is performance-sensitive, so exit as quickly as possible
// if we do not need to do anything.
if (isset(self::$stats[$definition]['stores'][$store])) {
return;
}
+
if (!array_key_exists($definition, self::$stats)) {
self::$stats[$definition] = array(
'mode' => $mode,
'stores' => array(
$store => array(
+ 'class' => $storeclass,
'hits' => 0,
'misses' => 0,
'sets' => 0,
);
} else if (!array_key_exists($store, self::$stats[$definition]['stores'])) {
self::$stats[$definition]['stores'][$store] = array(
+ 'class' => $storeclass,
'hits' => 0,
'misses' => 0,
'sets' => 0,
* In Moodle 2.9 the $definition argument changed from accepting only a string to accepting a string or a
* cache_definition instance. It is preferable to pass a cache definition instance.
*
+ * In Moodle 3.9 the first argument changed to also accept a cache_store.
+ *
* @internal
- * @param cache_definition $store
+ * @param string|cache_store $store
* @param cache_definition $definition You used to be able to pass a string here, however that is deprecated please pass the
* actual cache_definition object now.
* @param int $hits The number of hits to record (by default 1)
*/
public static function record_cache_hit($store, $definition, $hits = 1) {
+ $storeclass = '';
+ if ($store instanceof cache_store) {
+ $storeclass = get_class($store);
+ $store = $store->my_name();
+ }
list($definitionstr, $mode) = self::get_definition_stat_id_and_mode($definition);
- self::ensure_ready_for_stats($store, $definitionstr, $mode);
+ self::ensure_ready_for_stats($store, $storeclass, $definitionstr, $mode);
self::$stats[$definitionstr]['stores'][$store]['hits'] += $hits;
}
* In Moodle 2.9 the $definition argument changed from accepting only a string to accepting a string or a
* cache_definition instance. It is preferable to pass a cache definition instance.
*
+ * In Moodle 3.9 the first argument changed to also accept a cache_store.
+ *
* @internal
- * @param string $store
+ * @param string|cache_store $store
* @param cache_definition $definition You used to be able to pass a string here, however that is deprecated please pass the
* actual cache_definition object now.
* @param int $misses The number of misses to record (by default 1)
*/
public static function record_cache_miss($store, $definition, $misses = 1) {
+ $storeclass = '';
+ if ($store instanceof cache_store) {
+ $storeclass = get_class($store);
+ $store = $store->my_name();
+ }
list($definitionstr, $mode) = self::get_definition_stat_id_and_mode($definition);
- self::ensure_ready_for_stats($store, $definitionstr, $mode);
+ self::ensure_ready_for_stats($store, $storeclass, $definitionstr, $mode);
self::$stats[$definitionstr]['stores'][$store]['misses'] += $misses;
}
* In Moodle 2.9 the $definition argument changed from accepting only a string to accepting a string or a
* cache_definition instance. It is preferable to pass a cache definition instance.
*
+ * In Moodle 3.9 the first argument changed to also accept a cache_store.
+ *
* @internal
- * @param string $store
+ * @param string|cache_store $store
* @param cache_definition $definition You used to be able to pass a string here, however that is deprecated please pass the
* actual cache_definition object now.
* @param int $sets The number of sets to record (by default 1)
*/
public static function record_cache_set($store, $definition, $sets = 1) {
+ $storeclass = '';
+ if ($store instanceof cache_store) {
+ $storeclass = get_class($store);
+ $store = $store->my_name();
+ }
list($definitionstr, $mode) = self::get_definition_stat_id_and_mode($definition);
- self::ensure_ready_for_stats($store, $definitionstr, $mode);
+ self::ensure_ready_for_stats($store, $storeclass, $definitionstr, $mode);
self::$stats[$definitionstr]['stores'][$store]['sets'] += $sets;
}
$setaftervalidation = false;
if ($result === false) {
if ($this->perfdebug) {
- cache_helper::record_cache_miss($this->storetype, $this->definition);
+ cache_helper::record_cache_miss($this->store, $this->definition);
}
if ($this->loader !== false) {
// We must pass the original (unparsed) key to the next loader in the chain.
}
$setaftervalidation = ($result !== false);
} else if ($this->perfdebug) {
- cache_helper::record_cache_hit($this->storetype, $this->definition);
+ cache_helper::record_cache_hit($this->store, $this->definition);
}
// 5. Validate strictness.
if ($strictness === MUST_EXIST && $result === false) {
$hits++;
}
}
- cache_helper::record_cache_hit($this->storetype, $this->definition, $hits);
- cache_helper::record_cache_miss($this->storetype, $this->definition, $misses);
+ cache_helper::record_cache_hit($this->store, $this->definition, $hits);
+ cache_helper::record_cache_miss($this->store, $this->definition, $misses);
}
// Return the result. Phew!
*/
public function set($key, $data) {
if ($this->perfdebug) {
- cache_helper::record_cache_set($this->storetype, $this->definition);
+ cache_helper::record_cache_set($this->store, $this->definition);
}
if ($this->loader !== false) {
// We have a loader available set it there as well.
}
$successfullyset = $this->store->set_many($data);
if ($this->perfdebug && $successfullyset) {
- cache_helper::record_cache_set($this->storetype, $this->definition, $successfullyset);
+ cache_helper::record_cache_set($this->store, $this->definition, $successfullyset);
}
return $successfullyset;
}
}
if ($result !== false) {
if ($this->perfdebug) {
- cache_helper::record_cache_hit('** static acceleration **', $this->definition);
+ cache_helper::record_cache_hit(cache_store::STATIC_ACCEL, $this->definition);
}
if ($this->staticaccelerationsize > 1 && $this->staticaccelerationcount > 1) {
// Check to see if this is the last item on the static acceleration keys array.
return $result;
} else {
if ($this->perfdebug) {
- cache_helper::record_cache_miss('** static acceleration **', $this->definition);
+ cache_helper::record_cache_miss(cache_store::STATIC_ACCEL, $this->definition);
}
return false;
}
// 4. Load if from the loader/datasource if we don't already have it.
if ($result === false) {
if ($this->perfdebug) {
- cache_helper::record_cache_miss($this->storetype, $this->get_definition());
+ cache_helper::record_cache_miss($this->get_store(), $this->get_definition());
}
if ($this->get_loader() !== false) {
// We must pass the original (unparsed) key to the next loader in the chain.
$this->set($key, $result);
}
} else if ($this->perfdebug) {
- cache_helper::record_cache_hit($this->storetype, $this->get_definition());
+ cache_helper::record_cache_hit($this->get_store(), $this->get_definition());
}
// 5. Validate strictness.
if ($strictness === MUST_EXIST && $result === false) {
$loader->set($key, $data);
}
if ($this->perfdebug) {
- cache_helper::record_cache_set($this->storetype, $this->get_definition());
+ cache_helper::record_cache_set($this->get_store(), $this->get_definition());
}
if (is_object($data) && $data instanceof cacheable_object) {
$data = new cache_cached_object($data);
$hits++;
}
}
- cache_helper::record_cache_hit($this->storetype, $this->get_definition(), $hits);
- cache_helper::record_cache_miss($this->storetype, $this->get_definition(), $misses);
+ cache_helper::record_cache_hit($this->get_store(), $this->get_definition(), $hits);
+ cache_helper::record_cache_miss($this->get_store(), $this->get_definition(), $misses);
}
return $return;
}
$successfullyset = $this->get_store()->set_many($data);
if ($this->perfdebug && $successfullyset) {
- cache_helper::record_cache_set($this->storetype, $this->get_definition(), $successfullyset);
+ cache_helper::record_cache_set($this->store, $this->get_definition(), $successfullyset);
}
return $successfullyset;
}
* Request caches. Static caches really.
*/
const MODE_REQUEST = 4;
+ /**
+ * Static caches.
+ */
+ const STATIC_ACCEL = '** static accel. **';
/**
* Constructs an instance of the cache store.
$this->assertFalse($request->get('missMe'));
$endstats = cache_helper::get_stats();
- $this->assertEquals(2, $endstats[$applicationid]['stores']['cachestore_file']['misses']);
- $this->assertEquals(0, $endstats[$applicationid]['stores']['cachestore_file']['hits']);
- $this->assertEquals(0, $endstats[$applicationid]['stores']['cachestore_file']['sets']);
- $this->assertEquals(3, $endstats[$sessionid]['stores']['cachestore_session']['misses']);
- $this->assertEquals(0, $endstats[$sessionid]['stores']['cachestore_session']['hits']);
- $this->assertEquals(1, $endstats[$sessionid]['stores']['cachestore_session']['sets']);
- $this->assertEquals(4, $endstats[$requestid]['stores']['cachestore_static']['misses']);
- $this->assertEquals(0, $endstats[$requestid]['stores']['cachestore_static']['hits']);
- $this->assertEquals(0, $endstats[$requestid]['stores']['cachestore_static']['sets']);
+ $this->assertEquals(2, $endstats[$applicationid]['stores']['default_application']['misses']);
+ $this->assertEquals(0, $endstats[$applicationid]['stores']['default_application']['hits']);
+ $this->assertEquals(0, $endstats[$applicationid]['stores']['default_application']['sets']);
+ $this->assertEquals(3, $endstats[$sessionid]['stores']['default_session']['misses']);
+ $this->assertEquals(0, $endstats[$sessionid]['stores']['default_session']['hits']);
+ $this->assertEquals(1, $endstats[$sessionid]['stores']['default_session']['sets']);
+ $this->assertEquals(4, $endstats[$requestid]['stores']['default_request']['misses']);
+ $this->assertEquals(0, $endstats[$requestid]['stores']['default_request']['hits']);
+ $this->assertEquals(0, $endstats[$requestid]['stores']['default_request']['sets']);
$startstats = cache_helper::get_stats();
$this->assertTrue($request->set('setMe4', 4));
$endstats = cache_helper::get_stats();
- $this->assertEquals(0, $endstats[$applicationid]['stores']['cachestore_file']['misses'] -
- $startstats[$applicationid]['stores']['cachestore_file']['misses']);
- $this->assertEquals(0, $endstats[$applicationid]['stores']['cachestore_file']['hits'] -
- $startstats[$applicationid]['stores']['cachestore_file']['hits']);
- $this->assertEquals(2, $endstats[$applicationid]['stores']['cachestore_file']['sets'] -
- $startstats[$applicationid]['stores']['cachestore_file']['sets']);
- $this->assertEquals(0, $endstats[$sessionid]['stores']['cachestore_session']['misses'] -
- $startstats[$sessionid]['stores']['cachestore_session']['misses']);
- $this->assertEquals(0, $endstats[$sessionid]['stores']['cachestore_session']['hits'] -
- $startstats[$sessionid]['stores']['cachestore_session']['hits']);
- $this->assertEquals(3, $endstats[$sessionid]['stores']['cachestore_session']['sets'] -
- $startstats[$sessionid]['stores']['cachestore_session']['sets']);
- $this->assertEquals(0, $endstats[$requestid]['stores']['cachestore_static']['misses'] -
- $startstats[$requestid]['stores']['cachestore_static']['misses']);
- $this->assertEquals(0, $endstats[$requestid]['stores']['cachestore_static']['hits'] -
- $startstats[$requestid]['stores']['cachestore_static']['hits']);
- $this->assertEquals(4, $endstats[$requestid]['stores']['cachestore_static']['sets'] -
- $startstats[$requestid]['stores']['cachestore_static']['sets']);
+ $this->assertEquals(0, $endstats[$applicationid]['stores']['default_application']['misses'] -
+ $startstats[$applicationid]['stores']['default_application']['misses']);
+ $this->assertEquals(0, $endstats[$applicationid]['stores']['default_application']['hits'] -
+ $startstats[$applicationid]['stores']['default_application']['hits']);
+ $this->assertEquals(2, $endstats[$applicationid]['stores']['default_application']['sets'] -
+ $startstats[$applicationid]['stores']['default_application']['sets']);
+ $this->assertEquals(0, $endstats[$sessionid]['stores']['default_session']['misses'] -
+ $startstats[$sessionid]['stores']['default_session']['misses']);
+ $this->assertEquals(0, $endstats[$sessionid]['stores']['default_session']['hits'] -
+ $startstats[$sessionid]['stores']['default_session']['hits']);
+ $this->assertEquals(3, $endstats[$sessionid]['stores']['default_session']['sets'] -
+ $startstats[$sessionid]['stores']['default_session']['sets']);
+ $this->assertEquals(0, $endstats[$requestid]['stores']['default_request']['misses'] -
+ $startstats[$requestid]['stores']['default_request']['misses']);
+ $this->assertEquals(0, $endstats[$requestid]['stores']['default_request']['hits'] -
+ $startstats[$requestid]['stores']['default_request']['hits']);
+ $this->assertEquals(4, $endstats[$requestid]['stores']['default_request']['sets'] -
+ $startstats[$requestid]['stores']['default_request']['sets']);
$startstats = cache_helper::get_stats();
$this->assertEquals($request->get('setMe4'), 4);
$endstats = cache_helper::get_stats();
- $this->assertEquals(0, $endstats[$applicationid]['stores']['cachestore_file']['misses'] -
- $startstats[$applicationid]['stores']['cachestore_file']['misses']);
- $this->assertEquals(2, $endstats[$applicationid]['stores']['cachestore_file']['hits'] -
- $startstats[$applicationid]['stores']['cachestore_file']['hits']);
- $this->assertEquals(0, $endstats[$applicationid]['stores']['cachestore_file']['sets'] -
- $startstats[$applicationid]['stores']['cachestore_file']['sets']);
- $this->assertEquals(0, $endstats[$sessionid]['stores']['cachestore_session']['misses'] -
- $startstats[$sessionid]['stores']['cachestore_session']['misses']);
- $this->assertEquals(3, $endstats[$sessionid]['stores']['cachestore_session']['hits'] -
- $startstats[$sessionid]['stores']['cachestore_session']['hits']);
- $this->assertEquals(0, $endstats[$sessionid]['stores']['cachestore_session']['sets'] -
- $startstats[$sessionid]['stores']['cachestore_session']['sets']);
- $this->assertEquals(0, $endstats[$requestid]['stores']['cachestore_static']['misses'] -
- $startstats[$requestid]['stores']['cachestore_static']['misses']);
- $this->assertEquals(4, $endstats[$requestid]['stores']['cachestore_static']['hits'] -
- $startstats[$requestid]['stores']['cachestore_static']['hits']);
- $this->assertEquals(0, $endstats[$requestid]['stores']['cachestore_static']['sets'] -
- $startstats[$requestid]['stores']['cachestore_static']['sets']);
+ $this->assertEquals(0, $endstats[$applicationid]['stores']['default_application']['misses'] -
+ $startstats[$applicationid]['stores']['default_application']['misses']);
+ $this->assertEquals(2, $endstats[$applicationid]['stores']['default_application']['hits'] -
+ $startstats[$applicationid]['stores']['default_application']['hits']);
+ $this->assertEquals(0, $endstats[$applicationid]['stores']['default_application']['sets'] -
+ $startstats[$applicationid]['stores']['default_application']['sets']);
+ $this->assertEquals(0, $endstats[$sessionid]['stores']['default_session']['misses'] -
+ $startstats[$sessionid]['stores']['default_session']['misses']);
+ $this->assertEquals(3, $endstats[$sessionid]['stores']['default_session']['hits'] -
+ $startstats[$sessionid]['stores']['default_session']['hits']);
+ $this->assertEquals(0, $endstats[$sessionid]['stores']['default_session']['sets'] -
+ $startstats[$sessionid]['stores']['default_session']['sets']);
+ $this->assertEquals(0, $endstats[$requestid]['stores']['default_request']['misses'] -
+ $startstats[$requestid]['stores']['default_request']['misses']);
+ $this->assertEquals(4, $endstats[$requestid]['stores']['default_request']['hits'] -
+ $startstats[$requestid]['stores']['default_request']['hits']);
+ $this->assertEquals(0, $endstats[$requestid]['stores']['default_request']['sets'] -
+ $startstats[$requestid]['stores']['default_request']['sets']);
$startstats = cache_helper::get_stats();
$request->get_many(array('setMe1', 'setMe2', 'setMe3', 'setMe4'));
$endstats = cache_helper::get_stats();
- $this->assertEquals(0, $endstats[$applicationid]['stores']['cachestore_file']['misses'] -
- $startstats[$applicationid]['stores']['cachestore_file']['misses']);
- $this->assertEquals(2, $endstats[$applicationid]['stores']['cachestore_file']['hits'] -
- $startstats[$applicationid]['stores']['cachestore_file']['hits']);
- $this->assertEquals(0, $endstats[$applicationid]['stores']['cachestore_file']['sets'] -
- $startstats[$applicationid]['stores']['cachestore_file']['sets']);
- $this->assertEquals(0, $endstats[$sessionid]['stores']['cachestore_session']['misses'] -
- $startstats[$sessionid]['stores']['cachestore_session']['misses']);
- $this->assertEquals(3, $endstats[$sessionid]['stores']['cachestore_session']['hits'] -
- $startstats[$sessionid]['stores']['cachestore_session']['hits']);
- $this->assertEquals(0, $endstats[$sessionid]['stores']['cachestore_session']['sets'] -
- $startstats[$sessionid]['stores']['cachestore_session']['sets']);
- $this->assertEquals(0, $endstats[$requestid]['stores']['cachestore_static']['misses'] -
- $startstats[$requestid]['stores']['cachestore_static']['misses']);
- $this->assertEquals(4, $endstats[$requestid]['stores']['cachestore_static']['hits'] -
- $startstats[$requestid]['stores']['cachestore_static']['hits']);
- $this->assertEquals(0, $endstats[$requestid]['stores']['cachestore_static']['sets'] -
- $startstats[$requestid]['stores']['cachestore_static']['sets']);
+ $this->assertEquals(0, $endstats[$applicationid]['stores']['default_application']['misses'] -
+ $startstats[$applicationid]['stores']['default_application']['misses']);
+ $this->assertEquals(2, $endstats[$applicationid]['stores']['default_application']['hits'] -
+ $startstats[$applicationid]['stores']['default_application']['hits']);
+ $this->assertEquals(0, $endstats[$applicationid]['stores']['default_application']['sets'] -
+ $startstats[$applicationid]['stores']['default_application']['sets']);
+ $this->assertEquals(0, $endstats[$sessionid]['stores']['default_session']['misses'] -
+ $startstats[$sessionid]['stores']['default_session']['misses']);
+ $this->assertEquals(3, $endstats[$sessionid]['stores']['default_session']['hits'] -
+ $startstats[$sessionid]['stores']['default_session']['hits']);
+ $this->assertEquals(0, $endstats[$sessionid]['stores']['default_session']['sets'] -
+ $startstats[$sessionid]['stores']['default_session']['sets']);
+ $this->assertEquals(0, $endstats[$requestid]['stores']['default_request']['misses'] -
+ $startstats[$requestid]['stores']['default_request']['misses']);
+ $this->assertEquals(4, $endstats[$requestid]['stores']['default_request']['hits'] -
+ $startstats[$requestid]['stores']['default_request']['hits']);
+ $this->assertEquals(0, $endstats[$requestid]['stores']['default_request']['sets'] -
+ $startstats[$requestid]['stores']['default_request']['sets']);
}
public function test_static_cache() {
// Check that the static acceleration worked, even on empty arrays and the number 0.
$endstats = cache_helper::get_stats();
- $this->assertEquals(0, $endstats[$applicationid]['stores']['** static acceleration **']['misses']);
- $this->assertEquals(3, $endstats[$applicationid]['stores']['** static acceleration **']['hits']);
+ $this->assertEquals(0, $endstats[$applicationid]['stores']['** static accel. **']['misses']);
+ $this->assertEquals(3, $endstats[$applicationid]['stores']['** static accel. **']['hits']);
}
public function test_performance_debug_off() {
This files describes API changes in /cache/stores/* - cache store plugins.
Information provided here is intended especially for developers.
+=== 3.9 ===
+* The record_cache_hit/miss/set methods now take a cache_store instead of a cache_definition object
+
=== 3.8 ===
* The Redis cache store can now make use of the Zstandard compression algorithm (see MDL-66428).
"require-dev": {
"phpunit/phpunit": "7.5.*",
"phpunit/dbunit": "4.0.*",
- "moodlehq/behat-extension": "3.39.2",
+ "moodlehq/behat-extension": "3.39.3",
"mikey179/vfsstream": "^1.6",
"instaclick/php-webdriver": "dev-local as 1.x-dev"
}
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "a2329ee2d14a351b74f99322f42722da",
+ "content-hash": "b1953ceec577434625a7aee12f650daa",
"packages": [],
"packages-dev": [
{
"name": "behat/behat",
- "version": "v3.5.0",
+ "version": "v3.6.1",
"source": {
"type": "git",
"url": "https://github.com/Behat/Behat.git",
- "reference": "e4bce688be0c2029dc1700e46058d86428c63cab"
+ "reference": "9bfe195b4745c32e068af03fa4df9558b4916d30"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Behat/Behat/zipball/e4bce688be0c2029dc1700e46058d86428c63cab",
- "reference": "e4bce688be0c2029dc1700e46058d86428c63cab",
+ "url": "https://api.github.com/repos/Behat/Behat/zipball/9bfe195b4745c32e068af03fa4df9558b4916d30",
+ "reference": "9bfe195b4745c32e068af03fa4df9558b4916d30",
"shasum": ""
},
"require": {
- "behat/gherkin": "^4.5.1",
+ "behat/gherkin": "^4.6.0",
"behat/transliterator": "^1.2",
"container-interop/container-interop": "^1.2",
"ext-mbstring": "*",
"php": ">=5.3.3",
"psr/container": "^1.0",
- "symfony/class-loader": "~2.1||~3.0",
- "symfony/config": "~2.3||~3.0||~4.0",
- "symfony/console": "~2.7.40||^2.8.33||~3.3.15||^3.4.3||^4.0.3",
- "symfony/dependency-injection": "~2.1||~3.0||~4.0",
- "symfony/event-dispatcher": "~2.1||~3.0||~4.0",
- "symfony/translation": "~2.3||~3.0||~4.0",
- "symfony/yaml": "~2.1||~3.0||~4.0"
+ "symfony/config": "^2.7.51 || ^3.0 || ^4.0 || ^5.0",
+ "symfony/console": "^2.7.51 || ^2.8.33 || ^3.3.15 || ^3.4.3 || ^4.0.3 || ^5.0",
+ "symfony/dependency-injection": "^2.7.51 || ^3.0 || ^4.0 || ^5.0",
+ "symfony/event-dispatcher": "^2.7.51 || ^3.0 || ^4.0 || ^5.0",
+ "symfony/translation": "^2.7.51 || ^3.0 || ^4.0 || ^5.0",
+ "symfony/yaml": "^2.7.51 || ^3.0 || ^4.0 || ^5.0"
},
"require-dev": {
"herrera-io/box": "~1.6.1",
- "phpunit/phpunit": "^4.8.36|^6.3",
- "symfony/process": "~2.5|~3.0|~4.0"
+ "phpunit/phpunit": "^4.8.36 || ^6.3",
+ "symfony/process": "~2.5 || ^3.0 || ^4.0 || ^5.0"
+ },
+ "suggest": {
+ "ext-dom": "Needed to output test results in JUnit format."
},
"bin": [
"bin/behat"
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.5.x-dev"
+ "dev-master": "3.6.x-dev"
}
},
"autoload": {
- "psr-0": {
- "Behat\\Behat": "src/",
- "Behat\\Testwork": "src/"
+ "psr-4": {
+ "Behat\\Behat\\": "src/Behat/Behat/",
+ "Behat\\Testwork\\": "src/Behat/Testwork/"
}
},
"notification-url": "https://packagist.org/downloads/",
"symfony",
"testing"
],
- "time": "2018-08-10T18:56:51+00:00"
+ "time": "2020-02-06T09:54:48+00:00"
},
{
"name": "behat/gherkin",
},
{
"name": "guzzlehttp/guzzle",
- "version": "6.5.2",
+ "version": "6.5.3",
"source": {
"type": "git",
"url": "https://github.com/guzzle/guzzle.git",
- "reference": "43ece0e75098b7ecd8d13918293029e555a50f82"
+ "reference": "aab4ebd862aa7d04f01a4b51849d657db56d882e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/guzzle/guzzle/zipball/43ece0e75098b7ecd8d13918293029e555a50f82",
- "reference": "43ece0e75098b7ecd8d13918293029e555a50f82",
+ "url": "https://api.github.com/repos/guzzle/guzzle/zipball/aab4ebd862aa7d04f01a4b51849d657db56d882e",
+ "reference": "aab4ebd862aa7d04f01a4b51849d657db56d882e",
"shasum": ""
},
"require": {
"ext-json": "*",
"guzzlehttp/promises": "^1.0",
"guzzlehttp/psr7": "^1.6.1",
- "php": ">=5.5"
+ "php": ">=5.5",
+ "symfony/polyfill-intl-idn": "^1.11"
},
"require-dev": {
"ext-curl": "*",
"psr/log": "^1.1"
},
"suggest": {
- "ext-intl": "Required for Internationalized Domain Name (IDN) support",
"psr/log": "Required for using the Log middleware"
},
"type": "library",
"rest",
"web service"
],
- "time": "2019-12-23T11:57:10+00:00"
+ "time": "2020-04-18T10:38:46+00:00"
},
{
"name": "guzzlehttp/promises",
},
{
"name": "moodlehq/behat-extension",
- "version": "v3.39.2",
+ "version": "v3.39.3",
"source": {
"type": "git",
"url": "https://github.com/moodlehq/moodle-behat-extension.git",
- "reference": "7a2df2124ba8a85ccf21e517d18c78f932bdbbce"
+ "reference": "d05ea443ff24f90edb9b31c92e4dfe67c58a0b4b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/moodlehq/moodle-behat-extension/zipball/7a2df2124ba8a85ccf21e517d18c78f932bdbbce",
- "reference": "7a2df2124ba8a85ccf21e517d18c78f932bdbbce",
+ "url": "https://api.github.com/repos/moodlehq/moodle-behat-extension/zipball/d05ea443ff24f90edb9b31c92e4dfe67c58a0b4b",
+ "reference": "d05ea443ff24f90edb9b31c92e4dfe67c58a0b4b",
"shasum": ""
},
"require": {
- "behat/behat": "3.5.*",
+ "behat/behat": "3.6.*",
"behat/mink": "~1.8",
- "behat/mink-extension": "~2.2",
+ "behat/mink-extension": "~2.3",
"behat/mink-goutte-driver": "~1.2",
- "behat/mink-selenium2-driver": "~1.3",
+ "behat/mink-selenium2-driver": "~1.4",
"php": ">=7.2.0",
- "symfony/process": "2.8.*"
+ "symfony/process": "^4.0 || ^5.0"
},
"type": "library",
"autoload": {
"Behat",
"moodle"
],
- "time": "2020-04-09T16:06:14+00:00"
+ "time": "2020-04-20T09:32:44+00:00"
},
{
"name": "myclabs/deep-copy",
"time": "2017-02-14T16:28:37+00:00"
},
{
- "name": "psr/http-message",
- "version": "1.0.1",
+ "name": "psr/event-dispatcher",
+ "version": "1.0.0",
"source": {
"type": "git",
- "url": "https://github.com/php-fig/http-message.git",
- "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
+ "url": "https://github.com/php-fig/event-dispatcher.git",
+ "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
- "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
+ "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0",
+ "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0",
"shasum": ""
},
"require": {
- "php": ">=5.3.0"
+ "php": ">=7.2.0"
},
"type": "library",
"extra": {
},
"autoload": {
"psr-4": {
- "Psr\\Http\\Message\\": "src/"
+ "Psr\\EventDispatcher\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"homepage": "http://www.php-fig.org/"
}
],
- "description": "Common interface for HTTP messages",
- "homepage": "https://github.com/php-fig/http-message",
+ "description": "Standard interfaces for event handling.",
"keywords": [
- "http",
- "http-message",
+ "events",
"psr",
- "psr-7",
- "request",
- "response"
+ "psr-14"
],
- "time": "2016-08-06T14:39:51+00:00"
+ "time": "2019-01-08T18:20:26+00:00"
},
{
- "name": "psr/log",
- "version": "1.1.3",
+ "name": "psr/http-message",
+ "version": "1.0.1",
"source": {
"type": "git",
- "url": "https://github.com/php-fig/log.git",
- "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc"
+ "url": "https://github.com/php-fig/http-message.git",
+ "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc",
- "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc",
+ "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
+ "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
"shasum": ""
},
"require": {
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.1.x-dev"
+ "dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
- "Psr\\Log\\": "Psr/Log/"
+ "Psr\\Http\\Message\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"homepage": "http://www.php-fig.org/"
}
],
- "description": "Common interface for logging libraries",
- "homepage": "https://github.com/php-fig/log",
+ "description": "Common interface for HTTP messages",
+ "homepage": "https://github.com/php-fig/http-message",
"keywords": [
- "log",
+ "http",
+ "http-message",
"psr",
- "psr-3"
+ "psr-7",
+ "request",
+ "response"
],
- "time": "2020-03-23T09:12:05+00:00"
+ "time": "2016-08-06T14:39:51+00:00"
},
{
"name": "ralouphie/getallheaders",
],
"time": "2020-03-28T10:15:50+00:00"
},
- {
- "name": "symfony/class-loader",
- "version": "v3.4.39",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/class-loader.git",
- "reference": "e4636a4f23f157278a19e5db160c63de0da297d8"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/class-loader/zipball/e4636a4f23f157278a19e5db160c63de0da297d8",
- "reference": "e4636a4f23f157278a19e5db160c63de0da297d8",
- "shasum": ""
- },
- "require": {
- "php": "^5.5.9|>=7.0.8"
- },
- "require-dev": {
- "symfony/finder": "~2.8|~3.0|~4.0",
- "symfony/polyfill-apcu": "~1.1"
- },
- "suggest": {
- "symfony/polyfill-apcu": "For using ApcClassLoader on HHVM"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "3.4-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Symfony\\Component\\ClassLoader\\": ""
- },
- "exclude-from-classmap": [
- "/Tests/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Fabien Potencier",
- "email": "fabien@symfony.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "Symfony ClassLoader Component",
- "homepage": "https://symfony.com",
- "funding": [
- {
- "url": "https://symfony.com/sponsor",
- "type": "custom"
- },
- {
- "url": "https://github.com/fabpot",
- "type": "github"
- },
- {
- "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
- "type": "tidelift"
- }
- ],
- "time": "2020-03-15T09:38:08+00:00"
- },
{
"name": "symfony/config",
"version": "v4.4.7",
],
"description": "Symfony Config Component",
"homepage": "https://symfony.com",
- "funding": [
- {
- "url": "https://symfony.com/sponsor",
- "type": "custom"
- },
- {
- "url": "https://github.com/fabpot",
- "type": "github"
- },
- {
- "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
- "type": "tidelift"
- }
- ],
"time": "2020-03-27T16:54:36+00:00"
},
{
"name": "symfony/console",
- "version": "v3.3.18",
+ "version": "v5.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
- "reference": "af7ec995de93671c03cc1b4e3176c8588bc79dcc"
+ "reference": "5fa1caadc8cdaa17bcfb25219f3b53fe294a9935"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/af7ec995de93671c03cc1b4e3176c8588bc79dcc",
- "reference": "af7ec995de93671c03cc1b4e3176c8588bc79dcc",
+ "url": "https://api.github.com/repos/symfony/console/zipball/5fa1caadc8cdaa17bcfb25219f3b53fe294a9935",
+ "reference": "5fa1caadc8cdaa17bcfb25219f3b53fe294a9935",
"shasum": ""
},
"require": {
- "php": "^5.5.9|>=7.0.8",
- "symfony/debug": "~2.8|~3.0",
- "symfony/polyfill-mbstring": "~1.0"
+ "php": "^7.2.5",
+ "symfony/polyfill-mbstring": "~1.0",
+ "symfony/polyfill-php73": "^1.8",
+ "symfony/service-contracts": "^1.1|^2"
},
"conflict": {
- "symfony/dependency-injection": "<3.3"
+ "symfony/dependency-injection": "<4.4",
+ "symfony/event-dispatcher": "<4.4",
+ "symfony/lock": "<4.4",
+ "symfony/process": "<4.4"
+ },
+ "provide": {
+ "psr/log-implementation": "1.0"
},
"require-dev": {
"psr/log": "~1.0",
- "symfony/config": "~3.3",
- "symfony/dependency-injection": "~3.3",
- "symfony/event-dispatcher": "~2.8|~3.0",
- "symfony/filesystem": "~2.8|~3.0",
- "symfony/process": "~2.8|~3.0"
+ "symfony/config": "^4.4|^5.0",
+ "symfony/dependency-injection": "^4.4|^5.0",
+ "symfony/event-dispatcher": "^4.4|^5.0",
+ "symfony/lock": "^4.4|^5.0",
+ "symfony/process": "^4.4|^5.0",
+ "symfony/var-dumper": "^4.4|^5.0"
},
"suggest": {
"psr/log": "For using the console logger",
"symfony/event-dispatcher": "",
- "symfony/filesystem": "",
+ "symfony/lock": "",
"symfony/process": ""
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.3-dev"
+ "dev-master": "5.0-dev"
}
},
"autoload": {
],
"description": "Symfony Console Component",
"homepage": "https://symfony.com",
- "time": "2018-01-29T09:02:23+00:00"
+ "time": "2020-03-30T11:42:42+00:00"
},
{
"name": "symfony/css-selector",
],
"time": "2020-03-27T16:56:45+00:00"
},
- {
- "name": "symfony/debug",
- "version": "v3.4.39",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/debug.git",
- "reference": "ce9f3b5e8e1c50f849fded59b3a1b6bc3562ec29"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/debug/zipball/ce9f3b5e8e1c50f849fded59b3a1b6bc3562ec29",
- "reference": "ce9f3b5e8e1c50f849fded59b3a1b6bc3562ec29",
- "shasum": ""
- },
- "require": {
- "php": "^5.5.9|>=7.0.8",
- "psr/log": "~1.0"
- },
- "conflict": {
- "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2"
- },
- "require-dev": {
- "symfony/http-kernel": "~2.8|~3.0|~4.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "3.4-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Symfony\\Component\\Debug\\": ""
- },
- "exclude-from-classmap": [
- "/Tests/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Fabien Potencier",
- "email": "fabien@symfony.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "Symfony Debug Component",
- "homepage": "https://symfony.com",
- "funding": [
- {
- "url": "https://symfony.com/sponsor",
- "type": "custom"
- },
- {
- "url": "https://github.com/fabpot",
- "type": "github"
- },
- {
- "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
- "type": "tidelift"
- }
- ],
- "time": "2020-03-23T10:22:40+00:00"
- },
{
"name": "symfony/dependency-injection",
- "version": "v3.3.18",
+ "version": "v4.4.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/dependency-injection.git",
- "reference": "54243abc4e1a1a15e274e391bd6f7090b44711f1"
+ "reference": "755b18859be26b90f4bf63753432d3387458bf31"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/54243abc4e1a1a15e274e391bd6f7090b44711f1",
- "reference": "54243abc4e1a1a15e274e391bd6f7090b44711f1",
+ "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/755b18859be26b90f4bf63753432d3387458bf31",
+ "reference": "755b18859be26b90f4bf63753432d3387458bf31",
"shasum": ""
},
"require": {
- "php": "^5.5.9|>=7.0.8",
- "psr/container": "^1.0"
+ "php": "^7.1.3",
+ "psr/container": "^1.0",
+ "symfony/service-contracts": "^1.1.6|^2"
},
"conflict": {
- "symfony/config": "<3.3.7",
- "symfony/finder": "<3.3",
- "symfony/yaml": "<3.3"
+ "symfony/config": "<4.3|>=5.0",
+ "symfony/finder": "<3.4",
+ "symfony/proxy-manager-bridge": "<3.4",
+ "symfony/yaml": "<3.4"
},
"provide": {
- "psr/container-implementation": "1.0"
+ "psr/container-implementation": "1.0",
+ "symfony/service-implementation": "1.0"
},
"require-dev": {
- "symfony/config": "~3.3",
- "symfony/expression-language": "~2.8|~3.0",
- "symfony/yaml": "~3.3"
+ "symfony/config": "^4.3",
+ "symfony/expression-language": "^3.4|^4.0|^5.0",
+ "symfony/yaml": "^3.4|^4.0|^5.0"
},
"suggest": {
"symfony/config": "",
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.3-dev"
+ "dev-master": "4.4-dev"
}
},
"autoload": {
],
"description": "Symfony DependencyInjection Component",
"homepage": "https://symfony.com",
- "time": "2018-01-29T09:02:23+00:00"
+ "time": "2020-03-30T10:09:30+00:00"
},
{
"name": "symfony/dom-crawler",
},
{
"name": "symfony/event-dispatcher",
- "version": "v3.4.39",
+ "version": "v5.0.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
- "reference": "9d4e22943b73acc1ba50595b7de1a01fe9dbad48"
+ "reference": "24f40d95385774ed5c71dbf014edd047e2f2f3dc"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/9d4e22943b73acc1ba50595b7de1a01fe9dbad48",
- "reference": "9d4e22943b73acc1ba50595b7de1a01fe9dbad48",
+ "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/24f40d95385774ed5c71dbf014edd047e2f2f3dc",
+ "reference": "24f40d95385774ed5c71dbf014edd047e2f2f3dc",
"shasum": ""
},
"require": {
- "php": "^5.5.9|>=7.0.8"
+ "php": "^7.2.5",
+ "symfony/event-dispatcher-contracts": "^2"
},
"conflict": {
- "symfony/dependency-injection": "<3.3"
+ "symfony/dependency-injection": "<4.4"
+ },
+ "provide": {
+ "psr/event-dispatcher-implementation": "1.0",
+ "symfony/event-dispatcher-implementation": "2.0"
},
"require-dev": {
"psr/log": "~1.0",
- "symfony/config": "~2.8|~3.0|~4.0",
- "symfony/dependency-injection": "~3.3|~4.0",
- "symfony/expression-language": "~2.8|~3.0|~4.0",
- "symfony/stopwatch": "~2.8|~3.0|~4.0"
+ "symfony/config": "^4.4|^5.0",
+ "symfony/dependency-injection": "^4.4|^5.0",
+ "symfony/expression-language": "^4.4|^5.0",
+ "symfony/http-foundation": "^4.4|^5.0",
+ "symfony/service-contracts": "^1.1|^2",
+ "symfony/stopwatch": "^4.4|^5.0"
},
"suggest": {
"symfony/dependency-injection": "",
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.4-dev"
+ "dev-master": "5.0-dev"
}
},
"autoload": {
"type": "tidelift"
}
],
- "time": "2020-03-15T09:38:08+00:00"
+ "time": "2020-03-27T16:56:45+00:00"
+ },
+ {
+ "name": "symfony/event-dispatcher-contracts",
+ "version": "v2.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/event-dispatcher-contracts.git",
+ "reference": "af23c2584d4577d54661c434446fb8fbed6025dd"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/af23c2584d4577d54661c434446fb8fbed6025dd",
+ "reference": "af23c2584d4577d54661c434446fb8fbed6025dd",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5",
+ "psr/event-dispatcher": "^1"
+ },
+ "suggest": {
+ "symfony/event-dispatcher-implementation": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Contracts\\EventDispatcher\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Generic abstractions related to dispatching event",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "abstractions",
+ "contracts",
+ "decoupling",
+ "interfaces",
+ "interoperability",
+ "standards"
+ ],
+ "time": "2019-11-18T17:27:11+00:00"
},
{
"name": "symfony/filesystem",
],
"description": "Symfony Filesystem Component",
"homepage": "https://symfony.com",
- "funding": [
- {
- "url": "https://symfony.com/sponsor",
- "type": "custom"
- },
- {
- "url": "https://github.com/fabpot",
- "type": "github"
- },
- {
- "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
- "type": "tidelift"
- }
- ],
"time": "2020-03-27T16:56:45+00:00"
},
{
"time": "2020-02-27T09:26:54+00:00"
},
{
- "name": "symfony/polyfill-mbstring",
+ "name": "symfony/polyfill-intl-idn",
"version": "v1.15.0",
"source": {
"type": "git",
- "url": "https://github.com/symfony/polyfill-mbstring.git",
- "reference": "81ffd3a9c6d707be22e3012b827de1c9775fc5ac"
+ "url": "https://github.com/symfony/polyfill-intl-idn.git",
+ "reference": "47bd6aa45beb1cd7c6a16b7d1810133b728bdfcf"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/81ffd3a9c6d707be22e3012b827de1c9775fc5ac",
- "reference": "81ffd3a9c6d707be22e3012b827de1c9775fc5ac",
+ "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/47bd6aa45beb1cd7c6a16b7d1810133b728bdfcf",
+ "reference": "47bd6aa45beb1cd7c6a16b7d1810133b728bdfcf",
"shasum": ""
},
"require": {
- "php": ">=5.3.3"
+ "php": ">=5.3.3",
+ "symfony/polyfill-mbstring": "^1.3",
+ "symfony/polyfill-php72": "^1.10"
},
"suggest": {
- "ext-mbstring": "For best performance"
+ "ext-intl": "For best performance"
},
"type": "library",
"extra": {
},
"autoload": {
"psr-4": {
- "Symfony\\Polyfill\\Mbstring\\": ""
+ "Symfony\\Polyfill\\Intl\\Idn\\": ""
},
"files": [
"bootstrap.php"
],
"authors": [
{
- "name": "Nicolas Grekas",
- "email": "p@tchwork.com"
+ "name": "Laurent Bassin",
+ "email": "laurent@bassin.info"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
- "description": "Symfony polyfill for the Mbstring extension",
+ "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
- "mbstring",
+ "idn",
+ "intl",
"polyfill",
"portable",
"shim"
"time": "2020-03-09T19:04:49+00:00"
},
{
- "name": "symfony/process",
- "version": "v2.8.52",
+ "name": "symfony/polyfill-mbstring",
+ "version": "v1.15.0",
"source": {
"type": "git",
- "url": "https://github.com/symfony/process.git",
- "reference": "c3591a09c78639822b0b290d44edb69bf9f05dc8"
+ "url": "https://github.com/symfony/polyfill-mbstring.git",
+ "reference": "81ffd3a9c6d707be22e3012b827de1c9775fc5ac"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/81ffd3a9c6d707be22e3012b827de1c9775fc5ac",
+ "reference": "81ffd3a9c6d707be22e3012b827de1c9775fc5ac",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "suggest": {
+ "ext-mbstring": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.15-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Polyfill\\Mbstring\\": ""
+ },
+ "files": [
+ "bootstrap.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for the Mbstring extension",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "mbstring",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2020-03-09T19:04:49+00:00"
+ },
+ {
+ "name": "symfony/polyfill-php72",
+ "version": "v1.15.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-php72.git",
+ "reference": "37b0976c78b94856543260ce09b460a7bc852747"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/37b0976c78b94856543260ce09b460a7bc852747",
+ "reference": "37b0976c78b94856543260ce09b460a7bc852747",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.15-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Polyfill\\Php72\\": ""
+ },
+ "files": [
+ "bootstrap.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2020-02-27T09:26:54+00:00"
+ },
+ {
+ "name": "symfony/polyfill-php73",
+ "version": "v1.15.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-php73.git",
+ "reference": "0f27e9f464ea3da33cbe7ca3bdf4eb66def9d0f7"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/process/zipball/c3591a09c78639822b0b290d44edb69bf9f05dc8",
- "reference": "c3591a09c78639822b0b290d44edb69bf9f05dc8",
+ "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f27e9f464ea3da33cbe7ca3bdf4eb66def9d0f7",
+ "reference": "0f27e9f464ea3da33cbe7ca3bdf4eb66def9d0f7",
"shasum": ""
},
"require": {
- "php": ">=5.3.9"
+ "php": ">=5.3.3"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.8-dev"
+ "dev-master": "1.15-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Polyfill\\Php73\\": ""
+ },
+ "files": [
+ "bootstrap.php"
+ ],
+ "classmap": [
+ "Resources/stubs"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2020-02-27T09:26:54+00:00"
+ },
+ {
+ "name": "symfony/process",
+ "version": "v5.0.7",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/process.git",
+ "reference": "c5ca4a0fc16a0c888067d43fbcfe1f8a53d8e70e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/process/zipball/c5ca4a0fc16a0c888067d43fbcfe1f8a53d8e70e",
+ "reference": "c5ca4a0fc16a0c888067d43fbcfe1f8a53d8e70e",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
}
},
"autoload": {
],
"description": "Symfony Process Component",
"homepage": "https://symfony.com",
- "time": "2018-11-11T11:18:13+00:00"
+ "time": "2020-03-27T16:56:45+00:00"
+ },
+ {
+ "name": "symfony/service-contracts",
+ "version": "v2.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/service-contracts.git",
+ "reference": "144c5e51266b281231e947b51223ba14acf1a749"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/service-contracts/zipball/144c5e51266b281231e947b51223ba14acf1a749",
+ "reference": "144c5e51266b281231e947b51223ba14acf1a749",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5",
+ "psr/container": "^1.0"
+ },
+ "suggest": {
+ "symfony/service-implementation": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Contracts\\Service\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Generic abstractions related to writing services",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "abstractions",
+ "contracts",
+ "decoupling",
+ "interfaces",
+ "interoperability",
+ "standards"
+ ],
+ "time": "2019-11-18T17:27:11+00:00"
},
{
"name": "symfony/translation",
- "version": "v3.3.18",
+ "version": "v4.4.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/translation.git",
- "reference": "90cb5ca3eb84b3053fef876e11e405fd819487fc"
+ "reference": "4e54d336f2eca5facad449d0b0118bb449375b76"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/translation/zipball/90cb5ca3eb84b3053fef876e11e405fd819487fc",
- "reference": "90cb5ca3eb84b3053fef876e11e405fd819487fc",
+ "url": "https://api.github.com/repos/symfony/translation/zipball/4e54d336f2eca5facad449d0b0118bb449375b76",
+ "reference": "4e54d336f2eca5facad449d0b0118bb449375b76",
"shasum": ""
},
"require": {
- "php": "^5.5.9|>=7.0.8",
- "symfony/polyfill-mbstring": "~1.0"
+ "php": "^7.1.3",
+ "symfony/polyfill-mbstring": "~1.0",
+ "symfony/translation-contracts": "^1.1.6|^2"
},
"conflict": {
- "symfony/config": "<2.8",
- "symfony/yaml": "<3.3"
+ "symfony/config": "<3.4",
+ "symfony/dependency-injection": "<3.4",
+ "symfony/http-kernel": "<4.4",
+ "symfony/yaml": "<3.4"
+ },
+ "provide": {
+ "symfony/translation-implementation": "1.0"
},
"require-dev": {
"psr/log": "~1.0",
- "symfony/config": "~2.8|~3.0",
- "symfony/intl": "^2.8.18|^3.2.5",
- "symfony/yaml": "~3.3"
+ "symfony/config": "^3.4|^4.0|^5.0",
+ "symfony/console": "^3.4|^4.0|^5.0",
+ "symfony/dependency-injection": "^3.4|^4.0|^5.0",
+ "symfony/finder": "~2.8|~3.0|~4.0|^5.0",
+ "symfony/http-kernel": "^4.4",
+ "symfony/intl": "^3.4|^4.0|^5.0",
+ "symfony/service-contracts": "^1.1.2|^2",
+ "symfony/yaml": "^3.4|^4.0|^5.0"
},
"suggest": {
- "psr/log": "To use logging capability in translator",
+ "psr/log-implementation": "To use logging capability in translator",
"symfony/config": "",
"symfony/yaml": ""
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.3-dev"
+ "dev-master": "4.4-dev"
}
},
"autoload": {
],
"description": "Symfony Translation Component",
"homepage": "https://symfony.com",
- "time": "2018-01-18T14:19:00+00:00"
+ "time": "2020-03-27T16:54:36+00:00"
+ },
+ {
+ "name": "symfony/translation-contracts",
+ "version": "v2.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/translation-contracts.git",
+ "reference": "8cc682ac458d75557203b2f2f14b0b92e1c744ed"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/8cc682ac458d75557203b2f2f14b0b92e1c744ed",
+ "reference": "8cc682ac458d75557203b2f2f14b0b92e1c744ed",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5"
+ },
+ "suggest": {
+ "symfony/translation-implementation": ""
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Contracts\\Translation\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Generic abstractions related to translation",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "abstractions",
+ "contracts",
+ "decoupling",
+ "interfaces",
+ "interoperability",
+ "standards"
+ ],
+ "time": "2019-11-18T17:27:11+00:00"
},
{
"name": "symfony/yaml",
- "version": "v3.3.18",
+ "version": "v4.4.7",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
- "reference": "af615970e265543a26ee712c958404eb9b7ac93d"
+ "reference": "ef166890d821518106da3560086bfcbeb4fadfec"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/yaml/zipball/af615970e265543a26ee712c958404eb9b7ac93d",
- "reference": "af615970e265543a26ee712c958404eb9b7ac93d",
+ "url": "https://api.github.com/repos/symfony/yaml/zipball/ef166890d821518106da3560086bfcbeb4fadfec",
+ "reference": "ef166890d821518106da3560086bfcbeb4fadfec",
"shasum": ""
},
"require": {
- "php": "^5.5.9|>=7.0.8"
+ "php": "^7.1.3",
+ "symfony/polyfill-ctype": "~1.8"
+ },
+ "conflict": {
+ "symfony/console": "<3.4"
},
"require-dev": {
- "symfony/console": "~2.8|~3.0"
+ "symfony/console": "^3.4|^4.0|^5.0"
},
"suggest": {
"symfony/console": "For validating YAML files using the lint command"
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "3.3-dev"
+ "dev-master": "4.4-dev"
}
},
"autoload": {
],
"description": "Symfony Yaml Component",
"homepage": "https://symfony.com",
- "time": "2018-01-20T15:04:53+00:00"
+ "time": "2020-03-30T11:41:10+00:00"
},
{
"name": "theseer/tokenizer",
},
{
"name": "webmozart/assert",
- "version": "1.7.0",
+ "version": "1.8.0",
"source": {
"type": "git",
"url": "https://github.com/webmozart/assert.git",
- "reference": "aed98a490f9a8f78468232db345ab9cf606cf598"
+ "reference": "ab2cb0b3b559010b75981b1bdce728da3ee90ad6"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/webmozart/assert/zipball/aed98a490f9a8f78468232db345ab9cf606cf598",
- "reference": "aed98a490f9a8f78468232db345ab9cf606cf598",
+ "url": "https://api.github.com/repos/webmozart/assert/zipball/ab2cb0b3b559010b75981b1bdce728da3ee90ad6",
+ "reference": "ab2cb0b3b559010b75981b1bdce728da3ee90ad6",
"shasum": ""
},
"require": {
"symfony/polyfill-ctype": "^1.8"
},
"conflict": {
- "vimeo/psalm": "<3.6.0"
+ "vimeo/psalm": "<3.9.1"
},
"require-dev": {
"phpunit/phpunit": "^4.8.36 || ^7.5.13"
"check",
"validate"
],
- "time": "2020-02-14T12:15:55+00:00"
+ "time": "2020-04-18T12:12:48+00:00"
}
],
"aliases": [
// Force developer level debug and add debug info to the output of cron
// $CFG->showcrondebugging = true;
//
+// Force result of checks used to determine whether a site is considered "public" or not (such as for site registration).
+// $CFG->site_is_public = false;
+//
//=========================================================================
// 8. FORCED SETTINGS
//=========================================================================
*
* @return bool True if content could be accessed. False otherwise.
*/
- public function can_view(): bool {
+ public function is_view_allowed(): bool {
// There's no capability at content level to check,
// but plugins can overwrite this method in case they want to check something related to content properties.
return true;
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
- * Content bank manager class
+ * Content bank class
*
* @package core_contentbank
* @copyright 2020 Amaia Anabitarte <amaia@moodle.com>
namespace core_contentbank;
/**
- * Content bank manager class
+ * Content bank class
*
* @package core_contentbank
* @copyright 2020 Amaia Anabitarte <amaia@moodle.com>
$enabledtypes = \core\plugininfo\contenttype::get_enabled_plugins();
$types = [];
foreach ($enabledtypes as $name) {
- $classname = "\\contenttype_$name\\contenttype";
- if (class_exists($classname)) {
+ $contenttypeclassname = "\\contenttype_$name\\contenttype";
+ $contentclassname = "\\contenttype_$name\\content";
+ if (class_exists($contenttypeclassname) && class_exists($contentclassname)) {
$types[] = $name;
}
}
$supportedextensions = [];
foreach ($this->get_enabled_content_types() as $type) {
$classname = "\\contenttype_$type\\contenttype";
- if (class_exists($classname)) {
- $manager = new $classname;
- if ($manager->is_feature_supported($manager::CAN_UPLOAD)) {
- $extensions = $manager->get_manageable_extensions();
- foreach ($extensions as $extension) {
- if (array_key_exists($extension, $supportedextensions)) {
- $supportedextensions[$extension][] = $type;
- } else {
- $supportedextensions[$extension] = [$type];
- }
+ $contenttype = new $classname;
+ if ($contenttype->is_feature_supported($contenttype::CAN_UPLOAD)) {
+ $extensions = $contenttype->get_manageable_extensions();
+ foreach ($extensions as $extension) {
+ if (array_key_exists($extension, $supportedextensions)) {
+ $supportedextensions[$extension][] = $type;
+ } else {
+ $supportedextensions[$extension] = [$type];
}
}
}
foreach ($supportedextensions as $extension => $types) {
foreach ($types as $type) {
$classname = "\\contenttype_$type\\contenttype";
- if (class_exists($classname)) {
- $manager = new $classname($context);
- if ($manager->can_upload()) {
- $contextextensions[$extension] = $type;
- break;
- }
+ $contenttype = new $classname($context);
+ if ($contenttype->can_upload()) {
+ $contextextensions[$extension] = $type;
+ break;
}
}
}
}
return null;
}
+
+ /**
+ * Find the contents with %$search% in the contextid defined.
+ * If contextid and search are empty, all contents are returned.
+ * In all the cases, only the contents for the enabled contentbank-type plugins are returned.
+ *
+ * @param string|null $search Optional string to search (for now it will search only into the name).
+ * @param int $contextid Optional contextid to search.
+ * @return array The contents for the enabled contentbank-type plugins having $search as name and placed in $contextid.
+ */
+ public function search_contents(?string $search = null, ?int $contextid = 0): array {
+ global $DB;
+
+ $contents = [];
+
+ // Get only contents for enabled content-type plugins.
+ $contenttypes = array_map(function($contenttypename) {
+ return "contenttype_$contenttypename";
+ }, $this->get_enabled_content_types());
+ if (empty($contenttypes)) {
+ // Early return if there are no content-type plugins enabled.
+ return $contents;
+ }
+
+ list($sqlcontenttypes, $params) = $DB->get_in_or_equal($contenttypes, SQL_PARAMS_NAMED);
+ $sql = " contenttype $sqlcontenttypes ";
+
+ // Filter contents on this context (if defined).
+ if (!empty($contextid)) {
+ $params['contextid'] = $contextid;
+ $sql .= ' AND contextid = :contextid ';
+ }
+
+ // Search for contents having this string (if defined).
+ if (!empty($search)) {
+ $sql .= ' AND ' . $DB->sql_like('name', ':name', false, false);
+ $params['name'] = '%' . $DB->sql_like_escape($search) . '%';
+ }
+
+ $records = $DB->get_records_select('contentbank_content', $sql, $params);
+ foreach ($records as $record) {
+ $contentclass = "\\$record->contenttype\\content";
+ $content = new $contentclass($record);
+ if ($content->is_view_allowed()) {
+ $contents[] = $content;
+ }
+ }
+
+ return $contents;
+ }
}
use core_privacy\local\metadata\collection;
use core_privacy\local\request\approved_contextlist;
-use core_privacy\local\request\approved_userlist;
use core_privacy\local\request\contextlist;
+use core_privacy\local\request\transform;
+use core_privacy\local\request\writer;
use core_privacy\local\request\userlist;
+use core_privacy\local\request\approved_userlist;
+use context_system;
+use context_coursecat;
+use context_course;
/**
* Privacy provider implementation for core_contentbank.
\core_privacy\local\request\plugin\provider {
/**
- * Returns metadata.
- * TODO: MDL-67798.
+ * Returns meta data about this system.
*
* @param collection $collection The initialised collection to add items to.
* @return collection A listing of user data stored through this system.
*/
- public static function get_metadata(collection $collection) : collection {
- // We are not implementing a proper privacy provider for now.
- // A right privacy provider will be implemented in MDL-67798.
-
+ public static function get_metadata(collection $collection): collection {
$collection->add_database_table('contentbank_content', [
+ 'name' => 'privacy:metadata:content:name',
+ 'contenttype' => 'privacy:metadata:content:contenttype',
'usercreated' => 'privacy:metadata:content:usercreated',
'usermodified' => 'privacy:metadata:content:usermodified',
- ], 'privacy:metadata:userid');
+ 'timecreated' => 'privacy:metadata:content:timecreated',
+ 'timemodified' => 'privacy:metadata:content:timemodified',
+ ], 'privacy:metadata:contentbankcontent');
return $collection;
}
/**
- * TODO: MDL-67798.
+ * Get the list of contexts that contain user information for the specified user.
*
- * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination.
+ * @param int $userid The user to search.
+ * @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin.
*/
- public static function get_users_in_context(userlist $userlist) {
- // We are not implementing a proper privacy provider for now.
- // A right privacy provider will be implemented in MDL-67798.
- }
+ public static function get_contexts_for_userid(int $userid): contextlist {
+ $sql = "SELECT DISTINCT ctx.id
+ FROM {context} ctx
+ JOIN {contentbank_content} cb
+ ON cb.contextid = ctx.id
+ WHERE cb.usercreated = :userid
+ AND (ctx.contextlevel = :contextlevel1
+ OR ctx.contextlevel = :contextlevel2
+ OR ctx.contextlevel = :contextlevel3)";
- /**
- * TODO: MDL-67798.
- *
- * @param approved_userlist $userlist The approved context and user information to delete information for.
- */
- public static function delete_data_for_users(approved_userlist $userlist) {
- // We are not implementing a proper privacy provider for now.
- // A right privacy provider will be implemented in MDL-67798.
+ $params = [
+ 'userid' => $userid,
+ 'contextlevel1' => CONTEXT_SYSTEM,
+ 'contextlevel2' => CONTEXT_COURSECAT,
+ 'contextlevel3' => CONTEXT_COURSE,
+ ];
+
+ $contextlist = new contextlist();
+ $contextlist->add_from_sql($sql, $params);
+
+ return $contextlist;
}
/**
- * TODO: MDL-67798.
- * Get the list of contexts that contain user information for the specified user.
+ * Get the list of users within a specific context.
*
- * @param int $userid The user to search.
- * @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin.
+ * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination.
*/
- public static function get_contexts_for_userid(int $userid) : contextlist {
- // We are not implementing a proper privacy provider for now.
- // A right privacy provider will be implemented in MDL-67798.
+ public static function get_users_in_context(userlist $userlist) {
+ $context = $userlist->get_context();
+
+ $allowedcontextlevels = [
+ CONTEXT_SYSTEM,
+ CONTEXT_COURSECAT,
+ CONTEXT_COURSE,
+ ];
+
+ if (!in_array($context->contextlevel, $allowedcontextlevels)) {
+ return;
+ }
+
+ $sql = "SELECT cb.usercreated as userid
+ FROM {contentbank_content} cb
+ WHERE cb.contextid = :contextid";
- return (new contextlist());
+ $params = [
+ 'contextid' => $context->id
+ ];
+
+ $userlist->add_from_sql('userid', $sql, $params);
}
/**
- * TODO: MDL-67798.
* Export all user data for the specified user, in the specified contexts.
*
- * @param approved_contextlist $contextlist The approved contexts to export information for.
+ * @param approved_contextlist $contextlist The approved contexts to export information for.
*/
public static function export_user_data(approved_contextlist $contextlist) {
- // We are not implementing a proper privacy provider for now.
- // A right privacy provider will be implemented in MDL-67798.
+ global $DB;
+
+ // Remove contexts different from SYSTEM, COURSECAT or COURSE.
+ $contextids = array_reduce($contextlist->get_contexts(), function($carry, $context) {
+ if ($context->contextlevel == CONTEXT_SYSTEM || $context->contextlevel == CONTEXT_COURSECAT
+ || $context->contextlevel == CONTEXT_COURSE) {
+ $carry[] = $context->id;
+ }
+ return $carry;
+ }, []);
+
+ if (empty($contextids)) {
+ return;
+ }
+
+ $userid = $contextlist->get_user()->id;
+
+ list($contextsql, $contextparams) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED);
+ // Retrieve the contentbank_content records created for the user.
+ $sql = "SELECT cb.id,
+ cb.name,
+ cb.contenttype,
+ cb.usercreated,
+ cb.usermodified,
+ cb.timecreated,
+ cb.timemodified,
+ cb.contextid
+ FROM {contentbank_content} cb
+ WHERE cb.usercreated = :userid
+ AND cb.contextid {$contextsql}
+ ORDER BY cb.contextid";
+
+ $params = ['userid' => $userid] + $contextparams;
+
+ $contents = $DB->get_recordset_sql($sql, $params);
+ $data = [];
+ $lastcontextid = null;
+ $subcontext = [
+ get_string('name', 'core_contentbank'),
+ ];
+ foreach ($contents as $content) {
+ // The core_contentbank data export is organised in:
+ // {Sytem|Course Category|Course Context Level}/Content/data.json.
+ if ($lastcontextid && $lastcontextid != $content->contextid) {
+ $context = \context::instance_by_id($lastcontextid);
+ writer::with_context($context)->export_data($subcontext, (object)$data);
+ $data = [];
+ }
+ $data[] = (object) [
+ 'name' => $content->name,
+ 'contenttype' => $content->contenttype,
+ 'usercreated' => transform::user($content->usercreated),
+ 'usermodified' => transform::user($content->usermodified),
+ 'timecreated' => transform::datetime($content->timecreated),
+ 'timemodified' => transform::datetime($content->timemodified)
+ ];
+ $lastcontextid = $content->contextid;
+
+ // The core_contentbank files export is organised in:
+ // {Sytem|Course Category|Course Context Level}/Content/_files/public/_itemid/filename.
+ $context = \context::instance_by_id($lastcontextid);
+ writer::with_context($context)->export_area_files($subcontext, 'contentbank', 'public', $content->id);
+ }
+ if (!empty($data)) {
+ $context = \context::instance_by_id($lastcontextid);
+ writer::with_context($context)->export_data($subcontext, (object)$data);
+ }
+ $contents->close();
}
/**
- * TODO: MDL-67798.
* Delete all data for all users in the specified context.
*
- * @param context $context The specific context to delete data for.
+ * @param context $context The specific context to delete data for.
*/
public static function delete_data_for_all_users_in_context(\context $context) {
- // We are not implementing a proper privacy provider for now.
- // A right privacy provider will be implemented in MDL-67798.
+ global $DB;
+
+ if (!$context instanceof context_system && !$context instanceof context_coursecat
+ && !$context instanceof context_course) {
+ return;
+ }
+
+ static::delete_data($context, []);
+ }
+
+ /**
+ * Delete multiple users within a single context.
+ *
+ * @param approved_userlist $userlist The approved context and user information to delete information for.
+ */
+ public static function delete_data_for_users(approved_userlist $userlist) {
+ $context = $userlist->get_context();
+
+ if (!$context instanceof context_system && !$context instanceof context_coursecat
+ && !$context instanceof context_course) {
+ return;
+ }
+
+ static::delete_data($context, $userlist->get_userids());
}
/**
- * TODO: MDL-67798.
* Delete all user data for the specified user, in the specified contexts.
*
- * @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
+ * @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
*/
public static function delete_data_for_user(approved_contextlist $contextlist) {
- // We are not implementing a proper privacy provider for now.
- // A right privacy provider will be implemented in MDL-67798.
+ if (empty($contextlist->count())) {
+ return;
+ }
+
+ $userid = $contextlist->get_user()->id;
+ foreach ($contextlist->get_contexts() as $context) {
+ if (!$context instanceof context_system && !$context instanceof context_coursecat
+ && !$context instanceof context_course) {
+ continue;
+ }
+ static::delete_data($context, [$userid]);
+ }
+ }
+
+ /**
+ * Delete data related to a context and users (if defined).
+ *
+ * @param context $context A context.
+ * @param array $userids The user IDs.
+ */
+ protected static function delete_data(\context $context, array $userids) {
+ global $DB;
+
+ $params = ['contextid' => $context->id];
+ $select = 'contextid = :contextid';
+
+ // Delete the Content Bank files.
+ if (!empty($userids)) {
+ list($insql, $inparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED);
+ $params += $inparams;
+ $select .= ' AND usercreated '.$insql;
+ }
+ $fs = get_file_storage();
+ $contents = $DB->get_records_select('contentbank_content',
+ $select, $params);
+ foreach ($contents as $content) {
+ $fs->delete_area_files($content->contextid, 'contentbank', 'public', $content->id);
+ }
+
+ // Delete all the contents.
+ $DB->delete_records_select('contentbank_content', $select, $params);
}
}
$string['pluginname'] = 'H5P';
$string['pluginname_help'] = 'Content bank to upload and share H5P content';
$string['privacy:metadata'] = 'The H5P content bank plugin does not store any personal data.';
-$string['h5p:access'] = 'Access to H5P content in the content bank';
-$string['h5p:upload'] = 'Upload a new H5P content';
+$string['h5p:access'] = 'Access H5P content in the content bank';
+$string['h5p:upload'] = 'Upload new H5P content';
And I click on "Save changes" "button"
And I wait until the page is ready
And I should see "filltheblanks.h5p"
- And I navigate to "Plugins > Content bank > Manage content bank content types" in site administration
+ And I navigate to "Plugins > Content bank > Manage content types" in site administration
And I click on "Disable" "icon" in the "H5P" "table_row"
And I wait until the page is ready
When I click on "Content bank" "link"
require_login();
$contextid = optional_param('contextid', \context_system::instance()->id, PARAM_INT);
+$search = optional_param('search', '', PARAM_CLEAN);
$context = context::instance_by_id($contextid, MUST_EXIST);
require_capability('moodle/contentbank:access', $context);
$PAGE->set_pagetype('contenbank');
// Get all contents managed by active plugins to render.
-$foldercontents = array();
-$contents = $DB->get_records('contentbank_content', ['contextid' => $contextid]);
-foreach ($contents as $content) {
- $plugin = core_plugin_manager::instance()->get_plugin_info($content->contenttype);
- if (!$plugin || !$plugin->is_enabled()) {
- continue;
- }
- $contentclass = "\\$content->contenttype\\content";
- if (class_exists($contentclass)) {
- $contentmanager = new $contentclass($content);
- if ($contentmanager->can_view()) {
- $foldercontents[] = $contentmanager;
- }
- }
-}
+$cb = new \core_contentbank\contentbank();
+$foldercontents = $cb->search_contents($search, $contextid);
// Get the toolbar ready.
$toolbar = array ();
if (has_capability('moodle/contentbank:upload', $context)) {
// Don' show upload button if there's no plugin to support any file extension.
- $cb = new \core_contentbank\contentbank();
$accepted = $cb->get_supported_extensions_as_string($context);
if (!empty($accepted)) {
$importurl = new moodle_url('/contentbank/upload.php', ['contextid' => $contextid]);
}}
{{>core_contentbank/toolbar}}
-<div class="content-bank-container card">
+<div class="content-bank-container pb-3 border">
<div class="content-bank">
- <div class="cb-navbar">
+ <div class="cb-navbar bg-light p-2 border-bottom">
{{#pix}} i/folder {{/pix}}
</div>
- <div class="cb-content-wrapper">
+ <div class="cb-content-wrapper d-flex flex-wrap p-2">
{{#contents}}
- <div class="cb-content">
- <div class="cb-iconview">
- <div class="cb-file text-center position-relative">
- {{#link}}<a href="{{{ link }}}">{{/link}}
- <div style="position:relative;">
- <div class="cb-thumbnail text-center d-block" style="width: 110px; height: 110px;">
- {{{ icon }}}
- </div>
- </div>
- <div class="cb-contentname-field position-absolute overflow-visible">
- <div class="cb-contentname text-truncate" style="width: 112px;">{{{ name }}}</div>
- </div>
- {{#link}}</a>{{/link}}
+ <div class="cb-file position-relative mb-2">
+ <div class="p-2">
+ <div class="cb-thumbnail mb-1 text-center">
+ {{{ icon }}}
</div>
+
+ {{#link}}
+ <a href="{{{ link }}}" class="stretched-link" title="{{{name}}}">
+ {{/link}}
+ <span class="cb-name word-break-all clamp-2 text-center" >
+ {{{ name }}}
+ </span>
+ {{#link}}
+ </a>
+ {{/link}}
</div>
</div>
{{/contents}}
<div class="cb-toolbar float-sm-right">
{{#tools}}
{{#link}}<a href="{{{ link }}}" title="{{{ name }}}">{{/link}}
- <div class="cb-tool btn btn-secondary btn-sm">
+ <div class="cb-tool icon-no-margin btn btn-secondary btn-lg">
{{#pix}} {{{ icon }}} {{/pix}}
</div>
{{#link}}</a>{{/link}}
And I follow "Manage private files..."
And I upload "h5p/tests/fixtures/filltheblanks.h5p" file to "Files" filemanager
And I click on "Save changes" "button"
- And I click on "Content bank" "link"
+ And I am on site homepage
+ And I turn editing mode on
+ And I add the "Navigation" block if not present
+ And I configure the "Navigation" block
+ And I set the following fields to these values:
+ | Page contexts | Display throughout the entire site |
+ And I press "Save changes"
+ And I click on "Site pages" "list_item" in the "Navigation" "block"
+ And I click on "Content bank" "link" in the "Navigation" "block"
And I click on "Upload" "link"
And I click on "Choose a file..." "button"
And I click on "Private files" "link" in the ".fp-repo-area" "css_element"
And I click on "Save changes" "button"
Scenario: Admins can delete content from the content bank
- Given I click on "Content bank" "link"
- And I wait until the page is ready
- And I should see "filltheblanks.h5p"
- When I follow "filltheblanks.h5p"
- And I open the action menu in "region-main-settings-menu" "region"
+ Given I should see "filltheblanks.h5p"
+ And I follow "filltheblanks.h5p"
+ When I open the action menu in "region-main-settings-menu" "region"
Then I should see "Delete"
And I choose "Delete" in the open action menu
- And I should see "Are you sure you want to delete content 'filltheblanks.h5p'?"
+ And I should see "Are you sure you want to delete the content 'filltheblanks.h5p'"
And I click on "Cancel" "button" in the "Delete content" "dialogue"
And I should see "filltheblanks.h5p"
And I open the action menu in "region-main-settings-menu" "region"
| user | role | contextlevel | reference |
| manager | manager | System | |
And I log out
- When I log in as "manager"
- And I click on "Content bank" "link"
- And I wait until the page is ready
+ And I log in as "manager"
+ And I follow "Manage private files..."
+ And I upload "h5p/tests/fixtures/find-the-words.h5p" file to "Files" filemanager
+ And I click on "Save changes" "button"
+ When I click on "Site pages" "list_item" in the "Navigation" "block"
+ And I click on "Content bank" "link" in the "Navigation" "block"
And I should see "filltheblanks.h5p"
And I follow "filltheblanks.h5p"
Then ".header-actions-container" "css_element" should not exist
- And I click on "Private files" "link"
- And I upload "h5p/tests/fixtures/find-the-words.h5p" file to "Files" filemanager
- And I click on "Save changes" "button"
And I click on "Content bank" "link"
And I click on "Upload" "link"
And I click on "Choose a file..." "button"
And I click on "find-the-words.h5p" "link"
And I click on "Select this file" "button"
And I click on "Save changes" "button"
- And I wait until the page is ready
And I should see "filltheblanks.h5p"
And I should see "find-the-words.h5p"
- When I follow "find-the-words.h5p"
+ And I follow "find-the-words.h5p"
And I open the action menu in "region-main-settings-menu" "region"
- Then I should see "Delete"
+ And I should see "Delete"
$supporter = $cb->get_extension_supporter($extension, $systemcontext);
$this->assertEquals($expected, $supporter);
}
+
+ /**
+ * Test the behaviour of search_contents().
+ *
+ * @dataProvider search_contents_provider
+ * @param string $search String to search.
+ * @param int $contextid Contextid to search.
+ * @param int $expectedresult Expected result.
+ * @param array $contexts List of contexts where to create content.
+ */
+ public function test_search_contents(?string $search, int $contextid, int $expectedresult, array $contexts = []): void {
+ global $DB;
+
+ $this->resetAfterTest();
+
+ // Create users.
+ $managerroleid = $DB->get_field('role', 'id', ['shortname' => 'manager']);
+ $manager = $this->getDataGenerator()->create_user();
+ $this->getDataGenerator()->role_assign($managerroleid, $manager->id);
+
+ // Add some content to the content bank.
+ $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank');
+ foreach ($contexts as $context) {
+ $records = $generator->generate_contentbank_data('contenttype_h5p', 3,
+ $manager->id, $context, false);
+ }
+
+ // Search for some content.
+ $cb = new \core_contentbank\contentbank();
+ $contents = $cb->search_contents($search, $contextid);
+
+ $this->assertCount($expectedresult, $contents);
+ if (!empty($contents) && !empty($search)) {
+ foreach ($contents as $content) {
+ $this->assertContains($search, $content->get_name());
+ }
+ }
+ }
+
+ /**
+ * Data provider for test_search_contents().
+ *
+ * @return array
+ */
+ public function search_contents_provider(): array {
+ // Create a category and a course.
+ $systemcontext = \context_system::instance();
+ $coursecat = $this->getDataGenerator()->create_category();
+ $course = $this->getDataGenerator()->create_course();
+ $coursecatcontext = \context_coursecat::instance($coursecat->id);
+ $coursecontext = \context_course::instance($course->id);
+
+ return [
+ 'Search all content in all contexts' => [
+ null,
+ 0,
+ 9,
+ [$systemcontext, $coursecatcontext, $coursecontext]
+ ],
+ 'Search in all contexts for existing string in all contents' => [
+ 'content',
+ 0,
+ 9,
+ [$systemcontext, $coursecatcontext, $coursecontext]
+ ],
+ 'Search in all contexts for unexisting string in all contents' => [
+ 'chocolate',
+ 0,
+ 0,
+ [$systemcontext, $coursecatcontext, $coursecontext]
+ ],
+ 'Search in all contexts for existing string in some contents' => [
+ '1',
+ 0,
+ 3,
+ [$systemcontext, $coursecatcontext, $coursecontext]
+ ],
+ 'Search in all contexts for existing string in some contents (create only 1 context)' => [
+ '1',
+ 0,
+ 1,
+ [$systemcontext]
+ ],
+ 'Search in system context for existing string in all contents' => [
+ 'content',
+ $systemcontext->id,
+ 3,
+ [$systemcontext, $coursecatcontext, $coursecontext]
+ ],
+ 'Search in category context for unexisting string in all contents' => [
+ 'chocolate',
+ $coursecatcontext->id,
+ 0,
+ [$systemcontext, $coursecatcontext, $coursecontext]
+ ],
+ 'Search in course context for existing string in some contents' => [
+ '1',
+ $coursecontext->id,
+ 1,
+ [$systemcontext, $coursecatcontext, $coursecontext]
+ ],
+ 'Search in system context' => [
+ null,
+ $systemcontext->id,
+ 3,
+ [$systemcontext, $coursecatcontext, $coursecontext]
+ ],
+ 'Search in course context with existing content' => [
+ null,
+ $coursecontext->id,
+ 3,
+ [$systemcontext, $coursecatcontext, $coursecontext]
+ ],
+ 'Search in course context without existing content' => [
+ null,
+ $coursecontext->id,
+ 0,
+ [$systemcontext, $coursecatcontext]
+ ],
+ 'Search in an empty contentbank' => [
+ null,
+ 0,
+ 0,
+ []
+ ],
+ 'Search in a context in an empty contentbank' => [
+ null,
+ $systemcontext->id,
+ 0,
+ []
+ ],
+ 'Search for a string in an empty contentbank' => [
+ 'content',
+ 0,
+ 0,
+ []
+ ],
+ ];
+ }
}
--- /dev/null
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Base class for unit tests for core_contentbank.
+ *
+ * @package core_contentbank
+ * @category test
+ * @copyright 2020 Carlos Escobedo <carlos@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core_contentbank;
+
+defined('MOODLE_INTERNAL') || die();
+
+use stdClass;
+use context_system;
+use context_coursecat;
+use context_course;
+use core_contentbank\privacy\provider;
+use core_privacy\local\request\approved_contextlist;
+use core_privacy\local\request\writer;
+use core_privacy\tests\provider_testcase;
+use core_privacy\local\request\userlist;
+use core_privacy\local\request\approved_userlist;
+
+/**
+ * Unit tests for contentbank\classes\privacy\provider.php
+ *
+ * @copyright 2020 Carlos Escobedo <carlos@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_contentbank_privacy_testcase extends provider_testcase {
+
+ /**
+ * Setup to ensure that fixtures are loaded.
+ */
+ public static function setupBeforeClass(): void {
+ global $CFG;
+ require_once($CFG->dirroot . '/contentbank/tests/fixtures/testable_content.php');
+ }
+
+ /**
+ * Test for provider::get_contexts_for_userid().
+ */
+ public function test_get_contexts_for_userid() {
+
+ $this->resetAfterTest();
+ // Setup scenario.
+ $scenario = $this->setup_scenario();
+
+ // Testing againts Manager who has content in the three contexts.
+ $contextlist = provider::get_contexts_for_userid($scenario->manager->id);
+ // There are three contexts in the list.
+ $contextlistids = $contextlist->get_contextids();
+ $this->assertCount(3, $contextlistids);
+ // Check the list against the expected list of contexts.
+ $this->assertContains($scenario->systemcontext->id, $contextlistids);
+ $this->assertContains($scenario->coursecategorycontext->id,
+ $contextlistids);
+ $this->assertContains($scenario->coursecontext->id, $contextlistids);
+
+ // Testing againts Teacher who has content in the one context.
+ $contextlist = provider::get_contexts_for_userid($scenario->teacher->id);
+ // There are only one context in the list.
+ $contextlistids = $contextlist->get_contextids();
+ $this->assertCount(1, $contextlistids);
+ // Check the againts Course Context.
+ $this->assertContains($scenario->coursecontext->id, $contextlistids);
+ // And there is not a System and Course Category Context.
+ $this->assertNotContains($scenario->systemcontext->id, $contextlistids);
+ $this->assertNotContains($scenario->coursecategorycontext->id, $contextlistids);
+ }
+
+ /**
+ * Test for provider::get_users_in_context().
+ */
+ public function test_get_users_in_context() {
+
+ $this->resetAfterTest();
+ // Setup scenario.
+ $scenario = $this->setup_scenario();
+
+ // Get the userlist to Context System, only Manager will be there.
+ $userlist = new userlist($scenario->systemcontext, 'core_contentbank');
+ provider::get_users_in_context($userlist);
+ $this->assertEquals([$scenario->manager->id], $userlist->get_userids());
+ // Teacher will not be there.
+ $this->assertNotEquals([$scenario->teacher->id], $userlist->get_userids());
+
+ // Get the userlist to Context Course, Manager and Teacher will be there.
+ $userlist = new userlist($scenario->coursecontext, 'core_contentbank');
+ provider::get_users_in_context($userlist);
+
+ $expected = [$scenario->manager->id, $scenario->teacher->id];
+ sort($expected);
+ $actual = $userlist->get_userids();
+ sort($actual);
+ $this->assertEquals($expected, $actual);
+ }
+
+ /**
+ * Test for provider::test_export_user_data().
+ */
+ public function test_export_user_data() {
+
+ $this->resetAfterTest();
+ // Setup scenario.
+ $scenario = $this->setup_scenario();
+
+ $subcontexts = [
+ get_string('name', 'core_contentbank')
+ ];
+ // Get the data for the System Context.
+ $writer = writer::with_context($scenario->systemcontext);
+ $this->assertFalse($writer->has_any_data());
+ // Export data for Manager.
+ $this->export_context_data_for_user($scenario->manager->id,
+ $scenario->systemcontext, 'core_contentbank');
+ $data = $writer->get_data($subcontexts);
+ $this->assertCount(3, (array) $data);
+ $this->assertCount(3, $writer->get_files($subcontexts));
+
+ // Get the data for the Course Categoy Context.
+ $writer = writer::with_context($scenario->coursecategorycontext);
+ // Export data for Manager.
+ $this->export_context_data_for_user($scenario->manager->id,
+ $scenario->coursecategorycontext, 'core_contentbank');
+ $data = $writer->get_data($subcontexts);
+ $this->assertCount(2, (array) $data);
+ $this->assertCount(2, $writer->get_files($subcontexts));
+
+ // Get the data for the Course Context.
+ $writer = writer::with_context($scenario->coursecontext);
+ // Export data for Manager.
+ $this->export_context_data_for_user($scenario->manager->id,
+ $scenario->coursecontext, 'core_contentbank');
+ $data = $writer->get_data($subcontexts);
+ $this->assertCount(2, (array) $data);
+ $this->assertCount(2, $writer->get_files($subcontexts));
+
+ // Export data for Teacher.
+ $writer = writer::reset();
+ $writer = writer::with_context($scenario->coursecontext);
+ $this->export_context_data_for_user($scenario->teacher->id,
+ $scenario->coursecontext, 'core_contentbank');
+ $data = $writer->get_data($subcontexts);
+ $this->assertCount(3, (array) $data);
+ $this->assertCount(3, $writer->get_files($subcontexts));
+ }
+
+ /**
+ * Test for provider::delete_data_for_all_users_in_context().
+ */
+ public function test_delete_data_for_all_users_in_context() {
+ global $DB;
+
+ $this->resetAfterTest();
+ // Setup scenario.
+ $scenario = $this->setup_scenario();
+
+ // Before delete data, we have 4 contents.
+ // - 3 in a system context.
+ // - 2 in a course category context.
+ // - 5 in a course context (2 by manager and 3 by teacher).
+
+ // Delete data based on system context.
+ provider::delete_data_for_all_users_in_context($scenario->systemcontext);
+ $count = $DB->count_records('contentbank_content');
+ // 3 content should be deleted.
+ // 7 contents should be remain.
+ $this->assertEquals(7, $count);
+
+ // Delete data based on course category context.
+ provider::delete_data_for_all_users_in_context($scenario->coursecategorycontext);
+ $count = $DB->count_records('contentbank_content');
+ // 2 contents should be deleted.
+ // 5 content should be remain.
+ $this->assertEquals(5, $count);
+
+ // Delete data based on course context.
+ provider::delete_data_for_all_users_in_context($scenario->coursecontext);
+ $count = $DB->count_records('contentbank_content');
+ // 5 content should be deleted.
+ // 0 content should be remain.
+ $this->assertEquals(0, $count);
+ }
+
+ /**
+ * Test for provider::test_delete_data_for_users().
+ */
+ public function test_delete_data_for_users() {
+ global $DB;
+
+ $this->resetAfterTest();
+ // Setup scenario.
+ $scenario = $this->setup_scenario();
+
+ // Before delete data, we have 4 contents.
+ // - 3 in a system context.
+ // - 2 in a course category context.
+ // - 5 in a course context (2 by manager and 3 by teacher).
+
+ // A list of users who has created content in Course Category Context.
+ $userlist1 = new userlist($scenario->coursecategorycontext,
+ 'core_contentbank');
+ provider::get_users_in_context($userlist1);
+ $this->assertCount(1, $userlist1);
+ // Only Manager should be.
+ $this->assertEquals([$scenario->manager->id], $userlist1->get_userids());
+
+ // A list of users who has created content in Course Context.
+ $userlist2 = new userlist($scenario->coursecontext, 'core_contentbank');
+ provider::get_users_in_context($userlist2);
+ $this->assertCount(2, $userlist2);
+
+ // Manager and Teacher should be.
+ $expected = [$scenario->manager->id, $scenario->teacher->id];
+ sort($expected);
+ $actual = $userlist2->get_userids();
+ sort($actual);
+ $this->assertEquals($expected, $actual);
+
+ // Convert $userlist1 into an approved_contextlist.
+ $approvedlist1 = new approved_userlist($scenario->coursecategorycontext, 'core_contentbank', $userlist1->get_userids());
+ // Delete data for users in course category context.
+ provider::delete_data_for_users($approvedlist1);
+
+ // Re-fetch users in course category context.
+ $userlist1 = new userlist($scenario->coursecategorycontext,
+ 'core_contentbank');
+ provider::get_users_in_context($userlist1);
+ // The user data in course category context should be deleted.
+ $this->assertCount(0, $userlist1);
+ // Re-fetch users in course category context.
+ $userlist2 = new userlist($scenario->coursecontext, 'core_contentbank');
+ provider::get_users_in_context($userlist2);
+ // The user data in course context should be still present.
+ $this->assertCount(2, $userlist2);
+
+ // Convert $userlist2 into an approved_contextlist.
+ $approvedlist2 = new approved_userlist($scenario->coursecontext,
+ 'core_contentbank', $userlist2->get_userids());
+ // Delete data for users in course context.
+ provider::delete_data_for_users($approvedlist2);
+ $userlist2 = new userlist($scenario->coursecontext, 'core_contentbank');
+ provider::get_users_in_context($userlist2);
+ // The user data in course context should be deleted.
+ $this->assertCount(0, $userlist2);
+ }
+
+ /**
+ * Test for provider::delete_data_for_user().
+ */
+ public function test_delete_data_for_user() {
+ global $DB;
+
+ $this->resetAfterTest();
+ // Setup scenario.
+ $scenario = $this->setup_scenario();
+
+ // Before delete data, we have 4 contents.
+ // - 3 in a system context.
+ // - 2 in a course category context.
+ // - 5 in a course context (2 by manager and 3 by teacher).
+
+ // Get all the context for Manager.
+ $contextlist = provider::get_contexts_for_userid($scenario->manager->id);
+ $approvedcontextlist = new approved_contextlist($scenario->manager,
+ 'core_contentbank', $contextlist->get_contextids());
+ // Delete all the data created by the Manager in all the contexts.
+ provider::delete_data_for_user($approvedcontextlist);
+
+ // After deletion, only 3 content for teacher should be present.
+ $count = $DB->count_records('contentbank_content');
+ $this->assertEquals(3, $count);
+
+ // Confirm that the remaining content was created by the teacher.
+ $count = $DB->count_records('contentbank_content',
+ ['usercreated' => $scenario->teacher->id]);
+ $this->assertEquals(3, $count);
+
+ // Get all the context for Teacher.
+ $contextlist = provider::get_contexts_for_userid($scenario->teacher->id);
+ $approvedcontextlist = new approved_contextlist($scenario->teacher,
+ 'core_contentbank', $contextlist->get_contextids());
+ // Delete all the data created by the Teacher in all the contexts.
+ provider::delete_data_for_user($approvedcontextlist);
+
+ // After deletion, no content should be present.
+ $count = $DB->count_records('contentbank_content');
+ $this->assertEquals(0, $count);
+ }
+
+ /**
+ * Create a complex scenario to use into the tests.
+ *
+ * @return stdClass $scenario
+ */
+ protected function setup_scenario() {
+ global $DB;
+
+ $systemcontext = context_system::instance();
+ $manager = $this->getDataGenerator()->create_user();
+ $managerroleid = $DB->get_field('role', 'id', ['shortname' => 'manager']);
+ $this->getDataGenerator()->role_assign($managerroleid, $manager->id);
+
+ $coursecategory = $this->getDataGenerator()->create_category();
+ $coursecategorycontext = context_coursecat::instance($coursecategory->id);
+
+ $course = $this->getDataGenerator()->create_course();
+ $coursecontext = context_course::instance($course->id);
+ $teacher = $this->getDataGenerator()->create_and_enrol($course,
+ 'editingteacher');
+
+ // Add some content to the content bank.
+ $generator = $this->getDataGenerator()->get_plugin_generator('core_contentbank');
+ // Add contents by Manager in Context System.
+ $records = $generator->generate_contentbank_data('contenttype_testable',
+ 1, $manager->id, $systemcontext, false, 'systemtestfile1.h5p');
+ $records = $generator->generate_contentbank_data('contenttype_testable',
+ 1, $manager->id, $systemcontext, false, 'systemtestfile2.h5p');
+ $records = $generator->generate_contentbank_data('contenttype_testable',
+ 1, $manager->id, $systemcontext, false, 'systemtestfile3.h5p');
+ // Add contents by Manager in Context Course Category.
+ $records = $generator->generate_contentbank_data('contenttype_testable',
+ 1, $manager->id, $coursecategorycontext, false, 'coursecattestfile1.h5p');
+ $records = $generator->generate_contentbank_data('contenttype_testable',
+ 1, $manager->id, $coursecategorycontext, false, 'coursecattestfile2.h5p');
+ // Add contents by Manager in Context Course.
+ $records = $generator->generate_contentbank_data('contenttype_testable',
+ 1, $manager->id, $coursecontext, false, 'coursetestfile1.h5p');
+ $records = $generator->generate_contentbank_data('contenttype_testable',
+ 1, $manager->id, $coursecontext, false, 'coursetestfile2.h5p');
+ // Add contents by Teacher.
+ $records = $generator->generate_contentbank_data('contenttype_testable',
+ 1, $teacher->id, $coursecontext, false, 'courseteacherfile1.h5p');
+ $records = $generator->generate_contentbank_data('contenttype_testable',
+ 1, $teacher->id, $coursecontext, false, 'courseteacherfile2.h5p');
+ $records = $generator->generate_contentbank_data('contenttype_testable',
+ 1, $teacher->id, $coursecontext, false, 'courseteacherfile3.h5p');
+
+ $scenario = new stdClass();
+ $scenario->systemcontext = $systemcontext;
+ $scenario->coursecategorycontext = $coursecategorycontext;
+ $scenario->coursecontext = $coursecontext;
+ $scenario->manager = $manager;
+ $scenario->teacher = $teacher;
+
+ return $scenario;
+ }
+}
'string' => new \lang_string('restorecourse', 'admin')
);
}
+ // Recyclebyn.
+ if (\tool_recyclebin\category_bin::is_enabled()) {
+ $categorybin = new \tool_recyclebin\category_bin($category->id);
+ if ($categorybin->can_view()) {
+ $autohide = get_config('tool_recyclebin', 'autohide');
+ if ($autohide) {
+ $items = $categorybin->get_items();
+ } else {
+ $items = [];
+ }
+ if (!$autohide || !empty($items)) {
+ $pluginname = get_string('pluginname', 'tool_recyclebin');
+ $actions['recyclebin'] = [
+ 'url' => new \moodle_url('/admin/tool/recyclebin/index.php', ['contextid' => $category->get_context()->id]),
+ 'icon' => new \pix_icon('trash', $pluginname, 'tool_recyclebin'),
+ 'string' => $pluginname
+ ];
+ }
+ }
+ }
return $actions;
}
And I am on "Course" course homepage with editing mode on
Scenario: The available activities are displayed to the teacher in the activity chooser
- Given I click on "Add an activity or resource" "button" in the "Topic 1" "section"
- Then I should see "Add an activity or resource" in the ".modal-title" "css_element"
+ Given I click on "Add an activity" "button" in the "Topic 1" "section"
+ Then I should see "Add an activity" in the ".modal-title" "css_element"
And I should see "Assignment" in the ".modal-body" "css_element"
Scenario: The teacher can choose to add an activity from the activity items in the activity chooser
- Given I click on "Add an activity or resource" "button" in the "Topic 3" "section"
- When I click on "Add a new Assignment" "link" in the "Add an activity or resource" "dialogue"
+ Given I click on "Add an activity" "button" in the "Topic 3" "section"
+ When I click on "Add a new Assignment" "link" in the "Add an activity" "dialogue"
Then I should see "Adding a new Assignment"
And I set the following fields to these values:
| Assignment name | Test Assignment Topic 3 |
Then I should see "Test Assignment Topic 3" in the "Topic 3" "section"
Scenario: The teacher can choose to add an activity from the activity summary in the activity chooser
- Given I click on "Add an activity or resource" "button" in the "Topic 1" "section"
- When I click on "Information about the Assignment activity" "button" in the "Add an activity or resource" "dialogue"
+ Given I click on "Add an activity" "button" in the "Topic 1" "section"
+ When I click on "Information about the Assignment activity" "button" in the "Add an activity" "dialogue"
When I click on "Add a new Assignment" "link" in the "help" "core_course > Activity chooser screen"
Then I should see "Adding a new Assignment"
Scenario: Show summary
- Given I click on "Add an activity or resource" "button" in the "Topic 1" "section"
- When I click on "Information about the Assignment activity" "button" in the "Add an activity or resource" "dialogue"
+ Given I click on "Add an activity" "button" in the "Topic 1" "section"
+ When I click on "Information about the Assignment activity" "button" in the "Add an activity" "dialogue"
Then I should see "Assignment" in the "help" "core_course > Activity chooser screen"
And I should see "The assignment activity module enables a teacher to communicate tasks, collect work and provide grades and feedback."
Scenario: Hide summary
- Given I click on "Add an activity or resource" "button" in the "Topic 1" "section"
+ Given I click on "Add an activity" "button" in the "Topic 1" "section"
When I click on "Information about the Assignment activity" "button" in the "modules" "core_course > Activity chooser screen"
And I should see "The assignment activity module enables a teacher to communicate tasks, collect work and provide grades and feedback." in the "help" "core_course > Activity chooser screen"
And I should see "Back" in the "help" "core_course > Activity chooser screen"
Then "modules" "core_course > Activity chooser screen" should exist
And "help" "core_course > Activity chooser screen" should not exist
And "Back" "button" should not exist in the "modules" "core_course > Activity chooser screen"
- And I should not see "The assignment activity module enables a teacher to communicate tasks, collect work and provide grades and feedback." in the "Add an activity or resource" "dialogue"
+ And I should not see "The assignment activity module enables a teacher to communicate tasks, collect work and provide grades and feedback." in the "Add an activity" "dialogue"
Scenario: View recommended activities
When I log out
And I log in as "teacher"
And I am on "Course" course homepage with editing mode on
And I open the activity chooser
- Then I should see "Recommended" in the "Add an activity or resource" "dialogue"
- And I click on "Recommended" "link" in the "Add an activity or resource" "dialogue"
+ Then I should see "Recommended" in the "Add an activity" "dialogue"
+ And I click on "Recommended" "link" in the "Add an activity" "dialogue"
And I should see "Book" in the "recommended" "core_course > Activity chooser tab"
Scenario: Favourite a module in the activity chooser
Given I open the activity chooser
- And I should not see "Starred" in the "Add an activity or resource" "dialogue"
- And I click on "Star Assignment module" "button" in the "Add an activity or resource" "dialogue"
- And I should see "Starred" in the "Add an activity or resource" "dialogue"
- When I click on "Starred" "link" in the "Add an activity or resource" "dialogue"
+ And I should not see "Starred" in the "Add an activity" "dialogue"
+ And I click on "Star Assignment activity" "button" in the "Add an activity" "dialogue"
+ And I should see "Starred" in the "Add an activity" "dialogue"
+ When I click on "Starred" "link" in the "Add an activity" "dialogue"
Then I should see "Assignment" in the "favourites" "core_course > Activity chooser tab"
And I click on "Information about the Assignment activity" "button" in the "favourites" "core_course > Activity chooser tab"
And I should see "The assignment activity module enables a teacher to communicate tasks, collect work and provide grades and feedback."
Scenario: Add a favourite module and check it exists when reopening the chooser
Given I open the activity chooser
- And I click on "Star Assignment module" "button" in the "Add an activity or resource" "dialogue"
- And I click on "Star Forum module" "button" in the "Add an activity or resource" "dialogue"
- And I should see "Starred" in the "Add an activity or resource" "dialogue"
- And I click on "Close" "button" in the "Add an activity or resource" "dialogue"
- When I click on "Add an activity or resource" "button" in the "Topic 3" "section"
- And I click on "Starred" "link" in the "Add an activity or resource" "dialogue"
+ And I click on "Star Assignment activity" "button" in the "Add an activity" "dialogue"
+ And I click on "Star Forum activity" "button" in the "Add an activity" "dialogue"
+ And I should see "Starred" in the "Add an activity" "dialogue"
+ And I click on "Close" "button" in the "Add an activity" "dialogue"
+ When I click on "Add an activity" "button" in the "Topic 3" "section"
+ And I click on "Starred" "link" in the "Add an activity" "dialogue"
Then I should see "Forum" in the "favourites" "core_course > Activity chooser tab"
Scenario: Add a favourite and then remove it whilst checking the tabs work as expected
Given I open the activity chooser
- And I click on "Star Assignment module" "button" in the "Add an activity or resource" "dialogue"
- And I click on "Starred" "link" in the "Add an activity or resource" "dialogue"
- And I click on "Star Assignment module" "button" in the "Add an activity or resource" "dialogue"
- Then I should not see "Starred" in the "Add an activity or resource" "dialogue"
+ And I click on "Star Assignment activity" "button" in the "Add an activity" "dialogue"
+ And I click on "Starred" "link" in the "Add an activity" "dialogue"
+ And I click on "Star Assignment activity" "button" in the "Add an activity" "dialogue"
+ Then I should not see "Starred" in the "Add an activity" "dialogue"
Scenario: The teacher can search for an activity by it's name
- Given I click on "Add an activity or resource" "button" in the "Topic 1" "section"
+ Given I click on "Add an activity" "button" in the "Topic 1" "section"
When I set the field "search" to "Lesson"
- Then I should see "1 results found" in the "Add an activity or resource" "dialogue"
- And I should see "Lesson" in the "Add an activity or resource" "dialogue"
+ Then I should see "1 results found" in the "Add an activity" "dialogue"
+ And I should see "Lesson" in the "Add an activity" "dialogue"
Scenario: The teacher can search for an activity by it's description
Given I open the activity chooser
When I set the field "search" to "The lesson activity module enables a teacher to deliver content"
- Then I should see "1 results found" in the "Add an activity or resource" "dialogue"
- And I should see "Lesson" in the "Add an activity or resource" "dialogue"
+ Then I should see "1 results found" in the "Add an activity" "dialogue"
+ And I should see "Lesson" in the "Add an activity" "dialogue"
Scenario: Search results are not returned if the search query does not match any activity name or description
- Given I click on "Add an activity or resource" "button" in the "Topic 1" "section"
+ Given I click on "Add an activity" "button" in the "Topic 1" "section"
When I set the field "search" to "Random search query"
- Then I should see "0 results found" in the "Add an activity or resource" "dialogue"
+ Then I should see "0 results found" in the "Add an activity" "dialogue"
And ".option" "css_element" should not exist in the ".searchresultitemscontainer" "css_element"
Scenario: Teacher can return to the default activity chooser state by manually removing the search query
- Given I click on "Add an activity or resource" "button" in the "Topic 1" "section"
+ Given I click on "Add an activity" "button" in the "Topic 1" "section"
And I set the field "search" to "Lesson"
- And I should see "1 results found" in the "Add an activity or resource" "dialogue"
- And I should see "Lesson" in the "Add an activity or resource" "dialogue"
+ And I should see "1 results found" in the "Add an activity" "dialogue"
+ And I should see "Lesson" in the "Add an activity" "dialogue"
When I set the field "search" to ""
- And I should not see "1 results found" in the "Add an activity or resource" "dialogue"
+ And I should not see "1 results found" in the "Add an activity" "dialogue"
Then ".searchresultscontainer" "css_element" should not exist
And ".optionscontainer" "css_element" should exist
Scenario: Teacher can not see a "clear" button if a search query is not entered in the activity chooser search bar
- When I click on "Add an activity or resource" "button" in the "Topic 1" "section"
+ When I click on "Add an activity" "button" in the "Topic 1" "section"
Then "Clear search input" "button" should not exist
Scenario: Teacher can see a "clear" button after entering a search query in the activity chooser search bar
- Given I click on "Add an activity or resource" "button" in the "Topic 1" "section"
+ Given I click on "Add an activity" "button" in the "Topic 1" "section"
When I set the field "search" to "Search query"
Then "Clear search input" "button" should not exist
Scenario: Teacher can not see a "clear" button if the search query is removed in the activity chooser search bar
- Given I click on "Add an activity or resource" "button" in the "Topic 1" "section"
+ Given I click on "Add an activity" "button" in the "Topic 1" "section"
And I set the field "search" to "Search query"
And "Clear search input" "button" should exist
When I set the field "search" to ""
Then "Clear search input" "button" should not exist
Scenario: Teacher can instantly remove the search query from the activity search bar by clicking on the "clear" button
- Given I click on "Add an activity or resource" "button" in the "Topic 1" "section"
+ Given I click on "Add an activity" "button" in the "Topic 1" "section"
And I set the field "search" to "Search query"
- And I should see "results found" in the "Add an activity or resource" "dialogue"
+ And I should see "results found" in the "Add an activity" "dialogue"
When I click on "Clear search input" "button"
Then I should not see "Search query"
And ".searchresultscontainer" "css_element" should not exist
*
* @return string
*/
- protected function get_form_element_name() : string {
+ public function get_form_element_name() : string {
return 'customfield_' . $this->get_field()->get('shortname');
}
return $fieldcontroller;
}
+ /**
+ * Perform pre-processing of field values, for example those that originate from an external source (e.g. upload course tool)
+ *
+ * Override in plugin classes as necessary
+ *
+ * @param string $value
+ * @return mixed
+ */
+ public function parse_value(string $value) {
+ return $value;
+ }
+
/**
* Validate the data on the field configuration form
*
*
* @param \MoodleQuickForm $mform
* @param int $instanceid id of the instance, can be null when instance is being created
+ * @param string $headerlangidentifier If specified, a lang string will be used for field category headings
+ * @param string $headerlangcomponent
*/
- public function instance_form_definition(\MoodleQuickForm $mform, int $instanceid = 0) {
+ public function instance_form_definition(\MoodleQuickForm $mform, int $instanceid = 0,
+ ?string $headerlangidentifier = null, ?string $headerlangcomponent = null) {
$editablefields = $this->get_editable_fields($instanceid);
$fieldswithdata = api::get_instance_fields_data($editablefields, $instanceid);
foreach ($fieldswithdata as $data) {
$categoryid = $data->get_field()->get_category()->get('id');
if ($categoryid != $lastcategoryid) {
- $mform->addElement('header', 'category_' . $categoryid,
- format_string($data->get_field()->get_category()->get('name')));
+ $categoryname = format_string($data->get_field()->get_category()->get('name'));
+
+ // Load category header lang string if specified.
+ if (!empty($headerlangidentifier)) {
+ $categoryname = get_string($headerlangidentifier, $headerlangcomponent, $categoryname);
+ }
+
+ $mform->addElement('header', 'category_' . $categoryid, $categoryname);
$lastcategoryid = $categoryid;
}
$data->instance_form_definition($mform);
$this->get_formatted_name());
return $ret;
}
-}
+
+ /**
+ * Convert given value into appropriate timestamp
+ *
+ * @param string $value
+ * @return int
+ */
+ public function parse_value(string $value) {
+ $timestamp = strtotime($value);
+
+ // If we have a valid, positive timestamp then return it.
+ return $timestamp > 0 ? $timestamp : 0;
+ }
+}
\ No newline at end of file
$this->assertEquals(null, $d->export_value());
}
+ /**
+ * Data provider for {@see test_parse_value}
+ *
+ * @return array
+ */
+ public function parse_value_provider() : array {
+ return [
+ // Valid times.
+ ['2019-10-01', strtotime('2019-10-01')],
+ ['2019-10-01 14:00', strtotime('2019-10-01 14:00')],
+ // Invalid times.
+ ['ZZZZZ', 0],
+ ['202-04-01', 0],
+ ['2019-15-15', 0],
+ ];
+ }
+ /**
+ * Test field parse_value method
+ *
+ * @param string $value
+ * @param int $expected
+ * @return void
+ *
+ * @dataProvider parse_value_provider
+ */
+ public function test_parse_value(string $value, int $expected) {
+ $this->assertSame($expected, $this->cfields[1]->parse_value($value));
+ }
+
/**
* Deleting fields and data
*/
public function test_delete() {
$this->cfcat->get_handler()->delete_all();
}
-}
+}
\ No newline at end of file
$this->get_formatted_name());
return $ret;
}
-}
+
+ /**
+ * Locate the value parameter in the field options array, and return it's index
+ *
+ * @param string $value
+ * @return int
+ */
+ public function parse_value(string $value) {
+ return (int) array_search($value, self::get_options_array($this));
+ }
+}
\ No newline at end of file
$this->assertEquals('b', $d->export_value());
}
+ /**
+ * Data provider for {@see test_parse_value}
+ *
+ * @return array
+ */
+ public function parse_value_provider() : array {
+ return [
+ ['Red', 1],
+ ['Blue', 2],
+ ['Green', 3],
+ ['Mauve', 0],
+ ];
+ }
+
+ /**
+ * Test field parse_value method
+ *
+ * @param string $value
+ * @param int $expected
+ * @return void
+ *
+ * @dataProvider parse_value_provider
+ */
+ public function test_parse_value(string $value, int $expected) {
+ $field = $this->get_generator()->create_field([
+ 'categoryid' => $this->cfcat->get('id'),
+ 'type' => 'select',
+ 'shortname' => 'myselect',
+ 'configdata' => [
+ 'options' => "Red\nBlue\nGreen",
+ ],
+ ]);
+
+ $this->assertSame($expected, $field->parse_value($value));
+ }
+
/**
* Deleting fields and data
*/
public function test_delete() {
$this->cfcat->get_handler()->delete_all();
}
-}
+}
\ No newline at end of file
*
* @return string
*/
- protected function get_form_element_name() : string {
+ public function get_form_element_name() : string {
return parent::get_form_element_name() . '_editor';
}
$string['maxscore'] = 'Maximum score';
$string['name'] = 'Name';
$string['needregrademessage'] = 'The marking guide definition was changed after this student had been graded. The student can not see this marking guide until you check the marking guide and update the grade.';
-$string['outof'] = 'Out of {$a}';
+$string['outof'] = 'Score out of {$a}';
$string['pluginname'] = 'Marking guide';
$string['previewmarkingguide'] = 'Preview marking guide';
$string['privacy:metadata:criterionid'] = 'An identifier to a criterion for advanced marking.';
if ($mode == gradingform_rubric_controller::DISPLAY_EDIT_FULL) {
$value = get_string('criterionaddlevel', 'gradingform_rubric');
$button = html_writer::empty_tag('input', array('type' => 'submit', 'name' => '{NAME}[criteria][{CRITERION-id}][levels][addlevel]',
- 'id' => '{NAME}-criteria-{CRITERION-id}-levels-addlevel', 'value' => $value));
+ 'id' => '{NAME}-criteria-{CRITERION-id}-levels-addlevel', 'value' => $value, 'class' => 'btn btn-secondary'));
$criteriontemplate .= html_writer::tag('td', $button, array('class' => 'addlevel'));
}
$displayremark = ($options['enableremarks'] && ($mode != gradingform_rubric_controller::DISPLAY_VIEW || $options['showremarksstudent']));
.gradingform_rubric.editor .addcriterion input,
.gradingform_rubric.editor .addlevel input {
- background: transparent url([[pix:t/add]]) no-repeat top left;
+ background: #fff url([[pix:t/add]]) no-repeat 7px 8px;
display: block;
color: #555;
font-weight: bold;
}
.gradingform_rubric.editor .addcriterion input {
- background-position: 5px 8px;
height: 30px;
line-height: 29px;
margin-bottom: 14px;
}
.gradingform_rubric.editor .addlevel input {
- background-position: 5px 5px;
- height: 25px;
- line-height: 24px;
- margin-bottom: 45px;
- padding-left: 18px;
+ padding-left: 24px;
padding-right: 8px;
}
defined('MOODLE_INTERNAL') || die();
$string['pluginname'] = 'H5P framework v1.24';
-$string['pluginname_help'] = 'H5P framework. Version 1.24';
-$string['privacy:metadata'] = 'H5P framework v1.24 do not store any personal data.';
+$string['pluginname_help'] = 'H5P framework version 1.24.';
+$string['privacy:metadata'] = 'The H5P framework v1.24 does not store any personal data.';
defined('MOODLE_INTERNAL') || die();
-$string['thisdirection'] = 'ltr';
+$string['thisdirection'] = 'rtl';
$string['thislanguage'] = 'پښتو';
$string['configenablerssfeeds'] = 'If enabled, RSS feeds are generated by various features across the site, such as blogs, forums, database activities and glossaries. Note that RSS feeds also need to be enabled for the particular activity modules.';
$string['configenablerssfeedsdisabled'] = 'It is not available because RSS feeds are disabled in all the Site. To enable them, go to the Variables settings under Admin Configuration.';
$string['configenablerssfeedsdisabled2'] = 'RSS feeds are currently disabled at site level. They may be enabled in Advanced features in the Site administration.';
-$string['configenablesafebrowserintegration'] = 'This adds the choice \'Require Safe Exam Browser\' to the \'Browser security\' field on the quiz settings form. See https://www.safeexambrowser.org/ for more information.';
$string['configenablestats'] = 'If you choose \'yes\' here, Moodle\'s cronjob will process the logs and gather some statistics. Depending on the amount of traffic on your site, this can take awhile. If you enable this, you will be able to see some interesting graphs and statistics about each of your courses, or on a sitewide basis.';
$string['configenabletrusttext'] = 'By default Moodle will always thoroughly clean text that comes from users to remove any possible bad scripts, media etc that could be a security risk. The Trusted Content system is a way of giving particular users that you trust the ability to include these advanced features in their content without interference. To enable this system, you need to first enable this setting, and then grant the Trusted Content permission to a specific Moodle role. Texts created or uploaded by such users will be marked as trusted and will not be cleaned before display.';
$string['configenablewebservices'] = 'Web services enable other systems, such as the Moodle app, to log in to the site and perform operations. For extra security, the setting should be disabled if you are not using the app, or an external tool/service that requires integration via web services.';
$string['configrequestedstudentsname'] = 'Word for students used in requested courses';
$string['configrequestedteachername'] = 'Word for teacher used in requested courses';
$string['configrequestedteachersname'] = 'Word for teachers used in requested courses';
-$string['configreverseproxyignore'] = 'If your server is behind multiple reverse proxies that append to the X-Forwarded-For header then you will need to specify a comma separated list of ip addresses or subnets of the reverse proxies to be ignored in order to find the users correct IP address.';
+$string['configreverseproxyignore'] = 'If your server is behind multiple reverse proxies that append to the X-Forwarded-For header, then specify a comma-separated list of IP addresses or subnets of the reverse proxies to be ignored in order to find the user\'s correct IP address.';
$string['configsectioninterface'] = 'Interface';
$string['configsectionmail'] = 'Mail';
$string['configsectionmaintenance'] = 'Maintenance';
$string['enablemoodlenet_desc'] = 'If enabled, and provided the MoodleNet plugin is installed, users can import content from MoodleNet into this site.';
$string['enablerecordcache'] = 'Enable record cache';
$string['enablerssfeeds'] = 'Enable RSS feeds';
-$string['enablesafebrowserintegration'] = 'Enable Safe Exam Browser integration';
$string['enablesearchareas'] = 'Enable search areas';
$string['enablestats'] = 'Enable statistics';
$string['enabletrusttext'] = 'Enable trusted content';
$string['task_scheduled_concurrency_limit_desc'] = 'The number of scheduled task runners allowed to run concurrently. If the limit is high then the server may experience high load which affects performance. A setting of 0 will disable processing of scheduled tasks completely.';
$string['task_scheduled_max_runtime'] = 'Scheduled task runner lifetime';
$string['task_scheduled_max_runtime_desc'] = 'The age of a scheduled task runner before it is freed.';
-$string['task_adhoc_concurrency_limit'] = 'Adhoc task concurrency limit';
-$string['task_adhoc_concurrency_limit_desc'] = 'The number of adhoc task runners allowed to run concurrently. If the limit is high then scheduled tasks may not run regularly when there are lots of adhoc tasks. A setting of 0 will disable processing of adhoc tasks completely.';
-$string['task_adhoc_max_runtime'] = 'Adhoc task runner lifetime';
-$string['task_adhoc_max_runtime_desc'] = 'The age of an adhoc task runner before it is freed. A low duration is recommended as there is no limit to the number of adhoc tasks queued. If this number is too high and you have a large adhoc task queue then scheduled tasks may not be run regularly.';
+$string['task_adhoc_concurrency_limit'] = 'Ad hoc task concurrency limit';
+$string['task_adhoc_concurrency_limit_desc'] = 'The number of ad hoc task runners allowed to run concurrently. If the limit is high then scheduled tasks may not run regularly when there are lots of ad hoc tasks. A setting of 0 will disable processing of ad hoc tasks completely.';
+$string['task_adhoc_max_runtime'] = 'Ad hoc task runner lifetime';
+$string['task_adhoc_max_runtime_desc'] = 'The age of an ad hoc task runner before it is freed. A low duration is recommended as there is no limit to the number of ad hoc tasks queued. If this number is too high and you have a large ad hoc task queue then scheduled tasks may not be run regularly.';
$string['task_logmode'] = 'When to log';
$string['task_logmode_desc'] = 'You can choose when you wish task logging to take place. By default logs are always captured. You can disable logging entirely, or change to only log tasks which fail.';
$string['task_logmode_none'] = 'Do not log anything';
$string['cachedef_tags'] = 'Tags collections and areas';
$string['cachedef_temp_tables'] = 'Temporary tables cache';
$string['cachedef_userselections'] = 'Data used to persist user selections throughout Moodle';
-$string['cachedef_user_favourite_course_content_items'] = 'User\'s favourite content items (activities, resources and their subtypes)';
+$string['cachedef_user_favourite_course_content_items'] = 'User\'s starred items';
$string['cachedef_user_group_groupings'] = 'User\'s groupings and groups per course';
$string['cachedef_user_course_content_items'] = 'User\'s content items (activities, resources and their subtypes) per course';
$string['cachedef_yuimodules'] = 'YUI Module definitions';
$string['contentdeleted'] = 'The content has been deleted.';
$string['contentnotdeleted'] = 'An error was encountered while trying to delete the content.';
$string['deletecontent'] = 'Delete content';
-$string['deletecontentconfirm'] = '<p>Are you sure you want to delete content <em>\'{$a->name}\'</em>? It will remove the content and all its files.</p><p>This operation can not be undone.</p>';
+$string['deletecontentconfirm'] = 'Are you sure you want to delete the content <em>\'{$a->name}\'</em> and all associated files? This action cannot be undone.';
$string['file'] = 'Upload content';
$string['file_help'] = 'Files may be stored in the content bank for use in courses. Only files used by content types enabled on the site may be uploaded.';
$string['name'] = 'Content';
-$string['nopermissiontodelete'] = 'You have no permissions to delete the content.';
-$string['privacy:metadata:userid'] = 'The ID of the user creating or modifying content bank content.';
+$string['nopermissiontodelete'] = 'You do not have permission to delete content.';
+$string['privacy:metadata:content:contenttype'] = 'The contenttype plugin of the content in the content bank.';
+$string['privacy:metadata:content:name'] = 'Name of the content in the content bank.';
+$string['privacy:metadata:content:timecreated'] = 'The time when the content was created.';
+$string['privacy:metadata:content:timemodified'] = 'The time when the content was modified.';
$string['privacy:metadata:content:usercreated'] = 'The user has created the content.';
+$string['privacy:metadata:content:usercreated'] = 'The user who created the content.';
$string['privacy:metadata:content:usermodified'] = 'Last user has modified the content.';
+$string['privacy:metadata:content:usermodified'] = 'The last user who modified the content.';
+$string['privacy:metadata:contentbankcontent'] = 'Stores the content of the content bank.';
+$string['privacy:metadata:userid'] = 'The ID of the user creating or modifying content bank content.';
$string['timecreated'] = 'Time created';
-$string['unsupported'] = 'This content type is not supported';
+$string['unsupported'] = 'This content type is not supported.';
$string['upload'] = 'Upload';
$string['aria:courseimage'] = 'Course image';
$string['aria:courseshortname'] = 'Course short name';
$string['aria:coursename'] = 'Course name';
-$string['aria:defaulttab'] = 'The default modules';
+$string['aria:defaulttab'] = 'Default activities';
$string['aria:favourite'] = 'Course is starred';
-$string['aria:favouritestab'] = 'Your starred modules';
-$string['aria:recommendedtab'] = 'The recommended modules';
-$string['aria:modulefavourite'] = 'Star {$a} module';
+$string['aria:favouritestab'] = 'Starred activities';
+$string['aria:recommendedtab'] = 'Recommended activities';
+$string['aria:modulefavourite'] = 'Star {$a} activity';
$string['coursealreadyfinished'] = 'Course already finished';
$string['coursenotyetstarted'] = 'The course has not yet started';
$string['coursenotyetfinished'] = 'The course has not yet finished';
$string['favourite'] = 'Starred course';
$string['gradetopassnotset'] = 'This course does not have a grade to pass set. It may be set in the grade item of the course (Gradebook setup).';
$string['informationformodule'] = 'Information about the {$a} activity';
-$string['module'] = 'Module';
+$string['module'] = 'Activity';
$string['nocourseactivity'] = 'Not enough course activity between the start and the end of the course';
$string['nocourseendtime'] = 'The course does not have an end time';
$string['nocoursesections'] = 'No course sections';
$string['enrolmentmethod'] = 'Enrolment method';
$string['enrolments'] = 'Enrolments';
$string['enrolmentoptions'] = 'Enrolment options';
+$string['enrolmentupdatedforuser'] = 'The enrolment for user "{$a->fullname}" has been updated';
$string['enrolnotpermitted'] = 'You do not have permission or are not allowed to enrol someone in this course';
$string['enrolperiod'] = 'Enrolment duration';
$string['enrolusage'] = 'Instances / enrolments';
$string['totalotherusers'] = '{$a} other users';
$string['unassignnotpermitted'] = 'You do not have permission to unassign roles in this course';
$string['unenrol'] = 'Unenrol';
+$string['unenrolleduser'] = 'The user "{$a->fullname}" was unenrolled from the course';
$string['unenrolconfirm'] = 'Do you really want to unenrol "{$a->user}" (previously enrolled via "{$a->enrolinstancename}") from "{$a->course}"?';
$string['unenrolme'] = 'Unenrol me from {$a}';
$string['unenrolnotpermitted'] = 'You do not have permission or can not unenrol this user from this course.';
$string['componentisuptodate'] = 'Component is up-to-date';
$string['confirmationnotenabled'] = 'User confirmation is not enabled on this site';
$string['confirmsesskeybad'] = 'Sorry, but your session key could not be confirmed to carry out this action. This security feature prevents against accidental or malicious execution of important functions in your name. Please make sure you really wanted to execute this function.';
-$string['contenttypenotfound'] = 'The \'{$a}\' content bank type doesn\'t exist or is not recognized';
+$string['contenttypenotfound'] = 'The \'{$a}\' content bank type doesn\'t exist or is not recognised.';
$string['couldnotassignrole'] = 'A serious but unspecified error occurred while trying to assign a role to you';
$string['couldnotupdatenoexistinguser'] = 'Cannot update the user - user doesn\'t exist';
$string['couldnotverifyagedigitalconsent'] = 'An error occurred while trying to verify the age of digital consent.<br />Please contact administrator.';
$string['countriesphpempty'] = 'Error: The file countries.php in language pack {$a} is empty or missing.';
$string['coursedoesnotbelongtocategory'] = 'The course doesn\'t belong to this category';
-$string['courseformatnotfound'] = 'The course format \'{$a}\' doesn\'t exist or is not recognized';
+$string['courseformatnotfound'] = 'The course format \'{$a}\' doesn\'t exist or is not recognised.';
$string['coursegroupunknown'] = 'Course corresponding to group {$a} not specified';
-$string['courseidnotfound'] = 'Course id doesn\'t exist';
+$string['courseidnotfound'] = 'The course ID doesn\'t exist.';
$string['courseidnumbertaken'] = 'ID number is already used for another course ({$a})';
$string['coursemisconf'] = 'Course is misconfigured';
$string['courserequestdisabled'] = 'Sorry, but course requests have been disabled by the administrator.';
$string['noh5plibhandlerdefined'] = 'There isn\'t any H5P framework handler installed, so H5P content can\'t be displayed.';
$string['nojson'] = 'The main h5p.json file is not valid';
$string['nopermissiontodeploy'] = 'This file can\'t be displayed because it has been uploaded by a user without the required capability to deploy H5P content.';
-$string['nopermissiontoedit'] = 'You do not have permission to edit H5P content';
+$string['nopermissiontoedit'] = 'You do not have permission to edit H5P content.';
$string['notrustablefile'] = 'This file can\'t be displayed because it has been uploaded by a user without the capability to update H5P content types. Please contact your administrator to ask for the content type to be installed.';
$string['nounzip'] = 'The file you uploaded is not a valid HTML5 Package. (It is not possible to unzip it.)';
$string['offlineDialogBody'] = 'We were unable to send information about your completion of this task. Please check your internet connection.';
$string['addnousersrecip'] = 'Add users who haven\'t accessed this {$a} to recipient list';
$string['addpagehere'] = 'Add text here';
$string['addresource'] = 'Add a resource...';
-$string['addresourceoractivity'] = 'Add an activity or resource';
+$string['addresourceoractivity'] = 'Add an activity';
$string['addresourcetosection'] = 'Add a resource to section \'{$a}\'';
$string['address'] = 'Address';
$string['addsections'] = 'Add sections';
$string['backtopageyouwereon'] = 'Back to the page you were on';
$string['backup'] = 'Backup';
$string['backupactivehelp'] = 'Choose whether or not to do automated backups.';
-$string['backupadhocpending'] = 'Course backup adhoc task pending';
+$string['backupadhocpending'] = 'Course backup ad hoc task pending';
$string['backupcancelled'] = 'Backup cancelled';
$string['backupcoursefileshelp'] = 'If enabled then course files will be included in automated backups';
$string['backupdate'] = 'Backup date';
$string['changessaved'] = 'Changes saved';
$string['check'] = 'Check';
$string['checks'] = 'Checks';
-$string['checksok'] = 'All \'{$a}\' checks ok';
+$string['checksok'] = 'All \'{$a}\' checks OK';
$string['checkall'] = 'Check all';
$string['checkingbackup'] = 'Checking backup';
$string['checkingcourse'] = 'Checking course';
$string['makeunavailable'] = 'Make unavailable';
$string['manageblocks'] = 'Blocks';
$string['managecategorythis'] = 'Manage this category';
-$string['managecontentbanktypes'] = 'Manage content bank content types';
+$string['managecontentbanktypes'] = 'Manage content types';
$string['managecourses'] = 'Manage courses';
$string['managedataformats'] = 'Manage data formats';
$string['managedatabase'] = 'Database';
$string['moodlerelease'] = 'Moodle release';
$string['more'] = 'more';
$string['morehelp'] = 'More help';
-$string['morehelpaboutmodule'] = 'More help about the {$a} module';
+$string['morehelpaboutmodule'] = 'More help about the {$a} activity';
$string['moreinfo'] = 'More info';
$string['moreinformation'] = 'More information about this error';
$string['moreprofileinfoneeded'] = 'Please tell us more about yourself';
$string['privacy:metadata:log:time'] = 'The time when the action took place';
$string['privacy:metadata:log:url'] = 'The URL related to the event';
$string['privacy:metadata:log:userid'] = 'The ID of the user who performed the action';
-$string['privacy:metadata:task_adhoc'] = 'The status of adhoc tasks.';
+$string['privacy:metadata:task_adhoc'] = 'The status of ad hoc tasks.';
$string['privacy:metadata:task_adhoc:component'] = 'The component owning the task.';
$string['privacy:metadata:task_adhoc:nextruntime'] = 'The earliest time to run this task.';
$string['privacy:metadata:task_adhoc:userid'] = 'The user to run the task as.';
$string['notyourinstances'] = 'You can not view/edit repository instances of another user';
$string['off'] = 'Enabled but hidden';
$string['original'] = 'Original';
-$string['originalextensionchange'] = 'The original file extension has been modified as a part of the file name change. Changing the extension from ".{$a->originalextension}" to ".{$a->newextension}" could potentially cause some side effects.';
-$string['originalextensionremove'] = 'The original file extension has been removed as a part of the file name change. Removing the extension ".{$a}" could potentially cause some side effects.';
+$string['originalextensionchange'] = 'The original file extension has been modified as a part of the file name change. Changing the extension from ".{$a->originalextension}" to ".{$a->newextension}" may result in a file which cannot be opened.';
+$string['originalextensionremove'] = 'The original file extension has been removed as a part of the file name change. Removing the extension ".{$a}" is likely to result in a file which cannot be opened.';
$string['openpicker'] = 'Choose a file...';
$string['operation'] = 'Operation';
$string['on'] = 'Enabled and visible';
$string['confirmunassignyes'] = 'Remove';
$string['confirmunassignno'] = 'Cancel';
$string['contentbank:access'] = 'Access the content bank';
-$string['contentbank:deleteanycontent'] = 'Delete any content from the content bank on the site';
-$string['contentbank:deleteowncontent'] = 'Delete content from the content bank created by the user';
-$string['contentbank:upload'] = 'Upload new content in the content bank';
+$string['contentbank:deleteanycontent'] = 'Delete any content from the content bank';
+$string['contentbank:deleteowncontent'] = 'Delete content from own content bank';
+$string['contentbank:upload'] = 'Upload content to the content bank';
$string['context'] = 'Context';
$string['course:activityvisibility'] = 'Hide/show activities';
$string['course:bulkmessaging'] = 'Send a message to many people';
$string['course:movesections'] = 'Move sections';
$string['course:overridecompletion'] = 'Override activity completion status';
$string['course:renameroles'] = 'Rename roles';
-$string['course:recommendactivity'] = 'Recommend activities to the activity chooser';
+$string['course:recommendactivity'] = 'Recommend activities in the activity chooser';
$string['course:request'] = 'Request new courses';
$string['course:reset'] = 'Reset course';
$string['course:reviewotherusers'] = 'Review other users';
*/
Base.prototype._labels = null;
+ /**
+ * Options for chart legend display.
+ *
+ * @protected
+ * @type {Object}
+ */
+ Base.prototype._legendOptions = null;
+
/**
* The title of the chart.
*
Chart.setConfigColorSet(data