Compare commits

..

65 Commits

Author SHA1 Message Date
Eloy Lafuente (stronk7) 9650f3f56f Moodle release 3.4.9 2019-05-11 23:17:37 +02:00
Eloy Lafuente (stronk7) d44c73e438 weekly release 3.4.8+ 2019-05-10 20:31:09 +02:00
Eloy Lafuente (stronk7) 3020929da8 Merge branch 'install_34_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_34_STABLE 2019-05-10 20:31:07 +02:00
Michael Hawkins 3f4f2da9e0 MDL-61738 messageinbound: Fix quota checks & filesize for email uploads
Private files uploaded by email will now honour the file quota limit,
because the filesize is set correctly and checked against users'
remaining personal quota limit. Previously, attachment size was always
set to zero, and quota was checked against the draft area (this is
not valid for email uploads, because each file is moved out of the
draft area as it is processed, so multiple files totalling greater
than the remaining quota would still pass the check).
2019-05-07 10:17:57 +08:00
Dan Marsden 5a89ac9640 MDL-64708 cohort: Return url is not used anywhere in core. 2019-05-01 17:40:37 +02:00
AMOS bot 6bbf56b9e2 Automatically generated installer lang files 2019-04-30 00:12:42 +00:00
AMOS bot 6be97cb9ac Automatically generated installer lang files 2019-04-27 00:13:00 +00:00
AMOS bot 2fc307e4d4 Automatically generated installer lang files 2019-04-13 00:13:38 +00:00
AMOS bot c426429a08 Automatically generated installer lang files 2019-04-09 00:11:40 +00:00
AMOS bot b848c97164 Automatically generated installer lang files 2019-04-09 00:11:40 +00:00
AMOS bot df5502034a Automatically generated installer lang files 2019-03-29 00:15:44 +00:00
AMOS bot c2c3db84b1 Automatically generated installer lang files 2019-03-25 00:09:40 +00:00
Sara Arjona 52bd62f2be Moodle release 3.4.8 2019-03-08 11:03:07 +01:00
Sara Arjona 53392df219 Merge branch 'install_34_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_34_STABLE 2019-03-08 11:03:06 +01:00
AMOS bot 344386f873 Automatically generated installer lang files 2019-03-08 00:15:41 +00:00
Andrew Nicols 8a2e9542c9 MDL-64971 access: Ensure that the capability exists when fetching 2019-03-06 10:18:03 +08:00
AMOS bot 1f01b678df Automatically generated installer lang files 2019-03-06 00:09:48 +00:00
Jun Pataleta 90740716be Merge branch 'MDL-64799_34' of https://github.com/timhunt/moodle into MOODLE_34_STABLE 2019-03-05 15:52:15 +08:00
Shamim Rezaie 2cebb87682 MDL-64830 core_calendar: Do cap check on event edit form 2019-03-05 04:17:42 +01:00
Damyon Wiese 907b377e51 MDL-64651 comments: Do not send referrer
Use blanktarget option on all comments to prevent malicious links.
2019-03-05 04:17:42 +01:00
Viktor b77dcd23d8 MDL-62702 core_lib: fix broken verification signature 2019-03-05 04:17:42 +01:00
Mark Nelson 1a3612bb5b MDL-62702 core_lib: updated the 'ltiprovider' readme_moodle.txt 2019-03-05 04:17:42 +01:00
Brendan Cox fba7dcd90a MDL-62702 core_lib: parameters in POST override other LTI values
This fixes an exploit where you could alter the 'Content-Type' of the
request and bypass the case-sensitive check 'strstr'. For example,
setting it to 'application/x-www-FORM-urlencoded'. However, changing
this to use 'stristr' was also not an acceptable approach as you
could also bypass it by setting the value to 'multipart/form-data'.
2019-03-05 04:17:42 +01:00
Ryan Wyllie e836242e1c MDL-63786 core_my: prevent dashboard access when logged in as
Stop a "logged in as" session from accessing the user's dashboard
to prevent user JavaScript from being executed in the logged in
as session
2019-03-05 04:17:42 +01:00
Jun Pataleta 6f05d22622 Merge branch 'MDL-64652-34' of git://github.com/andrewnicols/moodle into MOODLE_34_STABLE 2019-03-05 11:13:03 +08:00
Andrew Nicols 141271b726 MDL-64652 mod_data: Break LEFT JOINs to multiple INNER 2019-03-05 10:32:22 +08:00
Andrew Nicols aae3a2fdd4 MDL-64652 glossary: Break LEFT JOINs to multiple INNER 2019-03-05 10:32:22 +08:00
Andrew Nicols abd634d64c MDL-64652 forum: Use an inner join when fetching ratings 2019-03-05 10:32:22 +08:00
Andrew Nicols e24f8994b5 MDL-64652 rating: Add optional inner join option to sql 2019-03-05 10:32:20 +08:00
Andrew Nicols d16e730116 MDL-64652 mod_forum: Performance improvement for provider
There are possibly some further improvements that can be made to:
- export_discussion_data
- export_all_posts
2019-03-05 10:31:51 +08:00
AMOS bot 3cc11668c8 Automatically generated installer lang files 2019-03-04 00:09:59 +00:00
AMOS bot 3c72d44b18 Automatically generated installer lang files 2019-03-01 00:16:04 +00:00
AMOS bot 4546269170 Automatically generated installer lang files 2019-02-27 00:09:59 +00:00
Tim Hunt ad4dfb1e8c MDL-64799 qtype_random: orphaned random questions should be deleted
This is a back-port of MDL-63260

This fix is based on some original code written by Bo Pierce.
2019-02-26 15:59:05 +00:00
AMOS bot 4f6373bf05 Automatically generated installer lang files 2019-02-16 00:14:36 +00:00
Andrew Nicols 31c05704b8 weekly release 3.4.7+ 2019-02-15 08:29:19 +08:00
Andrew Nicols 384c4c29db Merge branch 'install_34_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_34_STABLE 2019-02-15 08:29:17 +08:00
Jun Pataleta 2282d6932b Merge branch 'MDL-63892-34-rework' of git://github.com/mickhawkins/moodle into MOODLE_34_STABLE 2019-02-13 21:00:24 +08:00
Michael Hawkins 47265ccf62 MDL-63892 mod_forum: Behat update for post order 2019-02-13 17:02:29 +08:00
Jun Pataleta 2d0d9a4748 Merge branch 'MDL-64835-34' of git://github.com/andrewnicols/moodle into MOODLE_34_STABLE 2019-02-13 16:26:51 +08:00
Andrew Nicols 0cbb46b944 Merge branch 'MDL-63892-34-rework' of git://github.com/mickhawkins/moodle into MOODLE_34_STABLE 2019-02-13 10:20:13 +08:00
Andrew Nicols 3dcf5f9930 MDL-64835 JS: Reduce cache invalidation checks
The cache invalidation check was previously occuring every time either
the `set` or `get` function was called on the cache. However, the cache
invalidation check is based on the jsrev which is static for the
lifetime of the page.

This change moved the invalidation to happen during the setup of the AMD
module such that it only happens one time per storage type (Local +
Session).
2019-02-13 10:14:33 +08:00
Andrew Nicols 3456505930 MDL-64835 JS: Stop using the jsrev in the jsrevPrefix
We only use the jsrevPrefix to determine if the cache should be
invalidated, but the prefix that we were using is based on the new
jsrev.

For example, the jsrevPrefix will be:

    hash(wwwroot + '/ + config.jsrev) + '/jsrev'

Where config.jsrev is the _current_ (new) jsrev.

As a result when searching for the jsrev used to store the data which is
currently in the storage cache, no key is returned, and we instead set
an 'initial' value and the cache is not cleared

This patch changes the jsrevPrefix to be:

    hash(wwwroot) + '/jsrev'

Since the wwwroot does not change, the key remains static for the
current site. As a result, when the jsrev is bumped via a Moodle cache
purge, we are able to correctly fetch the old jsrev from the cache,
determine that the jsrev has changed, and purge the cache.
2019-02-13 10:14:33 +08:00
AMOS bot 18c80ae8a5 Automatically generated installer lang files 2019-02-13 00:12:01 +00:00
Michael Hawkins 4fccbafa46 MDL-63892 mod_forum: Updating lib unit test 2019-02-12 17:28:23 +08:00
Michael Hawkins 11fc8b3d02 MDL-63892 mod_forum: Fix last post details in forum view 2019-02-12 17:28:17 +08:00
Michael Hawkins 4e190d7eae MDL-63892 mod_forum: Correction to timestamp added to edit strings 2019-02-12 17:28:07 +08:00
Eloy Lafuente (stronk7) acd27d90c8 Merge branch 'MDL-64725-34' of git://github.com/junpataleta/moodle into MOODLE_34_STABLE 2019-02-11 17:06:15 +01:00
Jun Pataleta d81447751e Merge branch 'MDL-64644_34' of git://github.com/markn86/moodle into MOODLE_34_STABLE 2019-02-11 16:46:21 +08:00
Andrew Nicols eb3df7ecad Merge branch 'MDL-64597-34' of git://github.com/junpataleta/moodle into MOODLE_34_STABLE 2019-02-11 16:34:30 +08:00
Mark Nelson a9d153e61d MDL-64644 completion: fixed assertions in privacy test 2019-02-11 14:11:05 +08:00
Mark Nelson c2c8ce4b6d MDL-64644 completion: set status correctly in provider 2019-02-11 14:11:05 +08:00
Mark Nelson 27f608a5c9 MDL-64644 completion: ensure we return array for provider 2019-02-11 14:11:04 +08:00
AMOS bot 181cf34341 Automatically generated installer lang files 2019-02-10 00:10:33 +00:00
Jun Pataleta 56c15f5c99 MDL-64725 composer: Use lowercase for package name of dev dependencies 2019-02-04 09:07:48 +08:00
Jun Pataleta 87711249c3 weekly release 3.4.7+ 2019-02-01 08:57:05 +08:00
Jun Pataleta 0a03b2fd63 Merge branch 'install_34_STABLE' of https://git.in.moodle.com/amosbot/moodle-install into MOODLE_34_STABLE 2019-02-01 08:57:04 +08:00
Eloy Lafuente (stronk7) 398c4c107d Merge branch 'MDL-64737-34-attoprivacysql' of git://github.com/mudrd8mz/moodle into MOODLE_34_STABLE 2019-01-31 22:47:07 +01:00
David Mudrák 5789961754 MDL-64737 editor_atto: Fix the privacy export_user_data() implementation
Multiple issues fixed:

* Fixed order of the statements to avoid access to undefined variables.
* Fixed population of the SQL query parameter to make sure they match
  the placeholder in the query.
* Fixed missing table aliases in the second query to make sure the query
  actually works as expected.
2019-01-31 12:51:19 +01:00
David Mudrák 587a8a9732 MDL-64737 privacy: Fix privacy provider testsuite file names
Only files ending with "_test.php" suffix are implicit part of a
testsuite. So these two were not actually executed.
2019-01-31 12:51:19 +01:00
AMOS bot 53d0efba1f Automatically generated installer lang files 2019-01-30 00:10:32 +00:00
Eloy Lafuente (stronk7) 16045c4846 Merge branch 'MDL-64424-34-expiredfrontpage' of git://github.com/mudrd8mz/moodle into MOODLE_34_STABLE 2019-01-29 23:48:55 +01:00
Jun Pataleta 57d2a28425 MDL-64597 behat: Properly import behat_util when dropping parallel sites 2019-01-24 12:51:35 +08:00
David Mudrák 33cbe249dd MDL-64424 privacy: Use system context purpose on the front page, too
For activities on the site's front page, use the purpose and retention
periods as defined for the system context, not for the courses.
2019-01-23 11:09:02 +01:00
AMOS bot 82e23b9443 Automatically generated installer lang files 2019-01-22 00:10:14 +00:00
65 changed files with 1155 additions and 226 deletions
+11
View File
@@ -109,6 +109,17 @@ require_once(__DIR__ . '/../../../../lib/behat/lib.php');
require_once(__DIR__ . '/../../../../lib/behat/classes/behat_command.php');
require_once(__DIR__ . '/../../../../lib/behat/classes/behat_config_manager.php');
// Remove error handling overrides done in config.php. This is consistent with admin/tool/behat/cli/util_single_run.php.
$CFG->debug = (E_ALL | E_STRICT);
$CFG->debugdisplay = 1;
error_reporting($CFG->debug);
ini_set('display_errors', '1');
ini_set('log_errors', '1');
// Import the necessary libraries.
require_once($CFG->libdir . '/setuplib.php');
require_once($CFG->libdir . '/behat/classes/util.php');
// For drop option check if parallel site.
if ((empty($options['parallel'])) && ($options['drop']) || $options['updatesteps']) {
$options['parallel'] = behat_config_manager::get_behat_run_config_value('parallel');
@@ -920,9 +920,17 @@ class expired_contexts_manager {
* @return bool
*/
protected static function is_course_context_expired_or_unprotected_for_user(\context $context, \stdClass $user) {
$expiryrecords = self::get_nested_expiry_info_for_courses($context->path);
$info = $expiryrecords[$context->path]->info;
if ($context->get_course_context()->instanceid == SITEID) {
// The is an activity in the site course (front page).
$purpose = data_registry::get_effective_contextlevel_value(CONTEXT_SYSTEM, 'purpose');
$info = static::get_expiry_info($purpose);
} else {
$expiryrecords = self::get_nested_expiry_info_for_courses($context->path);
$info = $expiryrecords[$context->path]->info;
}
if ($info->is_fully_expired()) {
// This context is fully expired.
return true;
@@ -2222,6 +2222,40 @@ class tool_dataprivacy_expired_contexts_testcase extends advanced_testcase {
$this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($blockcontext, $user));
}
/**
* Test the is_context_expired functions when supplied with the front page course.
*/
public function test_is_context_expired_frontpage() {
$this->resetAfterTest();
$purposes = $this->setup_basics('PT1H', 'PT1H', 'P1D');
$frontcourse = get_site();
$frontcoursecontext = \context_course::instance($frontcourse->id);
$sitenews = $this->getDataGenerator()->create_module('forum', ['course' => $frontcourse->id]);
$cm = get_coursemodule_from_instance('forum', $sitenews->id);
$sitenewscontext = \context_module::instance($cm->id);
$user = $this->getDataGenerator()->create_user(['lastaccess' => time() - YEARSECS]);
$this->assertFalse(expired_contexts_manager::is_context_expired($frontcoursecontext));
$this->assertFalse(expired_contexts_manager::is_context_expired($sitenewscontext));
$this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($frontcoursecontext, $user));
$this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($sitenewscontext, $user));
// Protecting the course contextlevel does not impact the front page.
$purposes->course->set('protected', 1)->save();
$this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($frontcoursecontext, $user));
$this->assertTrue(expired_contexts_manager::is_context_expired_or_unprotected_for_user($sitenewscontext, $user));
// Protecting the system contextlevel affects the front page, too.
$purposes->system->set('protected', 1)->save();
$this->assertFalse(expired_contexts_manager::is_context_expired_or_unprotected_for_user($frontcoursecontext, $user));
$this->assertFalse(expired_contexts_manager::is_context_expired_or_unprotected_for_user($sitenewscontext, $user));
}
/**
* Test the is_context_expired functions when supplied with an expired course.
*/
+1 -1
View File
@@ -116,7 +116,7 @@ class tool_lp_external_testcase extends externallib_advanced_testcase {
$this->userrole = create_role('User role', 'lpuserrole', 'learning plan user role description');
assign_capability('moodle/competency:competencymanage', CAP_ALLOW, $this->creatorrole, $syscontext->id);
assign_capability('moodle/competency:competencycompetencyconfigure', CAP_ALLOW, $this->creatorrole, $syscontext->id);
assign_capability('moodle/competency:coursecompetencyconfigure', CAP_ALLOW, $this->creatorrole, $syscontext->id);
assign_capability('moodle/competency:planmanage', CAP_ALLOW, $this->creatorrole, $syscontext->id);
assign_capability('moodle/competency:planmanagedraft', CAP_ALLOW, $this->creatorrole, $syscontext->id);
assign_capability('moodle/competency:planmanageown', CAP_ALLOW, $this->creatorrole, $syscontext->id);
@@ -672,7 +672,7 @@ class manager {
$attachment->charset = $partdata->getCharset();
$attachment->description = $partdata->getDescription();
$attachment->contentid = $partdata->getContentId();
$attachment->filesize = $messagedata->getBodyPartSize($part);
$attachment->filesize = $partdata->getBytes();
if (!empty($CFG->antiviruses)) {
mtrace("--> Attempting virus scan of '{$attachment->filename}'");
+6
View File
@@ -4446,6 +4446,12 @@ class restore_create_categories_and_questions extends restore_structure_step {
// With newitemid = 0, let's create the question
if (!$questionmapping->newitemid) {
if ($data->qtype === 'random') {
// Ensure that this newly created question is considered by
// \qtype_random\task\remove_unused_questions.
$data->hidden = 0;
}
$newitemid = $DB->insert_record('question', $data);
$this->set_mapping('question', $oldid, $newitemid);
// Also annotate them as question_created, we need
+5
View File
@@ -3561,6 +3561,11 @@ function calendar_output_fragment_event_form($args) {
$mform->set_data($data);
} else {
$event = calendar_event::load($eventid);
if (!calendar_edit_event_allowed($event)) {
print_error('nopermissiontoupdatecalendar');
}
$mapper = new \core_calendar\local\event\mappers\create_update_form_mapper();
$eventdata = $mapper->from_legacy_event_to_data($event);
$data = array_merge((array) $eventdata, $data);
+2 -7
View File
@@ -28,7 +28,6 @@ require_once($CFG->dirroot.'/cohort/upload_form.php');
require_once($CFG->libdir . '/csvlib.class.php');
$contextid = optional_param('contextid', 0, PARAM_INT);
$returnurl = optional_param('returnurl', '', PARAM_URL);
require_login();
@@ -56,13 +55,9 @@ if ($context->contextlevel == CONTEXT_COURSECAT) {
navigation_node::override_active_url(new moodle_url('/cohort/index.php', array()));
}
$uploadform = new cohort_upload_form(null, array('contextid' => $context->id, 'returnurl' => $returnurl));
$uploadform = new cohort_upload_form(null, array('contextid' => $context->id));
if ($returnurl) {
$returnurl = new moodle_url($returnurl);
} else {
$returnurl = new moodle_url('/cohort/index.php', array('contextid' => $context->id));
}
$returnurl = new moodle_url('/cohort/index.php', array('contextid' => $context->id));
if ($uploadform->is_cancelled()) {
redirect($returnurl);
-3
View File
@@ -48,9 +48,6 @@ class cohort_upload_form extends moodleform {
$mform = $this->_form;
$data = (object)$this->_customdata;
$mform->addElement('hidden', 'returnurl');
$mform->setType('returnurl', PARAM_URL);
$mform->addElement('header', 'cohortfileuploadform', get_string('uploadafile'));
$filepickeroptions = array();
+3 -1
View File
@@ -102,6 +102,7 @@ class core_comment_external extends external_api {
if ($comments === false) {
throw new moodle_exception('nopermissions', 'error', '', 'view comments');
}
$options = array('blanktarget' => true);
foreach ($comments as $key => $comment) {
@@ -110,7 +111,8 @@ class core_comment_external extends external_api {
$context->id,
$params['component'],
'',
0);
0,
$options);
}
$results = array(
+3 -2
View File
@@ -570,7 +570,7 @@ class comment {
$params['itemid'] = $this->itemid;
$comments = array();
$formatoptions = array('overflowdiv' => true);
$formatoptions = array('overflowdiv' => true, 'blanktarget' => true);
$rs = $DB->get_recordset_sql($sql, $params, $start, $perpage);
foreach ($rs as $u) {
$c = new stdClass();
@@ -717,7 +717,8 @@ class comment {
$newcmt->fullname = fullname($USER);
$url = new moodle_url('/user/view.php', array('id' => $USER->id, 'course' => $this->courseid));
$newcmt->profileurl = $url->out();
$newcmt->content = format_text($newcmt->content, $newcmt->format, array('overflowdiv'=>true));
$formatoptions = array('overflowdiv' => true, 'blanktarget' => true);
$newcmt->content = format_text($newcmt->content, $newcmt->format, $formatoptions);
$newcmt->avatar = $OUTPUT->user_picture($USER, array('size'=>16));
$commentlist = array($newcmt);
+1 -1
View File
@@ -68,7 +68,7 @@ class comment_manager {
ON u.id=c.userid
ORDER BY c.timecreated ASC";
$rs = $DB->get_recordset_sql($sql, null, $start, $this->perpage);
$formatoptions = array('overflowdiv' => true);
$formatoptions = array('overflowdiv' => true, 'blanktarget' => true);
foreach ($rs as $item) {
// Set calculated fields
$item->fullname = fullname($item);
+1 -1
View File
@@ -139,7 +139,7 @@ class core_competency_external_testcase extends externallib_advanced_testcase {
$this->userrole = create_role('User role', 'userrole', 'learning plan user role description');
assign_capability('moodle/competency:competencymanage', CAP_ALLOW, $this->creatorrole, $syscontext->id);
assign_capability('moodle/competency:competencycompetencyconfigure', CAP_ALLOW, $this->creatorrole, $syscontext->id);
assign_capability('moodle/competency:coursecompetencyconfigure', CAP_ALLOW, $this->creatorrole, $syscontext->id);
assign_capability('moodle/competency:competencyview', CAP_ALLOW, $this->userrole, $syscontext->id);
assign_capability('moodle/competency:planmanage', CAP_ALLOW, $this->creatorrole, $syscontext->id);
assign_capability('moodle/competency:planmanagedraft', CAP_ALLOW, $this->creatorrole, $syscontext->id);
+43 -35
View File
@@ -161,47 +161,55 @@ class provider implements
$completioninfo = new \completion_info($course);
$completion = $completioninfo->is_enabled();
if ($completion == COMPLETION_ENABLED) {
if ($completion != COMPLETION_ENABLED) {
return [];
}
$coursecomplete = $completioninfo->is_course_complete($user->id);
$coursecomplete = $completioninfo->is_course_complete($user->id);
if ($coursecomplete) {
$status = get_string('complete');
} else {
$criteriacomplete = $completioninfo->count_course_user_data($user->id);
$ccompletion = new \completion_completion(['userid' => $user->id, 'course' => $course->id]);
$status = ($coursecomplete) ? get_string('complete') : '';
$status = (!$criteriacomplete && !$ccompletion->timestarted) ? get_string('notyetstarted', 'completion') :
get_string('inprogress', 'completion');
$completions = $completioninfo->get_completions($user->id);
$overall = get_string('nocriteriaset', 'completion');
if (!empty($completions)) {
if ($completioninfo->get_aggregation_method() == COMPLETION_AGGREGATION_ALL) {
$overall = get_string('criteriarequiredall', 'completion');
} else {
$overall = get_string('criteriarequiredany', 'completion');
}
if (!$criteriacomplete && !$ccompletion->timestarted) {
$status = get_string('notyetstarted', 'completion');
} else {
$status = get_string('inprogress', 'completion');
}
$coursecompletiondata = [
'status' => $status,
'required' => $overall,
];
$coursecompletiondata['criteria'] = array_map(function($completion) use ($completioninfo) {
$criteria = $completion->get_criteria();
$aggregation = $completioninfo->get_aggregation_method($criteria->criteriatype);
$required = ($aggregation == COMPLETION_AGGREGATION_ALL) ? get_string('all', 'completion') :
get_string('any', 'completion');
$data = [
'required' => $required,
'completed' => transform::yesno($completion->is_complete()),
'timecompleted' => isset($completion->timecompleted) ? transform::datetime($completion->timecompleted) : ''
];
$details = $criteria->get_details($completion);
$data = array_merge($data, $details);
return $data;
}, $completions);
return $coursecompletiondata;
}
$completions = $completioninfo->get_completions($user->id);
$overall = get_string('nocriteriaset', 'completion');
if (!empty($completions)) {
if ($completioninfo->get_aggregation_method() == COMPLETION_AGGREGATION_ALL) {
$overall = get_string('criteriarequiredall', 'completion');
} else {
$overall = get_string('criteriarequiredany', 'completion');
}
}
$coursecompletiondata = [
'status' => $status,
'required' => $overall,
];
$coursecompletiondata['criteria'] = array_map(function($completion) use ($completioninfo) {
$criteria = $completion->get_criteria();
$aggregation = $completioninfo->get_aggregation_method($criteria->criteriatype);
$required = ($aggregation == COMPLETION_AGGREGATION_ALL) ? get_string('all', 'completion') :
get_string('any', 'completion');
$data = [
'required' => $required,
'completed' => transform::yesno($completion->is_complete()),
'timecompleted' => isset($completion->timecompleted) ? transform::datetime($completion->timecompleted) : ''
];
$details = $criteria->get_details($completion);
$data = array_merge($data, $details);
return $data;
}, $completions);
return $coursecompletiondata;
}
/**
+21 -3
View File
@@ -193,13 +193,31 @@ class core_completion_privacy_test extends \core_privacy\tests\provider_testcase
$hasno = array_search('No', $coursecompletion1['criteria'], true);
$this->assertFalse($hasno);
$coursecompletion2 = \core_completion\privacy\provider::get_course_completion_info($user2, $this->course);
$hasyes = array_search('Yes', $coursecompletion1['criteria'], true);
$hasyes = array_search('Yes', $coursecompletion2['criteria'], true);
$this->assertFalse($hasyes);
$coursecompletion3 = \core_completion\privacy\provider::get_course_completion_info($user3, $this->course);
$hasno = array_search('No', $coursecompletion1['criteria'], true);
$hasno = array_search('No', $coursecompletion3['criteria'], true);
$this->assertFalse($hasno);
$coursecompletion4 = \core_completion\privacy\provider::get_course_completion_info($user4, $this->course);
$hasyes = array_search('Yes', $coursecompletion1['criteria'], true);
$hasyes = array_search('Yes', $coursecompletion4['criteria'], true);
$this->assertFalse($hasyes);
}
/**
* Test getting course completion information with completion disabled.
*/
public function test_get_course_completion_info_completion_disabled() {
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
$course = $this->getDataGenerator()->create_course(['enablecompletion' => 0]);
$this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
$coursecompletion = \core_completion\privacy\provider::get_course_completion_info($user, $course);
$this->assertTrue(is_array($coursecompletion));
$this->assertEmpty($coursecompletion);
}
}
+2 -2
View File
@@ -6,8 +6,8 @@
"homepage": "https://moodle.org",
"require-dev": {
"phpunit/phpunit": "6.5.*",
"phpunit/dbUnit": "3.0.*",
"phpunit/dbunit": "3.0.*",
"moodlehq/behat-extension": "3.34.2",
"mikey179/vfsStream": "^1.6"
"mikey179/vfsstream": "^1.6"
}
}
+1
View File
@@ -42,3 +42,4 @@ $string['cliunknowoption'] = 'خيارات غير معروفة
$string['cliyesnoprompt'] = 'ادخل (Y) تعني نعم أو (N) تعني لأ';
$string['environmentrequireinstall'] = 'مطلوب تثبيته/تفيله';
$string['environmentrequireversion'] = 'مطلوب الأصدار {$a->needed} ، وانت تستخدم الأصدار {$a->current}';
$string['upgradekeyset'] = 'مفتاح التحديث (اتركها فارغة إن لم ترغب بتحديده)';
+33
View File
@@ -0,0 +1,33 @@
<?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/>.
/**
* Automatically generated strings for Moodle installer
*
* Do not edit this file manually! It contains just a subset of strings
* needed during the very first steps of installation. This file was
* generated automatically by export-installer.php (which is part of AMOS
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
* list of strings defined in /install/stringnames.txt.
*
* @package installer
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$string['thislanguage'] = 'Bamanankan';
+34
View File
@@ -0,0 +1,34 @@
<?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/>.
/**
* Automatically generated strings for Moodle installer
*
* Do not edit this file manually! It contains just a subset of strings
* needed during the very first steps of installation. This file was
* generated automatically by export-installer.php (which is part of AMOS
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
* list of strings defined in /install/stringnames.txt.
*
* @package installer
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$string['parentlanguage'] = 'de';
$string['thislanguage'] = 'Deutsch für Arbeitsplatz';
+3 -1
View File
@@ -67,7 +67,9 @@ $string['pathsunsecuredataroot'] = 'Η τοποθεσία του Φάκελου
$string['pathswrongadmindir'] = 'Ο Φάκελος Admin δεν υπάρχει';
$string['phpextension'] = 'Επέκταση {$a} της PHP';
$string['phpversion'] = 'Έκδοση της PHP';
$string['phpversionhelp'] = 'p>Το Moodle απαιτεί η έκδοση της PHP να είναι τουλάχιστον 4.3.0 ή 5.1.05.0.x έχει έναν αριθμό γνωστών προβλημάτων).</p> <p>Αυτή τη στιγμή έχετε την έκδοση {$a}</p> <p>Πρέπει να αναβαθμίσετε την PHP ή να μεταφερθείτε σε έναν κεντρικό υπολογιστή με μια νεότερη έκδοση της PHP!<br/> (Σε περίπτωση που έχετε την 5.0.x μπορείτε επίσης να κάνετε και υποβάθμιση στην έκδοση 4.4.x)</p>';
$string['phpversionhelp'] = 'p>Το Moodle απαιτεί η έκδοση της PHP να είναι τουλάχιστον 5.6.5 ή 7.1 (η 7.0.x έχει κάποιους περιορισμούς στη μηχανή).</p>
<p>Αυτή τη στιγμή έχετε την έκδοση {$a}</p>
<p>Πρέπει να αναβαθμίσετε την PHP ή να μεταφερθείτε σε έναν κεντρικό Η/Υ με μια νεότερη έκδοση της PHP!</p>';
$string['welcomep10'] = '{$a->installername} ({$a->installerversion})';
$string['welcomep20'] = 'Βλέπετε αυτή τη σελίδα γιατί εγκαταστήσατε και ξεκινήσατε με επιτυχία το πακέτο <strong>{$a->packname} {$a->packversion}</strong> στον υπολογιστή σας. Συγχαρητήρια!';
$string['welcomep30'] = 'Αυτή η έκδοση/διανομή <strong>{$a->installername}</strong> περιλαμβάνει τις εφαρμογές για τη δημιουργία ενός περιβάλλοντος μέσα στο οποίο θα λειτουργεί το <strong>Moodle</strong>, ονομαστικά:';
+1
View File
@@ -30,5 +30,6 @@
defined('MOODLE_INTERNAL') || die();
$string['parentlanguage'] = '';
$string['thisdirection'] = 'ltr';
$string['thislanguage'] = 'Ελληνικά';
+33
View File
@@ -0,0 +1,33 @@
<?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/>.
/**
* Automatically generated strings for Moodle installer
*
* Do not edit this file manually! It contains just a subset of strings
* needed during the very first steps of installation. This file was
* generated automatically by export-installer.php (which is part of AMOS
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
* list of strings defined in /install/stringnames.txt.
*
* @package installer
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$string['thislanguage'] = 'English for Workplace';
+34
View File
@@ -0,0 +1,34 @@
<?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/>.
/**
* Automatically generated strings for Moodle installer
*
* Do not edit this file manually! It contains just a subset of strings
* needed during the very first steps of installation. This file was
* generated automatically by export-installer.php (which is part of AMOS
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
* list of strings defined in /install/stringnames.txt.
*
* @package installer
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$string['parentlanguage'] = 'es';
$string['thislanguage'] = 'Español para la Empresa';
+1 -1
View File
@@ -35,7 +35,7 @@ $string['cliansweryes'] = 'b';
$string['cliincorrectvalueerror'] = 'Errorea, "{$a->value}" balioa ez da egokia "{$a->option}"-rako';
$string['cliincorrectvalueretry'] = 'Balio ezegokia, mesedez saiatu berriz';
$string['clitypevalue'] = 'Sartu balioa';
$string['clitypevaluedefault'] = 'sartu balioa, sakatu Enter-i berezko balioa erabiltzeko ({$a})';
$string['clitypevaluedefault'] = 'sartu balioa, sakatu Enter-i lehenetsitako balioa erabiltzeko ({$a})';
$string['cliunknowoption'] = 'Aukera ezezagunak:
{$a}
Mesedez, erabili --help aukera.';
+4 -5
View File
@@ -33,7 +33,7 @@ defined('MOODLE_INTERNAL') || die();
$string['admindirname'] = 'Admin direktorioa';
$string['availablelangs'] = 'Hizkuntza-pakete eskuragarriak';
$string['chooselanguagehead'] = 'Aukeratu hizkuntza bat';
$string['chooselanguagesub'] = 'Mesedez, aukeratu instalaziorako hizkuntza bat. Hizkuntza hori erabiliko da gunearen berezko hizkuntza gisa, baina aurrerago alda daiteke.';
$string['chooselanguagesub'] = 'Mesedez, aukeratu instalaziorako hizkuntza bat. Hizkuntza hori erabiliko da gunearen hizkuntza lehenetsi gisa, baina aurrerago alda daiteke.';
$string['clialreadyconfigured'] = 'Dagoeneko badago config.php konfigurazio-fitxategia. Mesedez erabili admin/cli/install_database.php Moodle gune honetan instalatu nahi baduzu.';
$string['clialreadyinstalled'] = 'Dagoeneko badago config.php konfigurazio-fitxategia. Mesedez erabili admin/cli/upgrade.php zure Moodle gunea eguneratu nahi baduzu.';
$string['cliinstallheader'] = 'Moodle {$a} komando-lerro bidezko instalaziorako programa';
@@ -71,7 +71,7 @@ $string['pathserrcreatedataroot'] = 'Instalatzaileak ezin du datu-karpeta ({$a->
$string['pathshead'] = 'Egiaztatu bideak';
$string['pathsrodataroot'] = 'Dataroot direktorioa ez da idazteko modukoa.';
$string['pathsroparentdataroot'] = 'Goragoko karpeta ({$a->parent}) ez da idazteko modukoa. Instalatzaileak ezin du datu-karpeta ({$a->dataroot}) sortu.';
$string['pathssubadmindir'] = 'Web ostalari gutxi batzuk /admin URL berezi gisa erabiltzen dute kontrol-panel edo antzekora sarbidea emateko. Zoritxarrez, honek Moodle-ren kudeatze-orrien berezko kokapenarekin gatazka sortzen du. Hau konpondu dezakezu zure instalazioko admin karpeta berrizendatuz, eta izen berria hemen sartuta. Adibidez <em>moodleadmin</em>. Honek Moodle-ko admin estekak konponduko du.';
$string['pathssubadmindir'] = 'Web ostalari gutxi batzuk /admin URL berezi gisa erabiltzen dute kontrol-panel edo antzekora sarbidea emateko. Zoritxarrez, honek Moodle-ren kudeatze-orrien lehenetsitako kokapenarekin gatazka sortzen du. Hau konpondu dezakezu zure instalazioko admin karpeta berrizendatuz, eta izen berria hemen sartuta. Adibidez <em>moodleadmin</em>. Honek Moodle-ko admin estekak konponduko du.';
$string['pathssubdataroot'] = '<p>Moodle-k erabiltzaileek igotako fitxategien edukiak bilduko dituen direktorio bat.</p>
<p>Direktorio honetan web-zerbitzariaren erabiltzaileak irakurtzeko eta idazteko baimena izan beharko ditu (normalean \'www-data\', \'nobody\', edo \'apache\').</p>
<p>Ez litzateke web bidez eskuragarri egon beharko.</p>
@@ -90,9 +90,8 @@ $string['phpversionhelp'] = '<p>Moodle-k PHP 5.6.5 edo 7.1 bertsioetako bat beha
<p>PHP eguneratu edo PHP bertsio berriagoa duen zerbitzari batera jo.</p>';
$string['welcomep10'] = '{$a->installername} ({$a->installerversion})';
$string['welcomep20'] = 'Orri hau ikusten baduzu <strong>{$a->packname} {$a->packversion}</strong> paketea zure ordenagailuan instalatu ahal izan duzu. Zorionak!';
$string['welcomep30'] = '<strong>{$a->installername}</strong>ren bertsio honek <strong>Moodle</strong>k
zure ordenadorean funtzionatzeko behar diren aplikazioak dauzka,
bereziki:';
$string['welcomep30'] = '<strong>{$a->installername}</strong>ren bertsio honek <strong>Moodle</strong>-k
zure ordenagailuan funtzionatzeko behar diren aplikazioak dauzka, zehazki hurrengoak:';
$string['welcomep40'] = 'Paketeak ere zera dauka: <strong>Moodle {$a->moodlerelease} ({$a->moodleversion})</strong>.';
$string['welcomep50'] = 'Paketeko aplikazio guztien erabilpena dagozkien lizentziek
arautzen dute. <strong>{$a->installername}</strong> aplikazioak
+1
View File
@@ -31,6 +31,7 @@
defined('MOODLE_INTERNAL') || die();
$string['language'] = 'Kieli';
$string['moodlelogo'] = 'Moodlen logo';
$string['next'] = 'Seuraava';
$string['previous'] = 'Edellinen';
$string['reload'] = 'Lataa uudelleen';
+1
View File
@@ -32,3 +32,4 @@ defined('MOODLE_INTERNAL') || die();
$string['language'] = 'भाषा';
$string['next'] = 'अगला';
$string['reload'] = 'सीमा से अधिक लादना';
+2 -1
View File
@@ -31,7 +31,7 @@
defined('MOODLE_INTERNAL') || die();
$string['clianswerno'] = 'n';
$string['cliansweryes'] = 'y';
$string['cliansweryes'] = 'd';
$string['cliincorrectvalueerror'] = 'Pogreška, netočna vrijednost "{$a->value}" za "{$a->option}"';
$string['cliincorrectvalueretry'] = 'Netočna vrijednost, pokušajte ponovno';
$string['clitypevalue'] = 'unesite vrijednost';
@@ -40,3 +40,4 @@ $string['cliunknowoption'] = 'Nepoznate opcije: {$a} Molimo koristite --help opc
$string['cliyesnoprompt'] = 'unesite y (znači da) ili n (znači ne)';
$string['environmentrequireinstall'] = 'je neophodno instalirati/omogućiti';
$string['environmentrequireversion'] = 'neophodna inačica je {$a->needed}, a vi trenutačno koristite inačicu {$a->current}';
$string['upgradekeyset'] = 'Ključ za ažuriranje (ostavite prazno kako ga ne bi zadali)';
+1
View File
@@ -42,6 +42,7 @@ $string['cannotsavemd5file'] = 'Nije moguće pohraniti md5 datoteku';
$string['cannotsavezipfile'] = 'Nije moguće pohraniti ZIP datoteku';
$string['cannotunzipfile'] = 'Nije moguće otpakirati datoteku';
$string['componentisuptodate'] = 'Komponenta je dostupna u svojoj najnovijoj inačici.';
$string['dmlexceptiononinstall'] = '<p>Dogodila se pogreška baze podataka [{$a->errorcode}].<br />{$a->debuginfo}';
$string['downloadedfilecheckfailed'] = 'Došlo je do pogreške pri provjeri preuzete datoteke';
$string['invalidmd5'] = 'Neispravna md5 datoteka';
$string['missingrequiredfield'] = 'Nedostaje neko obvezatno polje';
+3 -3
View File
@@ -31,7 +31,7 @@
defined('MOODLE_INTERNAL') || die();
$string['admindirname'] = 'Admin mapa';
$string['availablelangs'] = 'Popis dostupnih jezika';
$string['availablelangs'] = 'Popis dostupnih jezičnih paketa';
$string['chooselanguagehead'] = 'Odaberite jezik';
$string['chooselanguagesub'] = 'Molimo odaberite jezik instalacije. Ovaj jezik će biti korišten kao zadani jezik sustava, a kasnije to možete lagano promijeniti.';
$string['clialreadyconfigured'] = 'Datoteka config.php već postoji. Upotrijebite naredbu admin/cli/install_database.php ako želite nastaviti instalaciju.';
@@ -68,7 +68,7 @@ $string['pathsroparentdataroot'] = 'Nije moguće zapisivati podatke u nadređenu
$string['pathssubadmindir'] = 'Manji broj webhosting tvrtki koristi /admin kao posebni URL za Vaš pristup upravljanju vašim hosting paketom. Nažalost, to rezultira konfliktom sa standardnom lokacijom za Moodle administratorsku stranicu. Navedenu lokaciju unutar Moodle sustava možete preimenovati. Na primjer: <br /> <br /><em>moodleadmin</em><br /> <br />
Ovo će promijeniti administratorsku poveznicu na Moodle sustavu u novu vrijednost.';
$string['pathssubdataroot'] = 'Mora postojati mapa u koju Moodle može pohraniti prenešene datoteke. Korisnik pod kojim je pokrenut web server (obično \'nobody\' ili \'apache\') bi morao imati mogućnost čitanja/pisanja podataka u toj mapi, ali oni ne bi trebali biti dostupni direktno preko weba. Instalacijska skripta će pokušati stvoriti navedenu mapu ako ista ne postoji.';
$string['pathssubdirroot'] = 'Puna putanja (PATH) do Moodle instalacije.';
$string['pathssubdirroot'] = '<p>Puna putanja (PATH) do Moodle instalacije.</p>';
$string['pathssubwwwroot'] = 'Unesite punu web adresu putem koje će se pristupati vašem Moodle sustavu.
Moodle sustavu NIJE MOGUĆE pristupiti preko više URL-ova, odaberite onaj koji vam najviše odgovara.
Ako vaš poslužitelj ima višestruke javne adrese, onda morate postaviti tzv. permanent redirect na sve osim ove adrese.
@@ -85,5 +85,5 @@ $string['welcomep30'] = 'Ova inačica <strong>{$a->installername}</strong> uklju
$string['welcomep40'] = 'Ovaj paket obuhvaća i <strong>Moodle {$a->moodlerelease} ({$a->moodleversion})</strong>.';
$string['welcomep50'] = 'Korištenje svih aplikacija u ovom paketu je uređeno licencama pod kojima su izdane. Cijeli paket <strong>{$a->installername}</strong> je rješenje <a href="http://www.opensource.org/docs/definition_plain.html">otvorenog koda</a> distribuirano pod <a href="http://www.gnu.org/copyleft/gpl.html">GPL</a> licencom.';
$string['welcomep60'] = 'Sljedeće stranice će vas voditi kroz postupak instalacije i podešavanja sustava <em>Moodle</em> na poslužitelju. Možete prihvatiti zadane postavke ili promijeniti iste sukladno vašim potrebama.';
$string['welcomep70'] = 'Kliknite na "Next" gumb kako bi započeli <strong>Moodle</strong> instalaciju.';
$string['welcomep70'] = 'Kliknite na "Next" gumb kako biste započeli <strong>Moodle</strong> instalaciju.';
$string['wwwroot'] = 'Web adresa';
+34
View File
@@ -0,0 +1,34 @@
<?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/>.
/**
* Automatically generated strings for Moodle installer
*
* Do not edit this file manually! It contains just a subset of strings
* needed during the very first steps of installation. This file was
* generated automatically by export-installer.php (which is part of AMOS
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
* list of strings defined in /install/stringnames.txt.
*
* @package installer
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$string['parentlanguage'] = 'it';
$string['thislanguage'] = 'Italiano per Workplace';
+2 -2
View File
@@ -73,9 +73,9 @@ $string['pathssubdataroot'] = '<p>ユーザによってアップロードされ
<p>ウェブからは直接アクセスできないようにしてください。</p>
<p>現在ディレクトリが存在しない場合、インストレーションプロセスは作成を試みます。</p';
$string['pathssubdirroot'] = '<p>Moodleコードを含むディレクトリに関するフルパスです。</p>';
$string['pathssubwwwroot'] = '<p>Moodleにアクセスすることのできるフルウェブアドレスです。例えばユーザがブラウザのアドレスバーに入力してMoodleにアクセスするためのアドレスです。</p>
$string['pathssubwwwroot'] = '<p>Moodleにアクセスできるフルウェブアドレスです。例えばユーザがブラウザのアドレスバーに入力してMoodleにアクセスするためのアドレスです。</p>
<p>複数アドレスを使用しMoodleアクセスすることはできません。あなたのサイトに複数アドレスからアクセスできる場合、最も簡単なアドレスを選択して、すべてのアドレスにパーマネントリダイレクトを設定してください。</p>
<p>複数アドレスを使用しMoodleへのアクセスはできません。あなたのサイトに複数アドレスからアクセスできる場合、最も簡単なアドレスを選択して、すべてのアドレスにパーマネントリダイレクトを設定してください。</p>
<p>あなたのサイトにインターネットおよび内部ネットワーク (イントラネットと呼ばれます) からアクセスできる場合、ここではパブリックアドレスを使用してください。</p>
+34
View File
@@ -0,0 +1,34 @@
<?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/>.
/**
* Automatically generated strings for Moodle installer
*
* Do not edit this file manually! It contains just a subset of strings
* needed during the very first steps of installation. This file was
* generated automatically by export-installer.php (which is part of AMOS
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
* list of strings defined in /install/stringnames.txt.
*
* @package installer
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$string['parentlanguage'] = 'no';
$string['thislanguage'] = 'Norsk Workplace';
+3 -3
View File
@@ -33,10 +33,10 @@ defined('MOODLE_INTERNAL') || die();
$string['clianswerno'] = 'n';
$string['cliansweryes'] = 's';
$string['cliincorrectvalueerror'] = 'Erro: o valor "{$a->value}" não é permitido para a opção "{$a->option}"';
$string['cliincorrectvalueretry'] = 'Valor incorreto, por favor tente novamente';
$string['cliincorrectvalueretry'] = 'Valor incorreto, por favor, tente novamente';
$string['clitypevalue'] = 'introduza valor';
$string['clitypevaluedefault'] = 'introduza valor, pressione a tecla Enter para usar o valor predefinido ({$a})';
$string['cliunknowoption'] = 'Opções desconhecidas: {$a}. Por favor use a opção --help';
$string['clitypevaluedefault'] = 'introduza valor, pressione a tecla \'Enter\' para usar o valor predefinido ({$a})';
$string['cliunknowoption'] = 'Opções desconhecidas: {$a}. Por favor, use a opção --help';
$string['cliyesnoprompt'] = 'digite s (para sim) ou n (para não)';
$string['environmentrequireinstall'] = 'deve estar instalada e ativa';
$string['environmentrequireversion'] = 'é requerida a versão {$a->needed} e está a correr a versão {$a->current}';
+1 -1
View File
@@ -49,7 +49,7 @@ $string['environmentsub2'] = 'Cada nova versão do Moodle tem pré-requisitos m
$string['errorsinenvironment'] = 'A verificação do sistema falhou!';
$string['installation'] = 'Instalação';
$string['langdownloaderror'] = 'Não foi possível descarregar o idioma <b>{$a}</b> . O processo de instalação continuará em Inglês.';
$string['memorylimithelp'] = '<p>O limite de memória para o PHP definido atualmente no servidor é <b>{$a}</b>.</p><p>Um número elevado de módulos em utilização ou de utilizadores registados pode fazer com que o Moodle apresente problemas de falta de memória.</p><p>É recomendado que o PHP seja configurado com um limite de memória de pelo menos 40MB. Esta configuração pode ser definida de diversas formas:</p><ol><li>Compilação do PHP com o parâmetro <b>--enable-memory-limit</b>. Esta definição permitirá ao próprio Moodle definir o valor a utilizar.</li><li>Alteração do parâmetro <b>memory_limit</b> no ficheiro de configuração do PHP para um valor igual ou superior a 40MB.</li><li>Criação de um ficheiro <b>.htaccess</b> na raiz da pasta do Moodle com a linha <b>php_value memory_limit 40M</b><p>ATENÇÃO: Em alguns servidores esta configuração impedirá o funcionamento de <b>todas</b> as páginas PHP. Nestes casos, não poderá ser utilizado o ficheiro <b>.htaccess</b>.</p></li></ol>';
$string['memorylimithelp'] = '<p>O limite de memória para o PHP definido atualmente no servidor é <b>{$a}</b>.</p><p>Um número elevado de módulos em utilização ou de utilizadores registados pode fazer com que o Moodle apresente problemas de falta de memória.</p><p>É recomendado que o PHP seja configurado com um limite de memória de pelo menos 40MB. Esta configuração pode ser definida de diversas formas:</p><ol><li>Compilação do PHP com o parâmetro <b>--enable-memory-limit</b>. Esta definição permitirá ao próprio Moodle definir o valor a utilizar.</li><li>Alteração do parâmetro <b>memory_limit</b> no ficheiro de configuração do PHP para um valor igual ou superior a 40MB.</li><li>Criação de um ficheiro <b>.htaccess</b> na raiz da pasta do Moodle com a linha <b>php_value memory_limit 40M</b><p><strong>Atenção</strong>: Em alguns servidores esta configuração impedirá o funcionamento de <b>todas</b> as páginas PHP. Nestes casos, não poderá ser utilizado o ficheiro <b>.htaccess</b>.</p></li></ol>';
$string['paths'] = 'Caminhos';
$string['pathserrcreatedataroot'] = 'O programa de instalação não conseguiu criar a pasta de dados <b>{$a->dataroot}</b>.';
$string['pathshead'] = 'Confirmar caminhos';
+1
View File
@@ -54,6 +54,7 @@ $string['paths'] = 'Căi';
$string['pathserrcreatedataroot'] = 'Data directory ({$a->dataroot}) nu poate fi creat de către installer.';
$string['pathshead'] = 'Confirmare căi';
$string['pathsrodataroot'] = 'Directorul dataroot nu poate fi scris.';
$string['pathsroparentdataroot'] = 'Directorul parent ({$a->parent}) nu poate fi scris. Directorul data ({$a->dataroot}) nu poate fi creat de persoana care îl instalează.';
$string['pathssubdataroot'] = '<p>Un director unde Moodle va stoca tot conținutul unui fișier încărcat de către utilizatori.</p>
<p>Acest director trebuie să poată fi citit și scris de către utilizatrii serverului web (de obicei \'www-data\', \'nobody\', or \'apache\').</p>
<p>Nu trebuie să fie direct accesibil de pe web.</p>
+1
View File
@@ -39,3 +39,4 @@ $string['reseteveryonesprofile'] = 'Reset profile for all users';
$string['resetpage'] = 'Reset page to default';
$string['reseterror'] = 'There was an error resetting your page';
$string['privacy:metadata:core_my:preference:user_home_page_preference'] = 'The user home page preference configured for the Dashboard page.';
$string['unabletoaccess'] = 'As a security precaution, you may not access another user\'s dashboard';
+43 -6
View File
@@ -330,6 +330,7 @@ function get_role_definitions_uncached(array $roleids) {
$sql = "SELECT ctx.path, rc.roleid, rc.capability, rc.permission
FROM {role_capabilities} rc
JOIN {context} ctx ON rc.contextid = ctx.id
JOIN {capabilities} cap ON rc.capability = cap.name
WHERE rc.roleid $sql
ORDER BY ctx.path, rc.roleid, rc.capability";
$rs = $DB->get_recordset_sql($sql, $params);
@@ -1144,7 +1145,17 @@ function is_safe_capability($capability) {
*/
function get_local_override($roleid, $contextid, $capability) {
global $DB;
return $DB->get_record('role_capabilities', array('roleid'=>$roleid, 'capability'=>$capability, 'contextid'=>$contextid));
return $DB->get_record_sql("
SELECT rc.*
FROM {role_capabilities} rc
JOIN {capability} cap ON rc.capability = cap.name
WHERE rc.roleid = :roleid AND rc.capability = :capability AND rc.contextid = :contextid", [
'roleid' => $roleid,
'contextid' => $contextid,
'capability' => $capability,
]);
}
/**
@@ -1290,6 +1301,11 @@ function assign_capability($capability, $permission, $roleid, $contextid, $overw
$context = context::instance_by_id($contextid);
}
// Capability must exist.
if (!$capinfo = get_capability_info($capability)) {
throw new coding_exception("Capability '{$capability}' was not found! This has to be fixed in code.");
}
if (empty($permission) || $permission == CAP_INHERIT) { // if permission is not set
unassign_capability($capability, $roleid, $context->id);
return true;
@@ -1337,6 +1353,11 @@ function assign_capability($capability, $permission, $roleid, $contextid, $overw
function unassign_capability($capability, $roleid, $contextid = null) {
global $DB;
// Capability must exist.
if (!$capinfo = get_capability_info($capability)) {
throw new coding_exception("Capability '{$capability}' was not found! This has to be fixed in code.");
}
if (!empty($contextid)) {
if ($contextid instanceof context) {
$context = $contextid;
@@ -1391,6 +1412,7 @@ function get_roles_with_capability($capability, $permission = null, $context = n
FROM {role} r
WHERE r.id IN (SELECT rc.roleid
FROM {role_capabilities} rc
JOIN {capabilities} cap ON rc.capability = cap.name
WHERE rc.capability = :capname
$contextsql
$permissionsql)";
@@ -2177,6 +2199,9 @@ function update_capabilities($component = 'moodle') {
$DB->insert_record('capabilities', $capability, false);
// Flush the cached, as we have changed DB.
cache::make('core', 'capabilities')->delete('core_capabilities');
if (isset($capdef['clonepermissionsfrom']) && in_array($capdef['clonepermissionsfrom'], $existingcaps)){
if ($rolecapabilities = $DB->get_records('role_capabilities', array('capability'=>$capdef['clonepermissionsfrom']))){
foreach ($rolecapabilities as $rolecapability){
@@ -2230,9 +2255,6 @@ function capabilities_cleanup($component, $newcapdef = null) {
if (empty($newcapdef) ||
array_key_exists($cachedcap->name, $newcapdef) === false) {
// Remove from capabilities cache.
$DB->delete_records('capabilities', array('name'=>$cachedcap->name));
$removedcount++;
// Delete from roles.
if ($roles = get_roles_with_capability($cachedcap->name)) {
foreach($roles as $role) {
@@ -2241,6 +2263,13 @@ function capabilities_cleanup($component, $newcapdef = null) {
}
}
}
// Remove from role_capabilities for any old ones.
$DB->delete_records('role_capabilities', array('capability' => $cachedcap->name));
// Remove from capabilities cache.
$DB->delete_records('capabilities', array('name' => $cachedcap->name));
$removedcount++;
} // End if.
}
}
@@ -2305,10 +2334,12 @@ function role_context_capabilities($roleid, context $context, $cap = '') {
}
$sql = "SELECT rc.*
FROM {role_capabilities} rc, {context} c
FROM {role_capabilities} rc
JOIN {context} c ON rc.contextid = c.id
JOIN {capabilities} cap ON rc.capability = cap.name
WHERE rc.contextid in $contexts
AND rc.roleid = ?
AND rc.contextid = c.id $search
$search
ORDER BY c.contextlevel DESC, rc.capability DESC";
$capabilities = array();
@@ -3024,6 +3055,7 @@ function get_switchable_roles(context $context) {
SELECT r.id, r.name, r.shortname, rn.name AS coursealias
FROM (SELECT DISTINCT rc.roleid
FROM {role_capabilities} rc
$extrajoins
$extrawhere) idlist
JOIN {role} r ON r.id = idlist.roleid
@@ -3286,6 +3318,7 @@ function get_users_by_capability(context $context, $capability, $fields = '', $s
$params = array_merge($params, $params2);
$sql = "SELECT rc.id, rc.roleid, rc.permission, rc.capability, ctx.path
FROM {role_capabilities} rc
JOIN {capabilities} cap ON rc.capability = cap.name
JOIN {context} ctx on rc.contextid = ctx.id
WHERE rc.contextid $incontexts AND rc.capability $incaps";
@@ -4381,6 +4414,7 @@ function get_roles_with_cap_in_context($context, $capability) {
$sql = "SELECT rc.id, rc.roleid, rc.permission, ctx.depth
FROM {role_capabilities} rc
JOIN {context} ctx ON ctx.id = rc.contextid
JOIN {capabilities} cap ON rc.capability = cap.name
WHERE rc.capability = :cap AND ctx.id IN ($ctxids)
ORDER BY rc.roleid ASC, ctx.depth DESC";
$params = array('cap'=>$capability);
@@ -4500,6 +4534,7 @@ function prohibit_is_removable($roleid, context $context, $capability) {
$sql = "SELECT ctx.id
FROM {role_capabilities} rc
JOIN {context} ctx ON ctx.id = rc.contextid
JOIN {capabilities} cap ON rc.capability = cap.name
WHERE rc.roleid = :roleid AND rc.permission = :prohibit AND rc.capability = :cap AND ctx.id IN ($ctxids)
ORDER BY ctx.depth DESC";
@@ -4543,6 +4578,7 @@ function role_change_permission($roleid, $context, $capname, $permission) {
$sql = "SELECT ctx.id, rc.permission, ctx.depth
FROM {role_capabilities} rc
JOIN {context} ctx ON ctx.id = rc.contextid
JOIN {capabilities} cap ON rc.capability = cap.name
WHERE rc.roleid = :roleid AND rc.capability = :cap AND ctx.id IN ($ctxids)
ORDER BY ctx.depth DESC";
@@ -7224,6 +7260,7 @@ function get_with_capability_join(context $context, $capability, $useridcolumn)
$defs = array();
$sql = "SELECT rc.id, rc.roleid, rc.permission, ctx.path
FROM {role_capabilities} rc
JOIN {capabilities} cap ON rc.capability = cap.name
JOIN {context} ctx on rc.contextid = ctx.id
WHERE rc.contextid $incontexts AND rc.capability $incaps";
$rcs = $DB->get_records_sql($sql, array_merge($cparams, $capsparams));
+1 -1
View File
@@ -1 +1 @@
define(["core/config"],function(a){var b=function(b){this.storage=b,this.supported=this.detectSupport(),this.hashSource=a.wwwroot+"/"+a.jsrev,this.hash=this.hashString(this.hashSource),this.prefix=this.hash+"/",this.jsrevPrefix=this.hash+"/jsrev"};return b.prototype.detectSupport=function(){if(a.jsrev==-1)return!1;if("undefined"==typeof this.storage)return!1;var b="test";try{return null!==this.storage&&(this.storage.setItem(b,"1"),this.storage.removeItem(b),!0)}catch(c){return!1}},b.prototype.prefixKey=function(a){return this.prefix+a},b.prototype.validateCache=function(){var b=this.storage.getItem(this.jsrevPrefix);if(null===b)return void this.storage.setItem(this.jsrevPrefix,a.jsrev);var c=a.jsrev;c!=b&&(this.storage.clear(),this.storage.setItem(this.jsrevPrefix,a.jsrev))},b.prototype.hashString=function(a){var b,c,d,e=0;if(0===a.length)return e;for(b=0,d=a.length;b<d;b++)c=a.charCodeAt(b),e=(e<<5)-e+c,e|=0;return e},b.prototype.get=function(a){return!!this.supported&&(this.validateCache(),a=this.prefixKey(a),this.storage.getItem(a))},b.prototype.set=function(a,b){if(!this.supported)return!1;this.validateCache(),a=this.prefixKey(a);try{this.storage.setItem(a,b)}catch(c){return!1}return!0},b});
define(["core/config"],function(a){var b=function(b){this.storage=b,this.supported=this.detectSupport(),this.hashSource=a.wwwroot+"/"+a.jsrev,this.hash=this.hashString(this.hashSource),this.prefix=this.hash+"/",this.jsrevPrefix=this.hashString(a.wwwroot)+"/jsrev",this.validateCache()};return b.prototype.detectSupport=function(){if(a.jsrev==-1)return!1;if("undefined"==typeof this.storage)return!1;var b="test";try{return null!==this.storage&&(this.storage.setItem(b,"1"),this.storage.removeItem(b),!0)}catch(c){return!1}},b.prototype.prefixKey=function(a){return this.prefix+a},b.prototype.validateCache=function(){var b=this.storage.getItem(this.jsrevPrefix);if(null===b)return void this.storage.setItem(this.jsrevPrefix,a.jsrev);var c=a.jsrev;c!=b&&(this.storage.clear(),this.storage.setItem(this.jsrevPrefix,a.jsrev))},b.prototype.hashString=function(a){var b,c,d,e=0;if(0===a.length)return e;for(b=0,d=a.length;b<d;b++)c=a.charCodeAt(b),e=(e<<5)-e+c,e|=0;return e},b.prototype.get=function(a){return!!this.supported&&(a=this.prefixKey(a),this.storage.getItem(a))},b.prototype.set=function(a,b){if(!this.supported)return!1;a=this.prefixKey(a);try{this.storage.setItem(a,b)}catch(c){return!1}return!0},b});
+3 -4
View File
@@ -35,7 +35,8 @@ define(['core/config'], function(config) {
this.hashSource = config.wwwroot + '/' + config.jsrev;
this.hash = this.hashString(this.hashSource);
this.prefix = this.hash + '/';
this.jsrevPrefix = this.hash + '/jsrev';
this.jsrevPrefix = this.hashString(config.wwwroot) + '/jsrev';
this.validateCache();
};
/**
@@ -89,8 +90,8 @@ define(['core/config'], function(config) {
this.storage.setItem(this.jsrevPrefix, config.jsrev);
return;
}
var moodleVersion = config.jsrev;
var moodleVersion = config.jsrev;
if (moodleVersion != cacheVersion) {
this.storage.clear();
this.storage.setItem(this.jsrevPrefix, config.jsrev);
@@ -132,7 +133,6 @@ define(['core/config'], function(config) {
if (!this.supported) {
return false;
}
this.validateCache();
key = this.prefixKey(key);
return this.storage.getItem(key);
@@ -150,7 +150,6 @@ define(['core/config'], function(config) {
if (!this.supported) {
return false;
}
this.validateCache();
key = this.prefixKey(key);
// This can throw exceptions when the storage limit is reached.
try {
@@ -26,6 +26,8 @@ namespace core\message\inbound;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/filelib.php');
/**
* A Handler to store attachments sent in e-mails as private files.
*
@@ -98,15 +100,17 @@ class private_files_handler extends handler {
$uploadedfiles = array();
$failedfiles = array();
$usedspace = file_get_user_used_space();
$fs = get_file_storage();
foreach ($data->attachments as $attachmenttype => $attachments) {
foreach ($attachments as $attachment) {
mtrace("--- Processing attachment '{$attachment->filename}'");
if (file_is_draft_area_limit_reached($itemid, $maxbytes, $attachment->filesize)) {
if ($maxbytes != USER_CAN_IGNORE_FILE_SIZE_LIMITS &&
($attachment->filesize + $usedspace) > $maxbytes) {
// The user quota will be exceeded if this file is included.
$skippedfiles[] = $attachment;
mtrace("---- Skipping attacment. User will be over quota.");
mtrace("---- Skipping attachment. User will be over quota.");
continue;
}
@@ -132,8 +136,9 @@ class private_files_handler extends handler {
// File created successfully.
mtrace("---- File uploaded successfully as {$record->filename}.");
$uploadedfiles[] = $attachment;
$usedspace += $attachment->filesize;
} else {
mtrace("---- Skipping attacment. Unknown failure during creation.");
mtrace("---- Skipping attachment. Unknown failure during creation.");
$failedfiles[] = $attachment;
}
}
+14 -7
View File
@@ -118,23 +118,30 @@ class provider implements
$user = $contextlist->get_user();
// Firstly export all autosave records from all contexts in the list owned by the given user.
list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
$contextparams['userid'] = $user->id;
$sql = "SELECT *
FROM {editor_atto_autosave}
WHERE userid = :userid AND contextid {$contextsql}";
list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
$contextparams['userid'] = $contextlist->get_user()->id;
$autosaves = $DB->get_recordset_sql($sql, $contextparams);
self::export_autosaves($user, $autosaves);
$sql = "SELECT *
FROM {editor_atto_autosave}
JOIN {context} c ON c.id = eas.contextid
WHERE c.id {$contextsql} AND contextlevel = :contextuser AND c.instanceid = :userid";
// Additionally export all eventual records in the given user's context regardless the actual owner.
// We still consider them to be the user's personal data even when edited by someone else.
list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
$contextparams['userid'] = $contextlist->get_user()->id;
$contextparams['userid'] = $user->id;
$contextparams['contextuser'] = CONTEXT_USER;
$sql = "SELECT eas.*
FROM {editor_atto_autosave} eas
JOIN {context} c ON c.id = eas.contextid
WHERE c.id {$contextsql} AND c.contextlevel = :contextuser AND c.instanceid = :userid";
$autosaves = $DB->get_recordset_sql($sql, $contextparams);
self::export_autosaves($user, $autosaves);
}
+6 -4
View File
@@ -6,10 +6,12 @@ Some changes from the upstream version have been made:
* Added context type property for Context class
* Set context type if 'context_type' parameter was submitted through POST
* Do not require tool_consumer_instance_guid
These changes can be reverted once the following pull requests/issues have been integrated upstream:
* https://github.com/IMSGlobal/LTI-Tool-Provider-Library-PHP/pull/10/commits/a9a1641f1a593eba4638133245c21d9ad47d8680
* https://github.com/IMSGlobal/LTI-Tool-Provider-Library-PHP/pull/11/commits/0bae60389bd020a02be5554516b86336e651e237
* https://github.com/IMSGlobal/LTI-Tool-Provider-Library-PHP/issues/19
* Prevent modification of the request to the provider
These changes can be reverted once the following pull requests have been integrated upstream:
* https://github.com/IMSGlobal/LTI-Tool-Provider-Library-PHP/pull/10
* https://github.com/IMSGlobal/LTI-Tool-Provider-Library-PHP/pull/11
* https://github.com/IMSGlobal/LTI-Tool-Provider-Library-PHP/pull/47
* https://github.com/IMSGlobal/LTI-Tool-Provider-Library-PHP/pull/48
It is recommended by upstream to install depdencies via composer - but the composer installation is bundled
with an autoloader so it's better to do it manually.
+4 -12
View File
@@ -17,7 +17,6 @@ class OAuthRequest {
// for debug purposes
public $base_string;
public static $version = '1.0';
public static $POST_INPUT = 'php://input';
function __construct($http_method, $http_url, $parameters = null) {
@@ -60,22 +59,15 @@ class OAuthRequest {
$parameters = array();
}
// It's a POST request of the proper content-type, so parse POST
// parameters and add those overriding any duplicates from GET
if ($http_method == "POST"
&& isset($request_headers['Content-Type'])
&& strstr($request_headers['Content-Type'], 'application/x-www-form-urlencoded')) {
$post_data = OAuthUtil::parse_parameters(file_get_contents(self::$POST_INPUT));
$parameters = array_merge($parameters, $post_data);
}
// We have a Authorization-header with OAuth data. Parse the header
// and add those overriding any duplicates from GET or POST
// We have a Authorization-header with OAuth data. Parse the header and add those.
if (isset($request_headers['Authorization']) && substr($request_headers['Authorization'], 0, 6) == 'OAuth ') {
$header_parameters = OAuthUtil::split_header($request_headers['Authorization']);
$parameters = array_merge($parameters, $header_parameters);
}
// If there are parameters in $_POST, these are likely what will be used. Therefore, they should be considered
// the final value in the case of any duplicates from sources parsed above.
$parameters = array_merge($parameters, $_POST);
}
return new OAuthRequest($http_method, $http_url, $parameters);
+99 -4
View File
@@ -1696,6 +1696,101 @@ class core_accesslib_testcase extends advanced_testcase {
$this->assertFalse(has_all_capabilities($sca, $coursecontext, 0));
}
/**
* Test that assigning a fake cap does not return.
*/
public function test_fake_capability() {
global $DB;
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$coursecontext = context_course::instance($course->id);
$teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'), '*', MUST_EXIST);
$teacher = $this->getDataGenerator()->create_user();
$fakecapname = 'moodle/fake:capability';
role_assign($teacherrole->id, $teacher->id, $coursecontext);
$admin = $DB->get_record('user', array('username' => 'admin'));
// Test a capability which does not exist.
// Note: Do not use assign_capability because it will not allow fake caps.
$DB->insert_record('role_capabilities', (object) [
'contextid' => $coursecontext->id,
'roleid' => $teacherrole->id,
'capability' => $fakecapname,
'permission' => CAP_ALLOW,
'timemodified' => time(),
'modifierid' => 0,
]);
// Check `has_capability`.
$this->assertFalse(has_capability($fakecapname, $coursecontext, $teacher));
$this->assertDebuggingCalled("Capability \"{$fakecapname}\" was not found! This has to be fixed in code.");
$this->assertFalse(has_capability($fakecapname, $coursecontext, $admin));
$this->assertDebuggingCalled("Capability \"{$fakecapname}\" was not found! This has to be fixed in code.");
// Check `get_with_capability_sql` (with uses `get_with_capability_join`).
list($sql, $params) = get_with_capability_sql($coursecontext, $fakecapname);
$users = $DB->get_records_sql($sql, $params);
$this->assertFalse(array_key_exists($teacher->id, $users));
$this->assertFalse(array_key_exists($admin->id, $users));
// Check `get_users_by_capability`.
$users = get_users_by_capability($coursecontext, $fakecapname);
$this->assertFalse(array_key_exists($teacher->id, $users));
$this->assertFalse(array_key_exists($admin->id, $users));
}
/**
* Test that assigning a fake cap does not return.
*/
public function test_fake_capability_assign() {
global $DB;
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$coursecontext = context_course::instance($course->id);
$teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'), '*', MUST_EXIST);
$teacher = $this->getDataGenerator()->create_user();
$capability = 'moodle/fake:capability';
role_assign($teacherrole->id, $teacher->id, $coursecontext);
$admin = $DB->get_record('user', array('username' => 'admin'));
$this->expectException('coding_exception');
$this->expectExceptionMessage("Capability '{$capability}' was not found! This has to be fixed in code.");
assign_capability($capability, CAP_ALLOW, $teacherrole->id, $coursecontext);
}
/**
* Test that assigning a fake cap does not return.
*/
public function test_fake_capability_unassign() {
global $DB;
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$coursecontext = context_course::instance($course->id);
$teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'), '*', MUST_EXIST);
$teacher = $this->getDataGenerator()->create_user();
$capability = 'moodle/fake:capability';
role_assign($teacherrole->id, $teacher->id, $coursecontext);
$admin = $DB->get_record('user', array('username' => 'admin'));
$this->expectException('coding_exception');
$this->expectExceptionMessage("Capability '{$capability}' was not found! This has to be fixed in code.");
unassign_capability($capability, CAP_ALLOW, $teacherrole->id, $coursecontext);
}
/**
* Test that the caching in get_role_definitions() and get_role_definitions_uncached()
* works as intended.
@@ -2815,7 +2910,7 @@ class core_accesslib_testcase extends advanced_testcase {
assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $CFG->defaultfrontpageroleid, $frontpagepagecontext, true);
assign_capability('mod/page:view', CAP_PREVENT, $allroles['guest'], $frontpagepagecontext, true);
assign_capability('mod/page:view', CAP_ALLOW, $allroles['user'], $frontpagepagecontext, true);
assign_capability('moodle/page:view', CAP_ALLOW, $allroles['student'], $frontpagepagecontext, true);
assign_capability('mod/page:view', CAP_ALLOW, $allroles['student'], $frontpagepagecontext, true);
assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $CFG->defaultuserroleid, $frontpagecontext, true);
assign_capability('moodle/site:accessallgroups', CAP_ALLOW, $CFG->defaultfrontpageroleid, $frontpagecontext, true);
@@ -3443,10 +3538,10 @@ class core_accesslib_testcase extends advanced_testcase {
$this->assertFalse(array_key_exists($guest->id, $users));
// Test role override.
assign_capability('moodle/site:backupcourse', CAP_PROHIBIT, $teacherrole->id, $coursecontext, true);
assign_capability('moodle/site:backupcourse', CAP_ALLOW, $studentrole->id, $coursecontext, true);
assign_capability('moodle/backup:backupcourse', CAP_PROHIBIT, $teacherrole->id, $coursecontext, true);
assign_capability('moodle/backup:backupcourse', CAP_ALLOW, $studentrole->id, $coursecontext, true);
list($sql, $params) = get_with_capability_sql($coursecontext, 'moodle/site:backupcourse');
list($sql, $params) = get_with_capability_sql($coursecontext, 'moodle/backup:backupcourse');
$users = $DB->get_records_sql($sql, $params);
$this->assertFalse(array_key_exists($teacher->id, $users));
+7
View File
@@ -1,6 +1,13 @@
This files describes API changes in core libraries and APIs,
information provided here is intended especially for developers.
=== 3.4.6 ===
* The \core_rating provider's get_sql_join function now accepts an optional $innerjoin parameter.
It is recommended that privacy providers using this function call rewrite any long query into a number of separate
calls to add_from_sql for improved performance, and that the new argument is used.
This will allow queries to remain backwards-compatible with older versions of Moodle but will have significantly better performance in version supporting the innerjoin parameter.
=== 3.4.5 ===
* Custom AJAX handlers for the form autocomplete fields can now optionally return string in their processResults()
+44 -16
View File
@@ -103,29 +103,57 @@ class provider implements
* @return contextlist the list of contexts containing user info for the user.
*/
public static function get_contexts_for_userid(int $userid) : contextlist {
// Fetch all data records.
$contextlist = new contextlist();
// Fetch all data records that the user rote.
$sql = "SELECT c.id
FROM {context} c
INNER JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel
INNER JOIN {modules} m ON m.id = cm.module AND m.name = :modname
INNER JOIN {data} d ON d.id = cm.instance
INNER JOIN {data_records} dr ON dr.dataid = d.id
LEFT JOIN {comments} com ON com.commentarea=:commentarea and com.itemid = dr.id AND com.userid = :userid1
LEFT JOIN {rating} r ON r.contextid = c.id AND r.itemid = dr.id AND r.component = :moddata
AND r.ratingarea = :ratingarea AND r.userid = :userid2
WHERE dr.userid = :userid OR com.id IS NOT NULL OR r.id IS NOT NULL";
JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel
JOIN {modules} m ON m.id = cm.module AND m.name = :modname
JOIN {data} d ON d.id = cm.instance
JOIN {data_records} dr ON dr.dataid = d.id
WHERE dr.userid = :userid";
$params = [
'modname' => 'data',
'contextlevel' => CONTEXT_MODULE,
'modname' => 'data',
'userid' => $userid,
'userid1' => $userid,
'userid2' => $userid,
'commentarea' => 'database_entry',
'moddata' => 'mod_data',
'ratingarea' => 'entry',
];
$contextlist = new contextlist();
$contextlist->add_from_sql($sql, $params);
// Fetch contexts where the user commented.
$sql = "SELECT c.id
FROM {context} c
JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel
JOIN {modules} m ON m.id = cm.module AND m.name = :modname
JOIN {data} d ON d.id = cm.instance
JOIN {data_records} dr ON dr.dataid = d.id
JOIN {comments} com ON com.commentarea = :commentarea and com.itemid = dr.id
WHERE com.userid = :userid";
$params = [
'contextlevel' => CONTEXT_MODULE,
'modname' => 'data',
'commentarea' => 'database_entry',
'userid' => $userid,
];
$contextlist->add_from_sql($sql, $params);
// Fetch all data records.
$ratingquery = \core_rating\privacy\provider::get_sql_join('r', 'mod_data', 'entry', 'dr.id', $userid, true);
$sql = "SELECT c.id
FROM {context} c
JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel
JOIN {modules} m ON m.id = cm.module AND m.name = :modname
JOIN {data} d ON d.id = cm.instance
JOIN {data_records} dr ON dr.dataid = d.id
{$ratingquery->join}
WHERE {$ratingquery->userwhere}";
$params = [
'contextlevel' => CONTEXT_MODULE,
'modname' => 'data',
] + $ratingquery->params;
$contextlist->add_from_sql($sql, $params);
return $contextlist;
+1 -1
View File
@@ -274,7 +274,7 @@ class mod_data_external_testcase extends externallib_advanced_testcase {
public function test_view_database_no_capabilities() {
// Test user with no capabilities.
// We need a explicit prohibit since this capability is allowed for students by default.
assign_capability('mod/data:viewpage', CAP_PROHIBIT, $this->studentrole->id, $this->context->id);
assign_capability('mod/data:view', CAP_PROHIBIT, $this->studentrole->id, $this->context->id);
accesslib_clear_all_caches_for_unit_testing();
$this->expectException('moodle_exception');
+162 -56
View File
@@ -152,46 +152,105 @@ class provider implements
* @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin.
*/
public static function get_contexts_for_userid(int $userid) : \core_privacy\local\request\contextlist {
$ratingsql = \core_rating\privacy\provider::get_sql_join('rat', 'mod_forum', 'post', 'p.id', $userid);
// Fetch all forum discussions, and forum posts.
$contextlist = new \core_privacy\local\request\contextlist();
$params = [
'modname' => 'forum',
'contextlevel' => CONTEXT_MODULE,
'userid' => $userid,
];
// Discussion creators.
$sql = "SELECT c.id
FROM {context} c
JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel
JOIN {modules} m ON m.id = cm.module AND m.name = :modname
JOIN {forum} f ON f.id = cm.instance
LEFT JOIN {forum_discussions} d ON d.forum = f.id
LEFT JOIN {forum_posts} p ON p.discussion = d.id
LEFT JOIN {forum_digests} dig ON dig.forum = f.id AND dig.userid = :digestuserid
LEFT JOIN {forum_subscriptions} sub ON sub.forum = f.id AND sub.userid = :subuserid
LEFT JOIN {forum_track_prefs} pref ON pref.forumid = f.id AND pref.userid = :prefuserid
LEFT JOIN {forum_read} hasread ON hasread.forumid = f.id AND hasread.userid = :hasreaduserid
LEFT JOIN {forum_discussion_subs} dsub ON dsub.forum = f.id AND dsub.userid = :dsubuserid
{$ratingsql->join}
WHERE (
p.userid = :postuserid OR
d.userid = :discussionuserid OR
dig.id IS NOT NULL OR
sub.id IS NOT NULL OR
pref.id IS NOT NULL OR
hasread.id IS NOT NULL OR
dsub.id IS NOT NULL OR
{$ratingsql->userwhere}
)
JOIN {forum_discussions} d ON d.forum = f.id
WHERE d.userid = :userid
";
$params = [
'modname' => 'forum',
'contextlevel' => CONTEXT_MODULE,
'postuserid' => $userid,
'discussionuserid' => $userid,
'digestuserid' => $userid,
'subuserid' => $userid,
'prefuserid' => $userid,
'hasreaduserid' => $userid,
'dsubuserid' => $userid,
];
$params += $ratingsql->params;
$contextlist->add_from_sql($sql, $params);
$contextlist = new \core_privacy\local\request\contextlist();
// Post authors.
$sql = "SELECT c.id
FROM {context} c
JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel
JOIN {modules} m ON m.id = cm.module AND m.name = :modname
JOIN {forum} f ON f.id = cm.instance
JOIN {forum_discussions} d ON d.forum = f.id
JOIN {forum_posts} p ON p.discussion = d.id
WHERE p.userid = :userid
";
$contextlist->add_from_sql($sql, $params);
// Forum digest records.
$sql = "SELECT c.id
FROM {context} c
JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel
JOIN {modules} m ON m.id = cm.module AND m.name = :modname
JOIN {forum} f ON f.id = cm.instance
JOIN {forum_digests} dig ON dig.forum = f.id
WHERE dig.userid = :userid
";
$contextlist->add_from_sql($sql, $params);
// Forum subscriptions.
$sql = "SELECT c.id
FROM {context} c
JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel
JOIN {modules} m ON m.id = cm.module AND m.name = :modname
JOIN {forum} f ON f.id = cm.instance
JOIN {forum_subscriptions} sub ON sub.forum = f.id
WHERE sub.userid = :userid
";
$contextlist->add_from_sql($sql, $params);
// Discussion subscriptions.
$sql = "SELECT c.id
FROM {context} c
JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel
JOIN {modules} m ON m.id = cm.module AND m.name = :modname
JOIN {forum} f ON f.id = cm.instance
JOIN {forum_discussion_subs} dsub ON dsub.forum = f.id
WHERE dsub.userid = :userid
";
$contextlist->add_from_sql($sql, $params);
// Discussion tracking preferences.
$sql = "SELECT c.id
FROM {context} c
JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel
JOIN {modules} m ON m.id = cm.module AND m.name = :modname
JOIN {forum} f ON f.id = cm.instance
JOIN {forum_track_prefs} pref ON pref.forumid = f.id
WHERE pref.userid = :userid
";
$contextlist->add_from_sql($sql, $params);
// Discussion read records.
$sql = "SELECT c.id
FROM {context} c
JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel
JOIN {modules} m ON m.id = cm.module AND m.name = :modname
JOIN {forum} f ON f.id = cm.instance
JOIN {forum_read} hasread ON hasread.forumid = f.id
WHERE hasread.userid = :userid
";
$contextlist->add_from_sql($sql, $params);
// Rating authors.
$ratingsql = \core_rating\privacy\provider::get_sql_join('rat', 'mod_forum', 'post', 'p.id', $userid, true);
$sql = "SELECT c.id
FROM {context} c
JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel
JOIN {modules} m ON m.id = cm.module AND m.name = :modname
JOIN {forum} f ON f.id = cm.instance
JOIN {forum_discussions} d ON d.forum = f.id
JOIN {forum_posts} p ON p.discussion = d.id
{$ratingsql->join}
WHERE {$ratingsql->userwhere}
";
$params += $ratingsql->params;
$contextlist->add_from_sql($sql, $params);
return $contextlist;
@@ -365,30 +424,68 @@ class provider implements
$userid = $user->id;
list($contextsql, $contextparams) = $DB->get_in_or_equal($contextlist->get_contextids(), SQL_PARAMS_NAMED);
$params = $contextparams;
// Digested forums.
$sql = "SELECT
c.id AS contextid,
dig.maildigest AS maildigest
FROM {context} c
JOIN {course_modules} cm ON cm.id = c.instanceid
JOIN {forum} f ON f.id = cm.instance
JOIN {forum_digests} dig ON dig.forum = f.id
WHERE (
dig.userid = :userid AND
c.id {$contextsql}
)
";
$params['userid'] = $userid;
$digests = $DB->get_records_sql_menu($sql, $params);
// Forum subscriptions.
$sql = "SELECT
c.id AS contextid,
sub.userid AS subscribed
FROM {context} c
JOIN {course_modules} cm ON cm.id = c.instanceid
JOIN {forum} f ON f.id = cm.instance
JOIN {forum_subscriptions} sub ON sub.forum = f.id
WHERE (
sub.userid = :userid AND
c.id {$contextsql}
)
";
$params['userid'] = $userid;
$subscriptions = $DB->get_records_sql_menu($sql, $params);
// Tracked forums.
$sql = "SELECT
c.id AS contextid,
f.*,
cm.id AS cmid,
dig.maildigest,
sub.userid AS subscribed,
pref.userid AS tracked
FROM {context} c
JOIN {course_modules} cm ON cm.id = c.instanceid
JOIN {forum} f ON f.id = cm.instance
LEFT JOIN {forum_digests} dig ON dig.forum = f.id AND dig.userid = :digestuserid
LEFT JOIN {forum_subscriptions} sub ON sub.forum = f.id AND sub.userid = :subuserid
LEFT JOIN {forum_track_prefs} pref ON pref.forumid = f.id AND pref.userid = :prefuserid
JOIN {forum_track_prefs} pref ON pref.forumid = f.id
WHERE (
pref.userid = :userid AND
c.id {$contextsql}
)
";
$params['userid'] = $userid;
$tracked = $DB->get_records_sql_menu($sql, $params);
$sql = "SELECT
c.id AS contextid,
f.*,
cm.id AS cmid
FROM {context} c
JOIN {course_modules} cm ON cm.id = c.instanceid
JOIN {forum} f ON f.id = cm.instance
WHERE (
c.id {$contextsql}
)
";
$params = [
'digestuserid' => $userid,
'subuserid' => $userid,
'prefuserid' => $userid,
];
$params += $contextparams;
// Keep a mapping of forumid to contextid.
@@ -407,9 +504,15 @@ class provider implements
request_helper::export_context_files($context, $user);
// Store relevant metadata about this forum instance.
static::export_digest_data($userid, $forum);
static::export_subscription_data($userid, $forum);
static::export_tracking_data($userid, $forum);
if (isset($digests[$forum->contextid])) {
static::export_digest_data($userid, $forum, $digests[$forum->contextid]);
}
if (isset($subscriptions[$forum->contextid])) {
static::export_subscription_data($userid, $forum, $subscriptions[$forum->contextid]);
}
if (isset($tracked[$forum->contextid])) {
static::export_tracking_data($userid, $forum, $tracked[$forum->contextid]);
}
}
$forums->close();
@@ -704,16 +807,17 @@ class provider implements
*
* @param int $userid The userid of the user whose data is to be exported.
* @param \stdClass $forum The forum whose data is being exported.
* @param int $maildigest The mail digest setting for this forum.
* @return bool Whether any data was stored.
*/
protected static function export_digest_data(int $userid, \stdClass $forum) {
if (null !== $forum->maildigest) {
protected static function export_digest_data(int $userid, \stdClass $forum, int $maildigest) {
if (null !== $maildigest) {
// The user has a specific maildigest preference for this forum.
$a = (object) [
'forum' => format_string($forum->name, true),
];
switch ($forum->maildigest) {
switch ($maildigest) {
case 0:
$a->type = get_string('emaildigestoffshort', 'mod_forum');
break;
@@ -726,7 +830,7 @@ class provider implements
}
writer::with_context(\context_module::instance($forum->cmid))
->export_metadata([], 'digestpreference', $forum->maildigest,
->export_metadata([], 'digestpreference', $maildigest,
get_string('privacy:digesttypepreference', 'mod_forum', $a));
return true;
@@ -740,10 +844,11 @@ class provider implements
*
* @param int $userid The userid of the user whose data is to be exported.
* @param \stdClass $forum The forum whose data is being exported.
* @param int $subscribed if the user is subscribed
* @return bool Whether any data was stored.
*/
protected static function export_subscription_data(int $userid, \stdClass $forum) {
if (null !== $forum->subscribed) {
protected static function export_subscription_data(int $userid, \stdClass $forum, int $subscribed) {
if (null !== $subscribed) {
// The user is subscribed to this forum.
writer::with_context(\context_module::instance($forum->cmid))
->export_metadata([], 'subscriptionpreference', 1, get_string('privacy:subscribedtoforum', 'mod_forum'));
@@ -798,10 +903,11 @@ class provider implements
*
* @param int $userid The userid of the user whose data is to be exported.
* @param \stdClass $forum The forum whose data is being exported.
* @param int $tracke if the user is subscribed
* @return bool Whether any data was stored.
*/
protected static function export_tracking_data(int $userid, \stdClass $forum) {
if (null !== $forum->tracked) {
protected static function export_tracking_data(int $userid, \stdClass $forum, int $tracked) {
if (null !== $tracked) {
// The user has a main preference to track all forums, but has opted out of this one.
writer::with_context(\context_module::instance($forum->cmid))
->export_metadata([], 'trackreadpreference', 0, get_string('privacy:readtrackingdisabled', 'mod_forum'));
+2 -5
View File
@@ -3925,7 +3925,7 @@ function forum_print_discussion_header(&$post, $forum, $group = -1, $datestring
}
echo '<td class="lastpost">';
$usedate = (empty($post->created)) ? $post->timemodified : $post->created;
$usedate = (empty($post->timemodified)) ? $post->created : $post->timemodified;
$parenturl = '';
$usermodified = new stdClass();
$usermodified->id = $post->usermodified;
@@ -4558,10 +4558,6 @@ function forum_update_post($newpost, $mform, $unused = null) {
}
$post->modified = time();
// Last post modified tracking.
$discussion->timemodified = $post->modified;
$discussion->usermodified = $post->userid;
if (!$post->parent) { // Post is a discussion starter - update discussion title and times too
$discussion->name = $post->subject;
$discussion->timestart = $post->timestart;
@@ -4574,6 +4570,7 @@ function forum_update_post($newpost, $mform, $unused = null) {
$post->message = file_save_draft_area_files($newpost->itemid, $context->id, 'mod_forum', 'post', $post->id,
mod_forum_post_form::editor_options($context, $post->id), $post->message);
$DB->update_record('forum_posts', $post);
// Note: Discussion modified time/user are intentionally not updated, to enable them to track the latest new post.
$DB->update_record('forum_discussions', $discussion);
forum_add_attachment($post, $forum, $cm, $mform);
+1 -1
View File
@@ -574,7 +574,7 @@ file_prepare_draft_area($draftitemid, $modcontext->id, 'mod_forum', 'attachment'
if ($USER->id != $post->userid) { // Not the original author, so add a message to the end
$data = new stdClass();
$data->date = userdate($post->modified);
$data->date = userdate($post->created);
if ($post->messageformat == FORMAT_HTML) {
$data->name = '<a href="'.$CFG->wwwroot.'/user/view.php?id='.$USER->id.'&course='.$post->course.'">'.
fullname($USER).'</a>';
@@ -71,14 +71,14 @@ Feature: New discussions and discussions with recently added replies are display
And I am on "Course 1" course homepage
And I follow "Course general forum"
#
# Make sure the order of the forum posts is as expected (most recently participated first).
# Make sure the order of the forum posts is as expected, with most recent new participation first (ie excluding edits).
#
Then I should see "Forum post 3" in the "//tr[contains(concat(' ', normalize-space(@class), ' '), ' discussion ')][position()=3]" "xpath_element"
And I should see "Edited forum post 2" in the "//tr[contains(concat(' ', normalize-space(@class), ' '), ' discussion ')][position()=2]" "xpath_element"
And I should see "Forum post 1" in the "//tr[contains(concat(' ', normalize-space(@class), ' '), ' discussion ')][position()=1]" "xpath_element"
Then I should see "Forum post 1" in the "//tr[contains(concat(' ', normalize-space(@class), ' '), ' discussion ')][position()=1]" "xpath_element"
And I should see "Forum post 3" in the "//tr[contains(concat(' ', normalize-space(@class), ' '), ' discussion ')][position()=2]" "xpath_element"
And I should see "Edited forum post 2" in the "//tr[contains(concat(' ', normalize-space(@class), ' '), ' discussion ')][position()=3]" "xpath_element"
#
# Make sure the next/prev navigation uses the same order of the posts.
#
And I follow "Edited forum post 2"
And I follow "Forum post 3"
And "//a[@aria-label='Next discussion: Forum post 1']" "xpath_element" should exist
And "//a[@aria-label='Previous discussion: Forum post 3']" "xpath_element" should exist
And "//a[@aria-label='Previous discussion: Edited forum post 2']" "xpath_element" should exist
+42 -1
View File
@@ -3298,6 +3298,17 @@ class mod_forum_lib_testcase extends advanced_testcase {
// On this freshly created discussion, the teacher is the author of the last post.
$this->assertEquals($teacher->id, $DB->get_field('forum_discussions', 'usermodified', ['id' => $discussion->id]));
// Fetch modified timestamp of the discussion.
$discussionmodified = $DB->get_field('forum_discussions', 'timemodified', ['id' => $discussion->id]);
$pasttime = $discussionmodified - 3600;
// Adjust the discussion modified timestamp back an hour, so it's in the past.
$adjustment = (object)[
'id' => $discussion->id,
'timemodified' => $pasttime,
];
$DB->update_record('forum_discussions', $adjustment);
// Let the student reply to the teacher's post.
$reply = $generator->create_post((object)[
'course' => $course->id,
@@ -3310,6 +3321,30 @@ class mod_forum_lib_testcase extends advanced_testcase {
// The student should now be the last post's author.
$this->assertEquals($student->id, $DB->get_field('forum_discussions', 'usermodified', ['id' => $discussion->id]));
// Fetch modified timestamp of the discussion and student's post.
$discussionmodified = $DB->get_field('forum_discussions', 'timemodified', ['id' => $discussion->id]);
$postmodified = $DB->get_field('forum_posts', 'modified', ['id' => $reply->id]);
// Discussion modified time should be updated to be equal to the newly created post's time.
$this->assertEquals($discussionmodified, $postmodified);
// Adjust the discussion and post timestamps, so they are in the past.
$adjustment = (object)[
'id' => $discussion->id,
'timemodified' => $pasttime,
];
$DB->update_record('forum_discussions', $adjustment);
$adjustment = (object)[
'id' => $reply->id,
'modified' => $pasttime,
];
$DB->update_record('forum_posts', $adjustment);
// The discussion and student's post time should now be an hour in the past.
$this->assertEquals($pasttime, $DB->get_field('forum_discussions', 'timemodified', ['id' => $discussion->id]));
$this->assertEquals($pasttime, $DB->get_field('forum_posts', 'modified', ['id' => $reply->id]));
// Let the teacher edit the student's reply.
$this->setUser($teacher->id);
$newpost = (object)[
@@ -3319,8 +3354,14 @@ class mod_forum_lib_testcase extends advanced_testcase {
];
forum_update_post($newpost, null);
// The student should be still the last post's author.
// The student should still be the last post's author.
$this->assertEquals($student->id, $DB->get_field('forum_discussions', 'usermodified', ['id' => $discussion->id]));
// The discussion modified time should not have changed.
$this->assertEquals($pasttime, $DB->get_field('forum_discussions', 'timemodified', ['id' => $discussion->id]));
// The post time should be updated.
$this->assertGreaterThan($pasttime, $DB->get_field('forum_posts', 'modified', ['id' => $reply->id]));
}
public function test_forum_core_calendar_provide_event_action() {
+40 -11
View File
@@ -80,26 +80,55 @@ class provider implements
* @return contextlist the list of contexts containing user info for the user.
*/
public static function get_contexts_for_userid(int $userid) : contextlist {
$ratingquery = \core_rating\privacy\provider::get_sql_join('r', 'mod_glossary', 'entry', 'ge.id', $userid);
$contextlist = new contextlist();
// Glossary entries.
$sql = "SELECT c.id
FROM {context} c
INNER JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel
INNER JOIN {modules} m ON m.id = cm.module AND m.name = :modname
INNER JOIN {glossary} g ON g.id = cm.instance
INNER JOIN {glossary_entries} ge ON ge.glossaryid = g.id
LEFT JOIN {comments} com ON com.commentarea =:commentarea AND com.itemid = ge.id
{$ratingquery->join}
WHERE ge.userid = :glossaryentryuserid OR com.userid = :commentuserid OR {$ratingquery->userwhere}";
JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel
JOIN {modules} m ON m.id = cm.module AND m.name = :modname
JOIN {glossary} g ON g.id = cm.instance
JOIN {glossary_entries} ge ON ge.glossaryid = g.id
WHERE ge.userid = :glossaryentryuserid";
$params = [
'contextlevel' => CONTEXT_MODULE,
'modname' => 'glossary',
'commentarea' => 'glossary_entry',
'glossaryentryuserid' => $userid,
'commentuserid' => $userid,
] + $ratingquery->params;
];
$contextlist->add_from_sql($sql, $params);
$contextlist = new contextlist();
// Where the user has rated something.
$ratingquery = \core_rating\privacy\provider::get_sql_join('r', 'mod_glossary', 'entry', 'ge.id', $userid, true);
$sql = "SELECT c.id
FROM {context} c
JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel
JOIN {modules} m ON m.id = cm.module AND m.name = :modname
JOIN {glossary} g ON g.id = cm.instance
JOIN {glossary_entries} ge ON ge.glossaryid = g.id
{$ratingquery->join}
WHERE {$ratingquery->userwhere}";
$params = [
'contextlevel' => CONTEXT_MODULE,
'modname' => 'glossary',
] + $ratingquery->params;
$contextlist->add_from_sql($sql, $params);
// Comments.
$sql = "SELECT c.id
FROM {context} c
JOIN {course_modules} cm ON cm.id = c.instanceid AND c.contextlevel = :contextlevel
JOIN {modules} m ON m.id = cm.module AND m.name = :modname
JOIN {glossary} g ON g.id = cm.instance
JOIN {glossary_entries} ge ON ge.glossaryid = g.id
JOIN {comments} com ON com.commentarea =:commentarea AND com.itemid = ge.id
WHERE com.userid = :commentuserid";
$params = [
'contextlevel' => CONTEXT_MODULE,
'modname' => 'glossary',
'commentarea' => 'glossary_entry',
'commentuserid' => $userid,
];
$contextlist->add_from_sql($sql, $params);
return $contextlist;
+7
View File
@@ -45,6 +45,13 @@ $reset = optional_param('reset', null, PARAM_BOOL);
require_login();
if (\core\session\manager::is_loggedinas()) {
// Disable access to the user's dashboard for "logged in as" sessions
// to mitigate risks associated with loading other users' JavaScript.
// See MDL-63786 for more information.
redirect(new moodle_url('/', ['redirect' => 0]), get_string('unabletoaccess', 'core_my'));
}
$hassiteconfig = has_capability('moodle/site:config', context_system::instance());
if ($hassiteconfig && moodle_needs_upgrading()) {
redirect(new moodle_url('/admin/index.php'));
@@ -0,0 +1,69 @@
<?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/>.
/**
* A scheduled task to remove unneeded random questions.
*
* @package qtype_random
* @category task
* @copyright 2018 Bo Pierce <email.bO.pierce@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace qtype_random\task;
defined('MOODLE_INTERNAL') || die();
/**
* A scheduled task to remove unneeded random questions.
*
* @copyright 2018 Bo Pierce <email.bO.pierce@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class remove_unused_questions extends \core\task\scheduled_task {
public function get_name() {
return get_string('taskunusedrandomscleanup', 'qtype_random');
}
public function execute() {
global $DB, $CFG;
require_once($CFG->libdir . '/questionlib.php');
// Find potentially unused random questions (up to 10000).
// Note, because we call question_delete_question below,
// the question will not actually be deleted if something else
// is using them, but nothing else in Moodle core uses qtype_random,
// and not many third-party plugins do.
$unusedrandomids = $DB->get_records_sql("
SELECT q.id, 1
FROM {question} q
LEFT JOIN {quiz_slots} qslots ON q.id = qslots.questionid
WHERE qslots.questionid IS NULL
AND q.qtype = ? AND hidden = ?", ['random', 0], 0, 10000);
$count = 0;
foreach ($unusedrandomids as $unusedrandomid => $notused) {
question_delete_question($unusedrandomid);
// In case the question was not actually deleted (because it was in use somehow
// mark it as hidden so the query above will not return it again.
$DB->set_field('question', 'hidden', 1, ['id' => $unusedrandomid]);
$count += 1;
}
mtrace('Cleaned up ' . $count . ' unused random questions.');
}
}
+38
View File
@@ -0,0 +1,38 @@
<?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/>.
/**
* Definition of question/type/random scheduled tasks.
*
* @package qtype_random
* @category task
* @copyright 2018 Bo Pierce <email.bO.pierce@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$tasks = array(
array(
'classname' => 'qtype_random\task\remove_unused_questions',
'blocking' => 0,
'minute' => 'R',
'hour' => '*',
'day' => '*',
'month' => '*',
'dayofweek' => '*'
)
);
@@ -33,3 +33,4 @@ $string['randomqname'] = 'Random ({$a})';
$string['randomqplusname'] = 'Random ({$a} and subcategories)';
$string['selectedby'] = '{$a->questionname} selected by {$a->randomname}';
$string['selectmanualquestions'] = 'Random questions can use manually graded questions';
$string['taskunusedrandomscleanup'] = 'Remove unused random questions';
@@ -0,0 +1,70 @@
<?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/>.
/**
* Tests of the scheduled task for cleaning up random questions.
*
* @package qtype_random
* @copyright 2018 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/mod/quiz/locallib.php');
/**
* Tests of the scheduled task for cleaning up random questions.
*
* @copyright 2018 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class qtype_random_cleanup_task_testcase extends advanced_testcase {
public function test_cleanup_task_removes_unused_question() {
global $DB;
$this->resetAfterTest();
$this->setAdminUser();
$generator = $this->getDataGenerator();
$questiongenerator = $generator->get_plugin_generator('core_question');
$quizgenerator = $generator->get_plugin_generator('mod_quiz');
$cat = $questiongenerator->create_question_category();
$quiz = $quizgenerator->create_instance(['course' => SITEID]);
// Add two random questions.
quiz_add_random_questions($quiz, 0, $cat->id, 2, false);
$quizslots = $DB->get_records('quiz_slots', ['quizid' => $quiz->id],
'slot', 'slot, id, questionid');
// Now remove the second from the quiz. (Do it manually,
// because the API cleans up the random question, but we are trying to
// create an orphaned random question.)
$DB->delete_records('quiz_slots', array('id' => $quizslots[2]->id));
// Run the scheduled task.
$task = new \qtype_random\task\remove_unused_questions();
$this->expectOutputString("Cleaned up 1 unused random questions.\n");
$task->execute();
// Verify.
$this->assertTrue($DB->record_exists('question', ['id' => $quizslots[1]->questionid]));
$this->assertFalse($DB->record_exists('question', ['id' => $quizslots[2]->questionid]));
}
}
+1 -1
View File
@@ -26,7 +26,7 @@
defined('MOODLE_INTERNAL') || die();
$plugin->component = 'qtype_random';
$plugin->version = 2017111300;
$plugin->version = 2017111301;
$plugin->requires = 2017110800;
+24 -9
View File
@@ -129,26 +129,41 @@ class provider implements
/**
* Get the SQL required to find all submission items where this user has had any involvements.
*
* If possible an inner join should be used.
*
* @param string $alias The name of the table alias to use.
* @param string $component The na eof the component to fetch ratings for.
* @param string $ratingarea The rating area to fetch results for.
* @param string $itemidjoin The right-hand-side of the JOIN ON clause.
* @param int $userid The ID of the user being stored.
* @param bool $innerjoin Whether to use an inner join (preferred)
* @return \stdClass
*/
public static function get_sql_join($alias, $component, $ratingarea, $itemidjoin, $userid) {
public static function get_sql_join($alias, $component, $ratingarea, $itemidjoin, $userid, $innerjoin = false) {
static $count = 0;
$count++;
// Join the rating table with the specified alias and the relevant join params.
$join = "LEFT JOIN {rating} {$alias} ON ";
$join .= "{$alias}.userid = :ratinguserid{$count} AND ";
$join .= "{$alias}.component = :ratingcomponent{$count} AND ";
$join .= "{$alias}.ratingarea = :ratingarea{$count} AND ";
$join .= "{$alias}.itemid = {$itemidjoin}";
$userwhere = '';
// Match against the specified user.
$userwhere = "{$alias}.id IS NOT NULL";
if ($innerjoin) {
// Join the rating table with the specified alias and the relevant join params.
$join = "JOIN {rating} {$alias} ON ";
$join .= "{$alias}.itemid = {$itemidjoin}";
$userwhere .= "{$alias}.userid = :ratinguserid{$count} AND ";
$userwhere .= "{$alias}.component = :ratingcomponent{$count} AND ";
$userwhere .= "{$alias}.ratingarea = :ratingarea{$count}";
} else {
// Join the rating table with the specified alias and the relevant join params.
$join = "LEFT JOIN {rating} {$alias} ON ";
$join .= "{$alias}.userid = :ratinguserid{$count} AND ";
$join .= "{$alias}.component = :ratingcomponent{$count} AND ";
$join .= "{$alias}.ratingarea = :ratingarea{$count} AND ";
$join .= "{$alias}.itemid = {$itemidjoin}";
// Match against the specified user.
$userwhere = "{$alias}.id IS NOT NULL";
}
$params = [
'ratingcomponent' . $count => $component,
+88
View File
@@ -153,6 +153,94 @@ class core_rating_privacy_testcase extends \core_privacy\tests\provider_testcase
$this->assertFalse(isset($courses[$course3->id]));
}
/**
* Ensure that the get_sql_join function returns valid SQL which returns the correct list of rated itemids.
* This makes use of the optional inner join argument.
*/
public function test_get_sql_join_inner() {
global $DB;
$this->resetAfterTest();
$course1 = $this->getDataGenerator()->create_course();
$course2 = $this->getDataGenerator()->create_course();
$course3 = $this->getDataGenerator()->create_course();
$u1 = $this->getDataGenerator()->create_user();
$u2 = $this->getDataGenerator()->create_user();
$u3 = $this->getDataGenerator()->create_user();
// Rate the courses.
$rm = new rating_manager();
$ratingoptions = (object) [
'component' => 'core_course',
'ratingarea' => 'course',
'scaleid' => 100,
];
// Rate all courses as u1, and something else in the same context.
$this->rate_as_user($u1->id, 'core_course', 'course', $course1->id, \context_course::instance($course1->id), 25);
$this->rate_as_user($u1->id, 'core_course', 'course', $course2->id, \context_course::instance($course2->id), 50);
$this->rate_as_user($u1->id, 'core_course', 'course', $course3->id, \context_course::instance($course3->id), 75);
$this->rate_as_user($u1->id, 'core_course', 'files', $course3->id, \context_course::instance($course3->id), 99);
// Rate course2 as u2, and something else in a different context/component..
$this->rate_as_user($u2->id, 'core_course', 'course', $course2->id, \context_course::instance($course2->id), 90);
$this->rate_as_user($u2->id, 'user', 'user', $u3->id, \context_user::instance($u3->id), 10);
// Return any course which the u1 has rated.
// u1 rated all three courses.
$ratingquery = provider::get_sql_join('r', 'core_course', 'course', 'c.id', $u1->id, true);
$sql = "SELECT c.id FROM {course} c {$ratingquery->join} WHERE {$ratingquery->userwhere}";
$courses = $DB->get_records_sql($sql, $ratingquery->params);
$this->assertCount(3, $courses);
$this->assertTrue(isset($courses[$course1->id]));
$this->assertTrue(isset($courses[$course2->id]));
$this->assertTrue(isset($courses[$course3->id]));
// User u1 rated files in course 3 only.
$ratingquery = provider::get_sql_join('r', 'core_course', 'files', 'c.id', $u1->id, true);
$sql = "SELECT c.id FROM {course} c {$ratingquery->join} WHERE {$ratingquery->userwhere}";
$courses = $DB->get_records_sql($sql, $ratingquery->params);
$this->assertCount(1, $courses);
$this->assertFalse(isset($courses[$course1->id]));
$this->assertFalse(isset($courses[$course2->id]));
$this->assertTrue(isset($courses[$course3->id]));
// Return any course which the u2 has rated.
// User u2 rated only course 2.
$ratingquery = provider::get_sql_join('r', 'core_course', 'course', 'c.id', $u2->id, true);
$sql = "SELECT c.id FROM {course} c {$ratingquery->join} WHERE {$ratingquery->userwhere}";
$courses = $DB->get_records_sql($sql, $ratingquery->params);
$this->assertCount(1, $courses);
$this->assertFalse(isset($courses[$course1->id]));
$this->assertTrue(isset($courses[$course2->id]));
$this->assertFalse(isset($courses[$course3->id]));
// User u2 rated u3.
$ratingquery = provider::get_sql_join('r', 'user', 'user', 'u.id', $u2->id, true);
$sql = "SELECT u.id FROM {user} u {$ratingquery->join} WHERE {$ratingquery->userwhere}";
$users = $DB->get_records_sql($sql, $ratingquery->params);
$this->assertCount(1, $users);
$this->assertFalse(isset($users[$u1->id]));
$this->assertFalse(isset($users[$u2->id]));
$this->assertTrue(isset($users[$u3->id]));
// Return any course which the u3 has rated.
// User u3 did not rate anything.
$ratingquery = provider::get_sql_join('r', 'core_course', 'course', 'c.id', $u3->id, true);
$sql = "SELECT c.id FROM {course} c {$ratingquery->join} WHERE {$ratingquery->userwhere}";
$courses = $DB->get_records_sql($sql, $ratingquery->params);
$this->assertCount(0, $courses);
$this->assertFalse(isset($courses[$course1->id]));
$this->assertFalse(isset($courses[$course2->id]));
$this->assertFalse(isset($courses[$course3->id]));
}
/**
* Ensure that export_area_ratings exports all ratings that a user has made, and all ratings for a users own content.
*/
+2 -2
View File
@@ -29,11 +29,11 @@
defined('MOODLE_INTERNAL') || die();
$version = 2017111307.00; // 20171113 = branching date YYYYMMDD - do not modify!
$version = 2017111309.00; // 20171113 = branching date YYYYMMDD - do not modify!
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.
$release = '3.4.7 (Build: 20190114)'; // Human-friendly version name
$release = '3.4.9 (Build: 20190513)'; // Human-friendly version name
$branch = '34'; // This version's branch.
$maturity = MATURITY_STABLE; // This version's maturity level.