Compare commits
312 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6a30269d98 | |||
| e064cef324 | |||
| eca36ffd7c | |||
| 5f115af823 | |||
| 474a338a97 | |||
| 7bbc73ce0c | |||
| cb5145334d | |||
| 5a552ad6f3 | |||
| 5c1115ba0b | |||
| 7ec6835728 | |||
| e567e64d12 | |||
| bade4a18e5 | |||
| 49ab08f771 | |||
| 3923a219bd | |||
| 750ec445bd | |||
| 1dfd080f54 | |||
| 3c239a6a11 | |||
| 4da24e858c | |||
| 4d4a8423b0 | |||
| b937159eed | |||
| 0c4d9eff86 | |||
| 25f23432f3 | |||
| 3a0efca438 | |||
| 1124ceea9a | |||
| e5cddc8c7e | |||
| 1f6ca1e497 | |||
| 332da1d527 | |||
| 645beae25f | |||
| 1831e2b2fc | |||
| ca88210f3f | |||
| 05582654f2 | |||
| c5f693dede | |||
| 7f9d364fd2 | |||
| b1f0f049d0 | |||
| 0ac06b7216 | |||
| 499dfeff2a | |||
| 59d7f7f2cb | |||
| 57e38ad969 | |||
| 54a8fef92f | |||
| cdbe521e62 | |||
| 8571a061e7 | |||
| 19069c7f28 | |||
| 18b42d9835 | |||
| ebd3da32b5 | |||
| b70e27f531 | |||
| bd7217b3d8 | |||
| d99d036b44 | |||
| 858f188c0f | |||
| d31f12bcbd | |||
| b7f8d08706 | |||
| ee4a93267f | |||
| da7d965e01 | |||
| 5e52542b1e | |||
| 767b4df9c3 | |||
| 10539bfad8 | |||
| 69f5ad8f36 | |||
| 8d650c02c4 | |||
| e971f11251 | |||
| 1ca2f014bb | |||
| f3c2624fbc | |||
| 543191e0dc | |||
| ecf1a221dc | |||
| 2a1ef8b3a7 | |||
| 77399dd147 | |||
| 48aad65284 | |||
| a5eac2bf95 | |||
| 15ebf11b5e | |||
| f0c559d33b | |||
| 8da3995f30 | |||
| 903f4721c9 | |||
| 810e115d6f | |||
| 3b7968954c | |||
| 15847cecbf | |||
| 4e41fc8b18 | |||
| c4e65af6f3 | |||
| 5ab6beeb50 | |||
| 684d142401 | |||
| 78d57d0ae7 | |||
| df8633b8d0 | |||
| a22c821033 | |||
| e3bd18d3d7 | |||
| 9856202e71 | |||
| c5892e5e37 | |||
| 9c0aa6e6d0 | |||
| 97a6abc12f | |||
| 7cea622409 | |||
| 085d6e79e1 | |||
| ddc3699107 | |||
| a0d8aefacf | |||
| 3017d2bab8 | |||
| 93c5807711 | |||
| 5bc2cb3c0e | |||
| caca47f50a | |||
| cf24975d2e | |||
| da048e60db | |||
| fc0bb48542 | |||
| 9234d79fd1 | |||
| 7971d7270c | |||
| 5ca6cce026 | |||
| 8b0bf17160 | |||
| e65777fd22 | |||
| f6735cb3e0 | |||
| 3c3a806f6b | |||
| bf52d11f1e | |||
| 1a11bf4706 | |||
| da428650cd | |||
| de186ff27f | |||
| 3aa7b0df78 | |||
| d329ada4b9 | |||
| 77d0435c3e | |||
| d64f8235fe | |||
| 3ebe724736 | |||
| 4f41f8407d | |||
| a95b434f3e | |||
| 827473833d | |||
| 84720307c3 | |||
| d70fb8fefe | |||
| 559b686e5b | |||
| dda635074d | |||
| 0ada61fdcf | |||
| dbc5a45cc6 | |||
| 8356ee9462 | |||
| 710ec0e546 | |||
| 3c3f5965d3 | |||
| 6f2760071c | |||
| 9c3d4683a4 | |||
| dcb7cfd384 | |||
| 0fbabd85c6 | |||
| 96482ded5e | |||
| 1cb4d36126 | |||
| 7ae0f78bf2 | |||
| f214adb270 | |||
| 613e3038f8 | |||
| 7f45491d4d | |||
| 2f39bfc7d8 | |||
| 09979fde33 | |||
| 4fbeed480a | |||
| 39f22c5f7d | |||
| 5a0c850e50 | |||
| a408e168cc | |||
| bb5b12d936 | |||
| 091faeeb09 | |||
| 6c00f683db | |||
| 30f719c44c | |||
| 1893b39bfc | |||
| 102579cf75 | |||
| 38629d7f32 | |||
| df5bca4acc | |||
| 7db7c8e953 | |||
| a77c92bb9c | |||
| 2850311ace | |||
| 8e09ae50b0 | |||
| 6ac4aa49aa | |||
| a639cddc39 | |||
| 4f910adaea | |||
| 9cb87ef480 | |||
| 7cdba7e644 | |||
| 5e0c8cfd32 | |||
| 0566565254 | |||
| 5068a6ccef | |||
| c98cd8ccc8 | |||
| 21010ab799 | |||
| 91c036db6f | |||
| e3d9c87993 | |||
| 02afdd58e8 | |||
| 59d22708dc | |||
| 79bd60bff7 | |||
| 03667bad20 | |||
| d92b3f2abb | |||
| d798b75928 | |||
| 1e364a4f9a | |||
| f3732b7838 | |||
| 5d85b4a272 | |||
| 12bd0d77d1 | |||
| 01f6eaed9b | |||
| 87fa24daaf | |||
| db51a593bb | |||
| 3ba7f31c15 | |||
| 2666fb1069 | |||
| d3ca2c25e5 | |||
| 4b530e5af8 | |||
| e3bf98b8ed | |||
| c316d854e9 | |||
| ea72ffc95c | |||
| b0d81fce7b | |||
| 2fb82c256c | |||
| d62afa7ae7 | |||
| 2bf3ee078b | |||
| b0781430e4 | |||
| 87eb1e7205 | |||
| ff03600ad0 | |||
| 8d4f41b473 | |||
| ee5b530ae6 | |||
| 0853ab5730 | |||
| 23e8249b36 | |||
| 3876b68dd0 | |||
| 4935c347cf | |||
| cd1a80769f | |||
| 0f13016a50 | |||
| 141d8cbe64 | |||
| ec564a7b32 | |||
| 90528c757d | |||
| c76f2e8095 | |||
| 4999558ed4 | |||
| 0101cefdca | |||
| 0a39c68105 | |||
| 5f9afeb734 | |||
| a193f2a1e3 | |||
| 148d163013 | |||
| c8c11974f2 | |||
| ecc9069384 | |||
| 0960745ecb | |||
| c5437625e1 | |||
| 941b5cc782 | |||
| 02bcfb3631 | |||
| a02b319727 | |||
| 328f918372 | |||
| 2f980fec47 | |||
| ad5c8a8954 | |||
| ebe574ff58 | |||
| 1a89704b05 | |||
| f082899085 | |||
| 0fbdc6705a | |||
| f4da3acc61 | |||
| ef24084985 | |||
| 8b44b91c15 | |||
| bfdc0244a1 | |||
| f7ade2220a | |||
| 5aa0106bc1 | |||
| 2b4ff27983 | |||
| 78ade565c6 | |||
| e222b1ed8d | |||
| 9bfa3e8f56 | |||
| 1c97c77cb9 | |||
| 0c73410e6c | |||
| ebe8a6726a | |||
| cb9494ddcc | |||
| 8ac0f287ee | |||
| 6fad97a975 | |||
| 07b75d56ed | |||
| d959768051 | |||
| 62566af752 | |||
| f910e87c12 | |||
| 8960fa5c1f | |||
| 5eb95aca21 | |||
| f417dab602 | |||
| 334e44fb2c | |||
| ccc7e00cc0 | |||
| c4e1a1518c | |||
| 12f7d66baf | |||
| 0d7ff46e11 | |||
| 91a59718cd | |||
| bd5f26b20b | |||
| 4ffedd3797 | |||
| c923621ec0 | |||
| efc45c2976 | |||
| 916c50484c | |||
| 191bcca9e2 | |||
| 4afc6a5711 | |||
| b32637b7e2 | |||
| 6d6322985d | |||
| a84e91d7f3 | |||
| 629741d93f | |||
| 727a0a97fe | |||
| 8340f83ddc | |||
| 8a3a133319 | |||
| e9822e8851 | |||
| 559a99c793 | |||
| bcdb7e3031 | |||
| 7f0d1164f5 | |||
| aa01e0e574 | |||
| 8070c74ebc | |||
| 93ade38caa | |||
| 39ef515a64 | |||
| 65462b439d | |||
| 481b001f25 | |||
| 3098122ac1 | |||
| f7a92e6a98 | |||
| 00906c109e | |||
| 177f92e74b | |||
| ae464fd7b2 | |||
| 909d02196f | |||
| 56d381cba3 | |||
| 49c2095b74 | |||
| 64b39b7f60 | |||
| b0e82e7359 | |||
| 520d73b0d6 | |||
| 56b3bec2ff | |||
| f87569ac6a | |||
| 7804b75021 | |||
| 571d588002 | |||
| c5fb5d69b1 | |||
| 54b4d6726b | |||
| af281ecdd6 | |||
| d92119979c | |||
| d6301a6dd3 | |||
| 2308148e23 | |||
| 41fc9f1937 | |||
| 96c24a1912 | |||
| f14bcafc5a | |||
| fdd756f779 | |||
| b9cd69b372 | |||
| 6d4e4bb8ef | |||
| 79ec71c87b | |||
| 79a6ef4c9b | |||
| 8bb3a1d295 | |||
| 5cad3e5d46 | |||
| e6e99db401 | |||
| 74c783924f | |||
| 9b6a5a9b24 | |||
| c3c8dfeb51 | |||
| 07f8abfb30 |
@@ -203,7 +203,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ["**/amd/src/*.js"],
|
||||
files: ["**/amd/src/*.js", "Gruntfile.js"],
|
||||
// Check AMD with some slightly stricter rules.
|
||||
rules: {
|
||||
'no-unused-vars': 'error',
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
**/amd/**
|
||||
/*.js
|
||||
+6
-7
@@ -11,7 +11,10 @@ notifications:
|
||||
|
||||
language: php
|
||||
|
||||
dist: trusty
|
||||
dist: xenial
|
||||
|
||||
services:
|
||||
- mysql
|
||||
|
||||
php:
|
||||
# We only run the highest and lowest supported versions to reduce the load on travis-ci.org.
|
||||
@@ -20,10 +23,6 @@ php:
|
||||
|
||||
addons:
|
||||
postgresql: "9.6"
|
||||
packages:
|
||||
- mysql-server-5.6
|
||||
- mysql-client-core-5.6
|
||||
- mysql-client-5.6
|
||||
|
||||
env:
|
||||
# Although we want to run these jobs and see failures as quickly as possible, we also want to get the slowest job to
|
||||
@@ -71,10 +70,10 @@ install:
|
||||
then
|
||||
sudo mkdir /mnt/ramdisk
|
||||
sudo mount -t tmpfs -o size=1024m tmpfs /mnt/ramdisk
|
||||
sudo stop mysql
|
||||
sudo service mysql stop
|
||||
sudo mv /var/lib/mysql /mnt/ramdisk
|
||||
sudo ln -s /mnt/ramdisk/mysql /var/lib/mysql
|
||||
sudo start mysql
|
||||
sudo service mysql restart
|
||||
fi
|
||||
- >
|
||||
if [ "$DB" = 'pgsql' ];
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
* Grunt configuration
|
||||
*/
|
||||
|
||||
/* eslint-env node */
|
||||
module.exports = function(grunt) {
|
||||
var path = require('path'),
|
||||
tasks = {},
|
||||
|
||||
@@ -623,7 +623,7 @@ class core_role_define_role_table_advanced extends core_role_capability_table_wi
|
||||
echo "</label>\n";
|
||||
}
|
||||
if ($helpicon) {
|
||||
echo '<span class="pull-xs-right text-nowrap">'.$helpicon.'</span>';
|
||||
echo '<span class="float-sm-right text-nowrap">'.$helpicon.'</span>';
|
||||
}
|
||||
echo '</div>';
|
||||
if (isset($this->errors[$name])) {
|
||||
|
||||
@@ -48,7 +48,4 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
|
||||
|
||||
$optionalsubsystems->add(new admin_setting_configcheckbox('allowstealth', new lang_string('allowstealthmodules'),
|
||||
new lang_string('allowstealthmodules_help'), 0, 1, 0));
|
||||
|
||||
$optionalsubsystems->add(new admin_setting_configcheckbox('enablecoursepublishing',
|
||||
new lang_string('enablecoursepublishing', 'hub'), new lang_string('enablecoursepublishing_help', 'hub'), 0));
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ Feature: Manage tokens
|
||||
| username | password | firstname | lastname |
|
||||
| testuser | testuser | Joe | Bloggs |
|
||||
| testuser2 | testuser2 | TestFirstname | TestLastname |
|
||||
And I change window size to "small"
|
||||
And I log in as "admin"
|
||||
And I am on site homepage
|
||||
|
||||
|
||||
@@ -20,12 +20,12 @@ Feature: Edit capabilities
|
||||
And I set the following system permissions of "Teacher" role:
|
||||
| capability | permission |
|
||||
| block/mnet_hosts:myaddinstance | Allow |
|
||||
| moodle/community:add | Inherit |
|
||||
| moodle/site:messageanyuser | Inherit |
|
||||
| moodle/grade:managesharedforms | Prevent |
|
||||
| moodle/course:request | Prohibit |
|
||||
When I follow "Edit Teacher role"
|
||||
Then "block/mnet_hosts:myaddinstance" capability has "Allow" permission
|
||||
And "moodle/community:add" capability has "Not set" permission
|
||||
And "moodle/site:messageanyuser" capability has "Not set" permission
|
||||
And "moodle/grade:managesharedforms" capability has "Prevent" permission
|
||||
And "moodle/course:request" capability has "Prohibit" permission
|
||||
|
||||
|
||||
@@ -114,15 +114,17 @@ class tool_capability_renderer extends plugin_renderer_base {
|
||||
}
|
||||
|
||||
// Start the list item, and print the context name as a link to the place to make changes.
|
||||
if ($contextid == context_system::instance()->id) {
|
||||
$url = new moodle_url('/admin/roles/manage.php');
|
||||
$title = get_string('changeroles', 'tool_capability');
|
||||
} else {
|
||||
$url = new moodle_url('/admin/roles/override.php', array('contextid' => $contextid));
|
||||
$title = get_string('changeoverrides', 'tool_capability');
|
||||
}
|
||||
$context = context::instance_by_id($contextid);
|
||||
$html = $this->output->heading(html_writer::link($url, $context->get_context_name(), array('title' => $title)), 3);
|
||||
|
||||
if ($context instanceof context_system) {
|
||||
$url = new moodle_url('/admin/roles/manage.php');
|
||||
} else {
|
||||
$url = new moodle_url('/admin/roles/permissions.php', ['contextid' => $contextid]);
|
||||
}
|
||||
|
||||
$title = get_string('permissionsincontext', 'core_role', $context->get_context_name());
|
||||
|
||||
$html = $this->output->heading(html_writer::link($url, $context->get_context_name(), ['title' => $title]), 3);
|
||||
$html .= html_writer::table($table);
|
||||
// If there are any child contexts, print them recursively.
|
||||
if (!empty($contexts[$contextid]->children)) {
|
||||
@@ -133,4 +135,4 @@ class tool_capability_renderer extends plugin_renderer_base {
|
||||
return $html;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -440,6 +440,9 @@ class purpose extends persistent {
|
||||
}
|
||||
if (!empty($data->sensitivedatareasons) && is_array($data->sensitivedatareasons)) {
|
||||
$data->sensitivedatareasons = implode(',', $data->sensitivedatareasons);
|
||||
} else {
|
||||
// Nothing selected. Set default value of null.
|
||||
$data->sensitivedatareasons = null;
|
||||
}
|
||||
|
||||
// A single value.
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
define(["jquery","core/notification","tool_lp/dialogue","core/str","core/ajax","core/templates"],function(a,b,c,d,e,f){var g=function(b){a(b).on("click",this.configureSettings.bind(this))};return g.prototype._dialogue=null,g.prototype.configureSettings=function(e){var g=a(e.target).closest("a").data("courseid"),h=a(e.target).closest("a").data("pushratingstouserplans"),i={courseid:g,settings:{pushratingstouserplans:h}};e.preventDefault(),f.render("tool_lp/course_competency_settings",i).done(function(a){d.get_string("configurecoursecompetencysettings","tool_lp").done(function(b){this._dialogue=new c(b,a,this.addListeners.bind(this))}.bind(this)).fail(b.exception)}.bind(this)).fail(b.exception)},g.prototype.addListeners=function(){var a=this._find('[data-action="save"]');a.on("click",this.saveSettings.bind(this));var b=this._find('[data-action="cancel"]');b.on("click",this.cancelChanges.bind(this))},g.prototype.cancelChanges=function(a){a.preventDefault(),this._dialogue.close()},g.prototype._find=function(b){return a('[data-region="coursecompetencysettings"]').find(b)},g.prototype.saveSettings=function(a){a.preventDefault();var c=this._find('input[name="pushratingstouserplans"]:checked').val(),d=this._find('input[name="courseid"]').val(),f={pushratingstouserplans:c};e.call([{methodname:"core_competency_update_course_competency_settings",args:{courseid:d,settings:f}}])[0].done(function(){this.refreshCourseCompetenciesPage()}.bind(this)).fail(b.exception)},g.prototype.refreshCourseCompetenciesPage=function(){var c=this._find('input[name="courseid"]').val();e.call([{methodname:"tool_lp_data_for_course_competencies_page",args:{courseid:c,moduleid:0}}])[0].done(function(c){f.render("tool_lp/course_competencies_page",c).done(function(b,c){a('[data-region="coursecompetenciespage"]').replaceWith(b),f.runTemplateJS(c),this._dialogue.close()}.bind(this)).fail(b.exception)}.bind(this)).fail(b.exception)},g});
|
||||
define(["jquery","core/notification","tool_lp/dialogue","core/str","core/ajax","core/templates","core/pending"],function(a,b,c,d,e,f,g){var h=function(b){a(b).on("click",this.configureSettings.bind(this))};return h.prototype._dialogue=null,h.prototype.configureSettings=function(e){var h=new g,i=a(e.target).closest("a").data("courseid"),j=a(e.target).closest("a").data("pushratingstouserplans"),k={courseid:i,settings:{pushratingstouserplans:j}};e.preventDefault(),a.when(d.get_string("configurecoursecompetencysettings","tool_lp"),f.render("tool_lp/course_competency_settings",k)).then(function(a,b){return this._dialogue=new c(a,b[0],this.addListeners.bind(this)),this._dialogue}.bind(this)).then(h.resolve)["catch"](b.exception)},h.prototype.addListeners=function(){var a=this._find('[data-action="save"]');a.on("click",this.saveSettings.bind(this));var b=this._find('[data-action="cancel"]');b.on("click",this.cancelChanges.bind(this))},h.prototype.cancelChanges=function(a){a.preventDefault(),this._dialogue.close()},h.prototype._find=function(b){return a('[data-region="coursecompetencysettings"]').find(b)},h.prototype.saveSettings=function(a){var c=new g;a.preventDefault();var d=this._find('input[name="pushratingstouserplans"]:checked').val(),f=this._find('input[name="courseid"]').val(),h={pushratingstouserplans:d};e.call([{methodname:"core_competency_update_course_competency_settings",args:{courseid:f,settings:h}}])[0].then(function(){return this.refreshCourseCompetenciesPage()}.bind(this)).then(c.resolve)["catch"](b.exception)},h.prototype.refreshCourseCompetenciesPage=function(){var c=this._find('input[name="courseid"]').val(),d=new g;e.call([{methodname:"tool_lp_data_for_course_competencies_page",args:{courseid:c,moduleid:0}}])[0].then(function(a){return f.render("tool_lp/course_competencies_page",a)}).then(function(b,c){f.replaceNode(a('[data-region="coursecompetenciespage"]'),b,c),this._dialogue.close()}.bind(this)).then(d.resolve)["catch"](b.exception)},h});
|
||||
+1
-1
@@ -1 +1 @@
|
||||
define(["jquery","core/notification","core/templates","tool_lp/dialogue","tool_lp/event_base","core/str"],function(a,b,c,d,e,f){var g=function(a){e.prototype.constructor.apply(this,[]),this._ratingOptions=a};return g.prototype=Object.create(e.prototype),g.prototype._popup=null,g.prototype._ratingOptions=null,g.prototype._afterRender=function(){var b=this._find('[data-action="rate"]'),c=this._find('[name="rating"]'),d=this._find('[name="comment"]');this._find('[data-action="cancel"]').click(function(a){a.preventDefault(),this._trigger("cancelled"),this.close()}.bind(this)),c.change(function(){var c=a(this);c.val()?b.prop("disabled",!1):b.prop("disabled",!0)}).change(),b.click(function(a){a.preventDefault();var b=c.val();b&&(this._trigger("rated",{rating:b,note:d.val()}),this.close())}.bind(this))},g.prototype.close=function(){this._popup.close(),this._popup=null},g.prototype.display=function(){return this._render().then(function(a){return f.get_string("rate","tool_lp").then(function(b){this._popup=new d(b,a,this._afterRender.bind(this))}.bind(this))}.bind(this)).fail(b.exception)},g.prototype._find=function(b){return a(this._popup.getContent()).find(b)},g.prototype._render=function(){var a={cangrade:this._canGrade,ratings:this._ratingOptions};return c.render("tool_lp/competency_grader",a)},g});
|
||||
define(["jquery","core/notification","core/templates","tool_lp/dialogue","tool_lp/event_base","core/str"],function(a,b,c,d,e,f){var g=function(a){e.prototype.constructor.apply(this,[]),this._ratingOptions=a};return g.prototype=Object.create(e.prototype),g.prototype._popup=null,g.prototype._ratingOptions=null,g.prototype._afterRender=function(){var b=this._find('[data-action="rate"]'),c=this._find('[name="rating"]'),d=this._find('[name="comment"]');this._find('[data-action="cancel"]').click(function(a){a.preventDefault(),this._trigger("cancelled"),this.close()}.bind(this)),c.change(function(){var c=a(this);c.val()?b.prop("disabled",!1):b.prop("disabled",!0)}).change(),b.click(function(a){a.preventDefault();var b=c.val();b&&(this._trigger("rated",{rating:b,note:d.val()}),this.close())}.bind(this))},g.prototype.close=function(){this._popup.close(),this._popup=null},g.prototype.display=function(){return a.when(f.get_string("rate","tool_lp"),this._render()).then(function(a,b){return this._popup=new d(a,b[0],this._afterRender.bind(this)),this._popup}.bind(this))["catch"](b.exception)},g.prototype._find=function(b){return a(this._popup.getContent()).find(b)},g.prototype._render=function(){var a={cangrade:this._canGrade,ratings:this._ratingOptions};return c.render("tool_lp/competency_grader",a)},g});
|
||||
@@ -1 +1 @@
|
||||
define(["jquery","core/notification","core/ajax","core/log","tool_lp/grade_dialogue","tool_lp/event_base","tool_lp/scalevalues"],function(a,b,c,d,e,f,g){var h=function(b,c,d,e,g,h,i){f.prototype.constructor.apply(this,[]);var j=a(b);if(!j.length)throw new Error("Could not find the trigger");this._scaleId=c,this._competencyId=d,this._userId=e,this._planId=g,this._courseId=h,this._chooseStr=i,this._setUp(),j.click(function(a){a.preventDefault(),this._dialogue.display()}.bind(this)),this._planId?(this._methodName="core_competency_grade_competency_in_plan",this._args={competencyid:this._competencyId,planid:this._planId}):this._courseId?(this._methodName="core_competency_grade_competency_in_course",this._args={competencyid:this._competencyId,courseid:this._courseId,userid:this._userId}):(this._methodName="core_competency_grade_competency",this._args={userid:this._userId,competencyid:this._competencyId})};return h.prototype=Object.create(f.prototype),h.prototype._setUp=function(){var a=[],d=this,f=g.get_values(d._scaleId);f.done(function(f){a.push({value:"",name:d._chooseStr});for(var g=0;g<f.length;g++){var h=f[g];a.push({value:h.id,name:h.name})}d._dialogue=new e(a),d._dialogue.on("rated",function(a,e){var f=d._args;f.grade=e.rating,f.note=e.note,c.call([{methodname:d._methodName,args:f,done:function(a){d._trigger("competencyupdated",{args:f,evidence:a})},fail:b.exception}])})}).fail(b.exception)},h.prototype._scaleId=null,h.prototype._competencyId=null,h.prototype._userId=null,h.prototype._planId=null,h.prototype._courseId=null,h.prototype._chooseStr=null,h.prototype._dialogue=null,h});
|
||||
define(["jquery","core/notification","core/ajax","core/log","tool_lp/grade_dialogue","tool_lp/event_base","tool_lp/scalevalues"],function(a,b,c,d,e,f,g){var h=function(b,c,d,e,g,h,i){f.prototype.constructor.apply(this,[]);var j=a(b);if(!j.length)throw new Error("Could not find the trigger");this._scaleId=c,this._competencyId=d,this._userId=e,this._planId=g,this._courseId=h,this._chooseStr=i,this._setUp(),j.click(function(a){a.preventDefault(),this._dialogue.display()}.bind(this)),this._planId?(this._methodName="core_competency_grade_competency_in_plan",this._args={competencyid:this._competencyId,planid:this._planId}):this._courseId?(this._methodName="core_competency_grade_competency_in_course",this._args={competencyid:this._competencyId,courseid:this._courseId,userid:this._userId}):(this._methodName="core_competency_grade_competency",this._args={userid:this._userId,competencyid:this._competencyId})};return h.prototype=Object.create(f.prototype),h.prototype._setUp=function(){var a=[],d=this,f=g.get_values(d._scaleId);f.then(function(b){a.push({value:"",name:d._chooseStr});for(var c=0;c<b.length;c++){var e=b[c];a.push({value:e.id,name:e.name})}return a}).then(function(a){return new e(a)}).then(function(a){return a.on("rated",function(a,e){var f=d._args;f.grade=e.rating,f.note=e.note,c.call([{methodname:d._methodName,args:f,done:function(a){d._trigger("competencyupdated",{args:f,evidence:a})},fail:b.exception}])}),a}).then(function(a){d._dialogue=a}).fail(b.exception)},h.prototype._scaleId=null,h.prototype._competencyId=null,h.prototype._userId=null,h.prototype._planId=null,h.prototype._courseId=null,h.prototype._chooseStr=null,h.prototype._dialogue=null,h});
|
||||
@@ -27,8 +27,9 @@ define(['jquery',
|
||||
'core/templates',
|
||||
'core/str',
|
||||
'tool_lp/competencypicker',
|
||||
'tool_lp/dragdrop-reorder'],
|
||||
function($, notification, ajax, templates, str, Picker, dragdrop) {
|
||||
'tool_lp/dragdrop-reorder',
|
||||
'core/pending'],
|
||||
function($, notification, ajax, templates, str, Picker, dragdrop, Pending) {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
@@ -117,6 +118,7 @@ define(['jquery',
|
||||
* Pick a competency
|
||||
*
|
||||
* @method pickCompetency
|
||||
* @return {Promise}
|
||||
*/
|
||||
competencies.prototype.pickCompetency = function() {
|
||||
var self = this;
|
||||
@@ -132,6 +134,7 @@ define(['jquery',
|
||||
self.pickerInstance = new Picker(self.pageContextId, false, pageContextIncludes);
|
||||
self.pickerInstance.on('save', function(e, data) {
|
||||
var compIds = data.competencyIds;
|
||||
var pendingPromise = new Pending();
|
||||
|
||||
if (self.itemtype === "course") {
|
||||
requests = [];
|
||||
@@ -181,17 +184,20 @@ define(['jquery',
|
||||
pagerender = 'tool_lp/plan_page';
|
||||
pageregion = 'plan-page';
|
||||
}
|
||||
ajax.call(requests)[requests.length - 1].then(function(context) {
|
||||
ajax.call(requests)[requests.length - 1]
|
||||
.then(function(context) {
|
||||
return templates.render(pagerender, context);
|
||||
}).then(function(html, js) {
|
||||
$('[data-region="' + pageregion + '"]').replaceWith(html);
|
||||
templates.runTemplateJS(js);
|
||||
})
|
||||
.then(function(html, js) {
|
||||
templates.replaceNode($('[data-region="' + pageregion + '"]'), html, js);
|
||||
return;
|
||||
}).catch(notification.exception);
|
||||
})
|
||||
.then(pendingPromise.resolve)
|
||||
.catch(notification.exception);
|
||||
});
|
||||
}
|
||||
|
||||
self.pickerInstance.display();
|
||||
return self.pickerInstance.display();
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -302,6 +308,7 @@ define(['jquery',
|
||||
if (localthis.itemtype == 'course') {
|
||||
// Course completion rule handling.
|
||||
$('[data-region="coursecompetenciespage"]').on('change', 'select[data-field="ruleoutcome"]', function(e) {
|
||||
var pendingPromise = new Pending();
|
||||
var requests = [];
|
||||
var pagerender = 'tool_lp/course_competencies_page';
|
||||
var pageregion = 'coursecompetenciespage';
|
||||
@@ -314,18 +321,24 @@ define(['jquery',
|
||||
args: {courseid: localthis.itemid, moduleid: 0}}
|
||||
]);
|
||||
|
||||
requests[1].done(function(context) {
|
||||
templates.render(pagerender, context).done(function(html, js) {
|
||||
$('[data-region="' + pageregion + '"]').replaceWith(html);
|
||||
templates.runTemplateJS(js);
|
||||
}).fail(notification.exception);
|
||||
}).fail(notification.exception);
|
||||
requests[1].then(function(context) {
|
||||
return templates.render(pagerender, context);
|
||||
})
|
||||
.then(function(html, js) {
|
||||
return templates.replaceNode($('[data-region="' + pageregion + '"]'), html, js);
|
||||
})
|
||||
.then(pendingPromise.resolve)
|
||||
.catch(notification.exception);
|
||||
});
|
||||
}
|
||||
|
||||
$('[data-region="actions"] button').click(function(e) {
|
||||
var pendingPromise = new Pending();
|
||||
e.preventDefault();
|
||||
localthis.pickCompetency();
|
||||
|
||||
localthis.pickCompetency()
|
||||
.then(pendingPromise.resolve)
|
||||
.catch();
|
||||
});
|
||||
$('[data-action="delete-competency-link"]').click(function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
@@ -33,8 +33,12 @@ define(['jquery',
|
||||
'tool_lp/menubar',
|
||||
'tool_lp/competencypicker',
|
||||
'tool_lp/competency_outcomes',
|
||||
'tool_lp/competencyruleconfig'],
|
||||
function($, url, templates, notification, str, ajax, dragdrop, Ariatree, Dialogue, menubar, Picker, Outcomes, RuleConfig) {
|
||||
'tool_lp/competencyruleconfig',
|
||||
'core/pending',
|
||||
],
|
||||
function(
|
||||
$, url, templates, notification, str, ajax, dragdrop, Ariatree, Dialogue, menubar, Picker, Outcomes, RuleConfig, Pending
|
||||
) {
|
||||
|
||||
// Private variables and functions.
|
||||
/** @var {Object} treeModel - This is an object representing the nodes in the tree. */
|
||||
@@ -412,6 +416,7 @@ define(['jquery',
|
||||
if (!pickerInstance) {
|
||||
pickerInstance = new Picker(pageContextId, relatedTarget.competencyframeworkid);
|
||||
pickerInstance.on('save', function(e, data) {
|
||||
var pendingPromise = new Pending();
|
||||
var compIds = data.competencyIds;
|
||||
|
||||
var calls = [];
|
||||
@@ -436,7 +441,9 @@ define(['jquery',
|
||||
templates.runTemplateJS(js);
|
||||
updatedRelatedCompetencies();
|
||||
return;
|
||||
}).catch(notification.exception);
|
||||
})
|
||||
.then(pendingPromise.resolve)
|
||||
.catch(notification.exception);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -31,8 +31,10 @@ define(['jquery',
|
||||
'core/templates',
|
||||
'tool_lp/dialogue',
|
||||
'core/str',
|
||||
'tool_lp/tree'],
|
||||
function($, Notification, Ajax, Templates, Dialogue, Str, Tree) {
|
||||
'tool_lp/tree',
|
||||
'core/pending'
|
||||
],
|
||||
function($, Notification, Ajax, Templates, Dialogue, Str, Tree, Pending) {
|
||||
|
||||
/**
|
||||
* Competency picker class.
|
||||
@@ -157,6 +159,7 @@ define(['jquery',
|
||||
// Add listener for add.
|
||||
self._find('[data-region="competencylinktree"] [data-action="add"]').click(function(e) {
|
||||
e.preventDefault();
|
||||
var pendingPromise = new Pending();
|
||||
if (!self._selectedCompetencies.length) {
|
||||
return;
|
||||
}
|
||||
@@ -168,7 +171,10 @@ define(['jquery',
|
||||
self._trigger('save', {competencyId: self._selectedCompetencies[0]});
|
||||
}
|
||||
|
||||
// The dialogue here is a YUI dialogue and doesn't support Promises at all.
|
||||
// However, it is typically synchronous so this shoudl suffice.
|
||||
self.close();
|
||||
pendingPromise.resolve();
|
||||
});
|
||||
|
||||
// The list of selected competencies will be modified while looping (because of the listeners above).
|
||||
|
||||
@@ -26,8 +26,10 @@ define(['jquery',
|
||||
'tool_lp/dialogue',
|
||||
'core/str',
|
||||
'core/ajax',
|
||||
'core/templates'],
|
||||
function($, notification, Dialogue, str, ajax, templates) {
|
||||
'core/templates',
|
||||
'core/pending'
|
||||
],
|
||||
function($, notification, Dialogue, str, ajax, templates, Pending) {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
@@ -48,6 +50,7 @@ define(['jquery',
|
||||
* @method configureSettings
|
||||
*/
|
||||
settingsMod.prototype.configureSettings = function(e) {
|
||||
var pendingPromise = new Pending();
|
||||
var courseid = $(e.target).closest('a').data('courseid');
|
||||
var currentValue = $(e.target).closest('a').data('pushratingstouserplans');
|
||||
var context = {
|
||||
@@ -56,16 +59,21 @@ define(['jquery',
|
||||
};
|
||||
e.preventDefault();
|
||||
|
||||
templates.render('tool_lp/course_competency_settings', context).done(function(html) {
|
||||
str.get_string('configurecoursecompetencysettings', 'tool_lp').done(function(title) {
|
||||
this._dialogue = new Dialogue(
|
||||
title,
|
||||
html,
|
||||
this.addListeners.bind(this)
|
||||
);
|
||||
}.bind(this)).fail(notification.exception);
|
||||
}.bind(this)).fail(notification.exception);
|
||||
$.when(
|
||||
str.get_string('configurecoursecompetencysettings', 'tool_lp'),
|
||||
templates.render('tool_lp/course_competency_settings', context)
|
||||
)
|
||||
.then(function(title, templateResult) {
|
||||
this._dialogue = new Dialogue(
|
||||
title,
|
||||
templateResult[0],
|
||||
this.addListeners.bind(this)
|
||||
);
|
||||
|
||||
return this._dialogue;
|
||||
}.bind(this))
|
||||
.then(pendingPromise.resolve)
|
||||
.catch(notification.exception);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -108,6 +116,7 @@ define(['jquery',
|
||||
* @method saveSettings
|
||||
*/
|
||||
settingsMod.prototype.saveSettings = function(e) {
|
||||
var pendingPromise = new Pending();
|
||||
e.preventDefault();
|
||||
|
||||
var newValue = this._find('input[name="pushratingstouserplans"]:checked').val();
|
||||
@@ -117,9 +126,12 @@ define(['jquery',
|
||||
ajax.call([
|
||||
{methodname: 'core_competency_update_course_competency_settings',
|
||||
args: {courseid: courseId, settings: settings}}
|
||||
])[0].done(function() {
|
||||
this.refreshCourseCompetenciesPage();
|
||||
}.bind(this)).fail(notification.exception);
|
||||
])[0]
|
||||
.then(function() {
|
||||
return this.refreshCourseCompetenciesPage();
|
||||
}.bind(this))
|
||||
.then(pendingPromise.resolve)
|
||||
.catch(notification.exception);
|
||||
|
||||
};
|
||||
|
||||
@@ -131,18 +143,23 @@ define(['jquery',
|
||||
*/
|
||||
settingsMod.prototype.refreshCourseCompetenciesPage = function() {
|
||||
var courseId = this._find('input[name="courseid"]').val();
|
||||
var pendingPromise = new Pending();
|
||||
|
||||
ajax.call([
|
||||
{methodname: 'tool_lp_data_for_course_competencies_page',
|
||||
args: {courseid: courseId, moduleid: 0}}
|
||||
])[0].done(function(context) {
|
||||
templates.render('tool_lp/course_competencies_page', context).done(function(html, js) {
|
||||
$('[data-region="coursecompetenciespage"]').replaceWith(html);
|
||||
templates.runTemplateJS(js);
|
||||
this._dialogue.close();
|
||||
}.bind(this)).fail(notification.exception);
|
||||
}.bind(this)).fail(notification.exception);
|
||||
])[0]
|
||||
.then(function(context) {
|
||||
return templates.render('tool_lp/course_competencies_page', context);
|
||||
})
|
||||
.then(function(html, js) {
|
||||
templates.replaceNode($('[data-region="coursecompetenciespage"]'), html, js);
|
||||
this._dialogue.close();
|
||||
|
||||
return;
|
||||
}.bind(this))
|
||||
.then(pendingPromise.resolve)
|
||||
.catch(notification.exception);
|
||||
};
|
||||
|
||||
return /** @alias module:tool_lp/configurecoursecompetencysettings */ settingsMod;
|
||||
|
||||
@@ -102,15 +102,20 @@ define(['jquery',
|
||||
* @return {Promise}
|
||||
*/
|
||||
Grade.prototype.display = function() {
|
||||
return this._render().then(function(html) {
|
||||
return Str.get_string('rate', 'tool_lp').then(function(title) {
|
||||
this._popup = new Dialogue(
|
||||
title,
|
||||
html,
|
||||
this._afterRender.bind(this)
|
||||
);
|
||||
}.bind(this));
|
||||
}.bind(this)).fail(Notification.exception);
|
||||
return $.when(
|
||||
Str.get_string('rate', 'tool_lp'),
|
||||
this._render()
|
||||
)
|
||||
.then(function(title, templateResult) {
|
||||
this._popup = new Dialogue(
|
||||
title,
|
||||
templateResult[0],
|
||||
this._afterRender.bind(this)
|
||||
);
|
||||
|
||||
return this._popup;
|
||||
}.bind(this))
|
||||
.catch(Notification.exception);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -95,7 +95,7 @@ define(['jquery',
|
||||
self = this;
|
||||
|
||||
var promise = ScaleValues.get_values(self._scaleId);
|
||||
promise.done(function(scalevalues) {
|
||||
promise.then(function(scalevalues) {
|
||||
options.push({
|
||||
value: '',
|
||||
name: self._chooseStr
|
||||
@@ -109,8 +109,13 @@ define(['jquery',
|
||||
});
|
||||
}
|
||||
|
||||
self._dialogue = new GradeDialogue(options);
|
||||
self._dialogue.on('rated', function(e, data) {
|
||||
return options;
|
||||
})
|
||||
.then(function(options) {
|
||||
return new GradeDialogue(options);
|
||||
})
|
||||
.then(function(dialogue) {
|
||||
dialogue.on('rated', function(e, data) {
|
||||
var args = self._args;
|
||||
args.grade = data.rating;
|
||||
args.note = data.note;
|
||||
@@ -123,7 +128,15 @@ define(['jquery',
|
||||
fail: notification.exception
|
||||
}]);
|
||||
});
|
||||
}).fail(notification.exception);
|
||||
|
||||
return dialogue;
|
||||
})
|
||||
.then(function(dialogue) {
|
||||
self._dialogue = dialogue;
|
||||
|
||||
return;
|
||||
})
|
||||
.fail(notification.exception);
|
||||
};
|
||||
|
||||
/** @type {Number} The scale id for this competency. */
|
||||
|
||||
@@ -30,6 +30,8 @@
|
||||
}}
|
||||
<div class="float-right card p-2">
|
||||
<form class="user-competency-course-navigation">
|
||||
<input type="hidden" name="courseid" value="{{courseid}}"/>
|
||||
<input type="hidden" name="mod" value="{{moduleid}}"/>
|
||||
{{#hasmodules}}
|
||||
<span>
|
||||
<label for="module-nav-{{uniqid}}" class="accesshide">{{#str}}filterbyactivity, tool_lp{{/str}}</label>
|
||||
|
||||
@@ -38,6 +38,9 @@
|
||||
<div class="float-sm-right card card-block p-x-1 p-b-1">
|
||||
<p>{{{groupselector}}}</p>
|
||||
<form class="user-competency-course-navigation">
|
||||
<input type="hidden" name="userid" value="{{userid}}"/>
|
||||
<input type="hidden" name="competencyid" value="{{competencyid}}"/>
|
||||
<input type="hidden" name="courseid" value="{{courseid}}"/>
|
||||
{{#hasusers}}
|
||||
<span>
|
||||
<label for="user-nav-{{uniqid}}" class="accesshide">{{#str}}jumptouser, tool_lp{{/str}}</label>
|
||||
|
||||
@@ -6,6 +6,7 @@ Feature: Manage competency frameworks
|
||||
|
||||
Background:
|
||||
Given I log in as "admin"
|
||||
And I change window size to "small"
|
||||
And I am on site homepage
|
||||
|
||||
Scenario: Create a new framework
|
||||
|
||||
@@ -6,6 +6,7 @@ Feature: Manage plearning plan templates
|
||||
|
||||
Background:
|
||||
Given I log in as "admin"
|
||||
And I change window size to "small"
|
||||
And I am on site homepage
|
||||
|
||||
Scenario: Create a new learning plan template
|
||||
|
||||
@@ -105,7 +105,7 @@ $string['requirevalidation'] = 'Validate sender address';
|
||||
$string['name'] = 'Name';
|
||||
$string['ssl'] = 'SSL (Auto-detect SSL version)';
|
||||
$string['sslv2'] = 'SSLv2 (Force SSL Version 2)';
|
||||
$string['sslv3'] = 'SSLv2 (Force SSL Version 3)';
|
||||
$string['sslv3'] = 'SSLv3 (Force SSL Version 3)';
|
||||
$string['taskcleanup'] = 'Cleanup of unverified incoming email';
|
||||
$string['taskpickup'] = 'Incoming email pickup';
|
||||
$string['tls'] = 'TLS (TLS; started via protocol-level negotiation over unencrypted channel; RECOMMENDED way of initiating secure connection)';
|
||||
|
||||
@@ -82,7 +82,7 @@ $string['mobileappearance'] = 'Mobile appearance';
|
||||
$string['mobileauthentication'] = 'Mobile authentication';
|
||||
$string['mobilecssurl'] = 'CSS';
|
||||
$string['mobilefeatures'] = 'Mobile features';
|
||||
$string['mobilenotificationsdisabledwarning'] = 'Mobile notifications are not enabled. They should be enabled in Manage message outputs.';
|
||||
$string['mobilenotificationsdisabledwarning'] = 'Mobile notifications are not enabled. They should be enabled in Notification settings.';
|
||||
$string['mobilesettings'] = 'Mobile settings';
|
||||
$string['offlineuse'] = 'Offline use';
|
||||
$string['pluginname'] = 'Moodle app tools';
|
||||
|
||||
@@ -30,7 +30,7 @@ require_once($CFG->libdir . '/externallib.php');
|
||||
|
||||
$serviceshortname = required_param('service', PARAM_ALPHANUMEXT);
|
||||
$passport = required_param('passport', PARAM_RAW); // Passport send from the app to validate the response URL.
|
||||
$urlscheme = optional_param('urlscheme', 'moodlemobile', PARAM_NOTAGS); // The URL scheme the app supports.
|
||||
$urlscheme = optional_param('urlscheme', 'moodlemobile', PARAM_ALPHANUM); // The URL scheme the app supports.
|
||||
$confirmed = optional_param('confirmed', false, PARAM_BOOL); // If we are being redirected after user confirmation.
|
||||
$oauthsso = optional_param('oauthsso', 0, PARAM_INT); // Id of the OpenID issuer (for OAuth direct SSO).
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ if ($hassiteconfig) {
|
||||
|
||||
$temp->add(new admin_setting_configtext('tool_mobile/forcedurlscheme',
|
||||
new lang_string('forcedurlscheme_key', 'tool_mobile'),
|
||||
new lang_string('forcedurlscheme', 'tool_mobile'), '', PARAM_NOTAGS));
|
||||
new lang_string('forcedurlscheme', 'tool_mobile'), '', PARAM_ALPHANUM));
|
||||
|
||||
$ADMIN->add('mobileapp', $temp);
|
||||
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
define(["jquery","core/ajax","core/notification","core/modal_factory","core/modal_events"],function(a,b,c,d,e){var f={VIEW_POLICY:'[data-action="view"]'},g=function(){this.registerEvents()};return g.prototype.registerEvents=function(){a(f.VIEW_POLICY).click(function(f){f.preventDefault();var g=a(this).data("versionid"),h=a(this).data("behalfid"),i={versionid:g,behalfid:h},j={methodname:"tool_policy_get_policy_version",args:i},k=a.Deferred(),l=a.Deferred(),m=d.create({title:k,body:l,large:!0}).then(function(a){return a.getRoot().on(e.hidden,function(){a.destroy()}),a}).then(function(a){return a.show(),a})["catch"](c.exception),n=b.call([j]);a.when(n[0]).then(function(a){if(a.result.policy)return k.resolve(a.result.policy.name),l.resolve(a.result.policy.content),a;throw new Error(a.warnings[0].message)})["catch"](function(a){return m.then(function(a){return a.hide(),a.destroy(),a})["catch"](c.exception),c.addNotification({message:a,type:"error"})})})},{init:function(){return new g}}});
|
||||
define(["jquery","core/ajax","core/notification","core/modal_factory","core/modal_events"],function(a,b,c,d,e){var f=function(a){this.registerEvents(a)};return f.prototype.registerEvents=function(f){f.on("click",function(f){f.preventDefault();var g=a(this).data("versionid"),h=a(this).data("behalfid"),i={versionid:g,behalfid:h},j={methodname:"tool_policy_get_policy_version",args:i},k=a.Deferred(),l=a.Deferred(),m=d.create({title:k,body:l,large:!0}).then(function(a){return a.getRoot().on(e.hidden,function(){a.destroy()}),a}).then(function(a){return a.show(),a})["catch"](c.exception),n=b.call([j]);a.when(n[0]).then(function(a){if(a.result.policy)return k.resolve(a.result.policy.name),l.resolve(a.result.policy.content),a;throw new Error(a.warnings[0].message)})["catch"](function(a){return m.then(function(a){return a.hide(),a.destroy(),a})["catch"](c.exception),c.addNotification({message:a,type:"error"})})})},{init:function(b){return b=a(b),new f(b)}}});
|
||||
@@ -29,27 +29,18 @@ define([
|
||||
'core/modal_events'],
|
||||
function($, Ajax, Notification, ModalFactory, ModalEvents) {
|
||||
|
||||
/**
|
||||
* List of action selectors.
|
||||
*
|
||||
* @type {{VIEW_POLICY: string}}
|
||||
*/
|
||||
var ACTIONS = {
|
||||
VIEW_POLICY: '[data-action="view"]'
|
||||
};
|
||||
|
||||
/**
|
||||
* PolicyActions class.
|
||||
*/
|
||||
var PolicyActions = function() {
|
||||
this.registerEvents();
|
||||
var PolicyActions = function(root) {
|
||||
this.registerEvents(root);
|
||||
};
|
||||
|
||||
/**
|
||||
* Register event listeners.
|
||||
*/
|
||||
PolicyActions.prototype.registerEvents = function() {
|
||||
$(ACTIONS.VIEW_POLICY).click(function(e) {
|
||||
PolicyActions.prototype.registerEvents = function(root) {
|
||||
root.on("click", function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var versionid = $(this).data('versionid');
|
||||
@@ -127,8 +118,9 @@ function($, Ajax, Notification, ModalFactory, ModalEvents) {
|
||||
* @method init
|
||||
* @return {PolicyActions}
|
||||
*/
|
||||
'init': function() {
|
||||
return new PolicyActions();
|
||||
'init': function(root) {
|
||||
root = $(root);
|
||||
return new PolicyActions(root);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
@@ -111,7 +111,7 @@ class accept_policy extends \moodleform {
|
||||
}
|
||||
}
|
||||
|
||||
$PAGE->requires->js_call_amd('tool_policy/policyactions', 'init');
|
||||
$PAGE->requires->js_call_amd('tool_policy/policyactions', 'init', ['[data-action="view"]']);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -197,4 +197,4 @@ class accept_policy extends \moodleform {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ require(['jquery', 'tool_policy/jquery-eu-cookie-law-popup', 'tool_policy/policy
|
||||
"<ul>{{#policies}}" +
|
||||
"<li>" +
|
||||
"<a href=\"{{pluginbaseurl}}/view.php?versionid={{id}}{{#returnurl}}&returnurl={{.}}{{/returnurl}}\" " +
|
||||
" data-action=\"view\" data-versionid=\"{{id}}\" data-behalfid=\"1\" >" +
|
||||
" data-action=\"view-guest\" data-versionid=\"{{id}}\" data-behalfid=\"1\" >" +
|
||||
"{{{name}}}" +
|
||||
"</a>" +
|
||||
"</li>" +
|
||||
@@ -81,7 +81,7 @@ require(['jquery', 'tool_policy/jquery-eu-cookie-law-popup', 'tool_policy/policy
|
||||
{{/haspolicies}}
|
||||
|
||||
// Initialise the JS for the modal window which displays the policy versions.
|
||||
ActionsMod.init();
|
||||
ActionsMod.init('[data-action="view-guest"]');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -143,6 +143,6 @@
|
||||
{{#js}}
|
||||
// Initialise the JS for the modal window which displays the policy versions.
|
||||
require(['tool_policy/policyactions'], function(ActionsMod) {
|
||||
ActionsMod.init();
|
||||
ActionsMod.init('[data-action="view"]');
|
||||
});
|
||||
{{/js}}
|
||||
|
||||
@@ -73,6 +73,6 @@
|
||||
{{#js}}
|
||||
// Initialise the JS for the modal window which displays the policy versions.
|
||||
require(['tool_policy/policyactions'], function(ActionsMod) {
|
||||
ActionsMod.init();
|
||||
ActionsMod.init('[data-action="view"]');
|
||||
});
|
||||
{{/js}}
|
||||
|
||||
@@ -77,18 +77,22 @@ class provider implements
|
||||
}
|
||||
|
||||
if ($descriptionidentifier !== null) {
|
||||
$time = transform::datetime($value);
|
||||
$tour = \tool_usertours\tour::instance($tourid);
|
||||
try {
|
||||
$tour = \tool_usertours\tour::instance($tourid);
|
||||
$time = transform::datetime($value);
|
||||
|
||||
writer::export_user_preference(
|
||||
'tool_usertours',
|
||||
$name,
|
||||
$time,
|
||||
get_string($descriptionidentifier, 'tool_usertours', (object) [
|
||||
'name' => $tour->get_name(),
|
||||
'time' => $time,
|
||||
])
|
||||
);
|
||||
writer::export_user_preference(
|
||||
'tool_usertours',
|
||||
$name,
|
||||
$time,
|
||||
get_string($descriptionidentifier, 'tool_usertours', (object) [
|
||||
'name' => $tour->get_name(),
|
||||
'time' => $time,
|
||||
])
|
||||
);
|
||||
} catch (\dml_missing_record_exception $ex) {
|
||||
// The tour related to this user preference no longer exists.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,4 +112,37 @@ class tool_usertours_privacy_testcase extends \core_privacy\tests\provider_testc
|
||||
|
||||
$this->assertCount(2, (array) $prefs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that export_user_preferences excludes deleted tours.
|
||||
*/
|
||||
public function test_export_user_preferences_deleted_tour() {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
$this->setAdminUser();
|
||||
|
||||
$user = \core_user::get_user_by_username('admin');
|
||||
|
||||
$alltours = $DB->get_records('tool_usertours_tours');
|
||||
|
||||
$tour1 = tour::instance(array_shift($alltours)->id);
|
||||
$tour1->mark_user_completed();
|
||||
|
||||
$tour2 = tour::instance(array_shift($alltours)->id);
|
||||
$tour2->mark_user_completed();
|
||||
$tour2->remove();
|
||||
|
||||
$writer = writer::with_context(\context_system::instance());
|
||||
|
||||
provider::export_user_preferences($user->id);
|
||||
$this->assertTrue($writer->has_any_data());
|
||||
|
||||
// We should have one preference.
|
||||
$prefs = $writer->get_user_preferences('tool_usertours');
|
||||
$this->assertCount(1, (array) $prefs);
|
||||
|
||||
// The preference should be related to the first tour.
|
||||
$this->assertContains($tour1->get_name(), reset($prefs)->description);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,12 +50,11 @@ abstract class by_course extends base {
|
||||
if (!empty($this->options['filter'])) {
|
||||
$courses = array();
|
||||
foreach ($this->options['filter'] as $courseid) {
|
||||
$courses[$courseid] = new \stdClass();
|
||||
$courses[$courseid]->id = $courseid;
|
||||
$courses[$courseid] = intval($courseid);
|
||||
}
|
||||
|
||||
list($coursesql, $courseparams) = $DB->get_in_or_equal($courses, SQL_PARAMS_NAMED);
|
||||
$sql .= " AND c.id IN $coursesql";
|
||||
$sql .= " AND c.id $coursesql";
|
||||
$params = $params + $courseparams;
|
||||
}
|
||||
|
||||
|
||||
@@ -78,9 +78,9 @@ class result_file extends result {
|
||||
// if this analyser was analysed less that 1 week ago we skip generating a new one. This
|
||||
// helps scale the evaluation process as sites with tons of courses may need a lot of time to
|
||||
// complete an evaluation.
|
||||
if (!empty($options['evaluation']) && !empty($options['reuseprevanalysed'])) {
|
||||
if (!empty($this->options['evaluation']) && !empty($this->options['reuseprevanalysed'])) {
|
||||
|
||||
$previousanalysis = \core_analytics\dataset_manager::get_evaluation_analysable_file($this->analyser->get_modelid(),
|
||||
$previousanalysis = \core_analytics\dataset_manager::get_evaluation_analysable_file($this->modelid,
|
||||
$analysable->get_id(), $timesplitting->get_id());
|
||||
// 1 week is a partly random time interval, no need to worry about DST.
|
||||
$boundary = time() - WEEKSECS;
|
||||
|
||||
@@ -229,7 +229,12 @@ abstract class base extends \core_analytics\calculable {
|
||||
*/
|
||||
public function get_insights_users(\context $context) {
|
||||
if ($context->contextlevel === CONTEXT_USER) {
|
||||
$users = [$context->instanceid => \core_user::get_user($context->instanceid)];
|
||||
if (!has_capability('moodle/analytics:listowninsights', $context, $context->instanceid)) {
|
||||
$users = [];
|
||||
} else {
|
||||
$users = [$context->instanceid => \core_user::get_user($context->instanceid)];
|
||||
}
|
||||
|
||||
} else if ($context->contextlevel >= CONTEXT_COURSE) {
|
||||
// At course level or below only enrolled users although this is not ideal for
|
||||
// teachers assigned at category level.
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once(__DIR__ . '/test_target_shortname.php');
|
||||
require_once(__DIR__ . '/test_target_site_users.php');
|
||||
|
||||
/**
|
||||
* Test target.
|
||||
|
||||
@@ -478,6 +478,13 @@ class core_analytics_prediction_testcase extends advanced_testcase {
|
||||
$message = 'The returned status code ' . $result->status . ' should include ' . $expected[$timesplitting];
|
||||
$filtered = $result->status & $expected[$timesplitting];
|
||||
$this->assertEquals($expected[$timesplitting], $filtered, $message);
|
||||
|
||||
$options = ['evaluation' => true, 'reuseprevanalysed' => true];
|
||||
$result = new \core_analytics\local\analysis\result_file($model->get_id(), true, $options);
|
||||
$timesplittingobj = \core_analytics\manager::get_time_splitting($timesplitting);
|
||||
$analysable = new \core_analytics\site();
|
||||
$cachedanalysis = $result->retrieve_cached_result($timesplittingobj, $analysable);
|
||||
$this->assertInstanceOf(\stored_file::class, $cachedanalysis);
|
||||
}
|
||||
|
||||
set_config('enabled_stores', '', 'tool_log');
|
||||
|
||||
@@ -72,6 +72,8 @@ class login implements renderable, templatable {
|
||||
public $username;
|
||||
/** @var string The csrf token to limit login to requests that come from the login form. */
|
||||
public $logintoken;
|
||||
/** @var string Maintenance message, if Maintenance is enabled. */
|
||||
public $maintenance;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
@@ -109,6 +111,10 @@ class login implements renderable, templatable {
|
||||
$this->instructions = get_string('loginsteps', 'core', 'signup.php');
|
||||
}
|
||||
|
||||
if ($CFG->maintenance_enabled == true && !empty($CFG->maintenance_message)) {
|
||||
$this->maintenance = $CFG->maintenance_message;
|
||||
}
|
||||
|
||||
// Identity providers.
|
||||
$this->identityproviders = \auth_plugin_base::get_identity_providers($authsequence);
|
||||
$this->logintoken = \core\session\manager::get_login_token();
|
||||
@@ -145,6 +151,7 @@ class login implements renderable, templatable {
|
||||
$data->signupurl = $this->signupurl->out(false);
|
||||
$data->username = $this->username;
|
||||
$data->logintoken = $this->logintoken;
|
||||
$data->maintenance = format_text($this->maintenance, FORMAT_MOODLE);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ use core\oauth2\client;
|
||||
|
||||
require_once($CFG->libdir.'/authlib.php');
|
||||
require_once($CFG->dirroot.'/user/lib.php');
|
||||
require_once($CFG->dirroot.'/user/profile/lib.php');
|
||||
|
||||
/**
|
||||
* Plugin for oauth2 authentication.
|
||||
|
||||
@@ -92,3 +92,25 @@ Feature: availability_grouping
|
||||
# P1 should show but not B2.
|
||||
Then I should see "P1" in the "region-main" "region"
|
||||
And I should not see "P2" in the "region-main" "region"
|
||||
|
||||
@javascript
|
||||
Scenario: Check grouping access restriction message on course homepage
|
||||
Given the following "groupings" exist:
|
||||
| name | course | idnumber |
|
||||
| Grouping A | C1 | GA |
|
||||
And the following "grouping groups" exist:
|
||||
| grouping | group |
|
||||
| GA | GI1 |
|
||||
And the following "activities" exist:
|
||||
| activity | name | intro | course | idnumber | groupmode | grouping |
|
||||
| assign | Test assign | Assign description | C1 | assign1 | 1 | GA |
|
||||
And I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage
|
||||
And I turn editing mode on
|
||||
And I open "Test assign" actions menu
|
||||
And I choose "Edit settings" in the open action menu
|
||||
And I expand all fieldsets
|
||||
And the field "groupingid" matches value "Grouping A"
|
||||
And I press "Add group/grouping access restriction"
|
||||
When I press "Save and return to course"
|
||||
Then I should see "Not available unless: You belong to a group in Grouping A"
|
||||
|
||||
@@ -332,6 +332,21 @@ class restore_controller extends base_controller {
|
||||
public function get_plan() {
|
||||
return $this->plan;
|
||||
}
|
||||
/**
|
||||
* Gets the value for the requested setting
|
||||
*
|
||||
* @param string $name
|
||||
* @param bool $default
|
||||
* @return mixed
|
||||
*/
|
||||
public function get_setting_value($name, $default = false) {
|
||||
try {
|
||||
return $this->get_plan()->get_setting($name)->get_value();
|
||||
} catch (Exception $e) {
|
||||
debugging('Failed to find the setting: '.$name, DEBUG_DEVELOPER);
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
|
||||
public function get_info() {
|
||||
return $this->info;
|
||||
@@ -341,6 +356,14 @@ class restore_controller extends base_controller {
|
||||
// Basic/initial prevention against time/memory limits
|
||||
core_php_time_limit::raise(1 * 60 * 60); // 1 hour for 1 course initially granted
|
||||
raise_memory_limit(MEMORY_EXTRA);
|
||||
|
||||
// Do course cleanup precheck, if required. This was originally in restore_ui. Moved to handle async backup/restore.
|
||||
if ($this->get_target() == backup::TARGET_CURRENT_DELETING || $this->get_target() == backup::TARGET_EXISTING_DELETING) {
|
||||
$options = array();
|
||||
$options['keep_roles_and_enrolments'] = $this->get_setting_value('keep_roles_and_enrolments');
|
||||
$options['keep_groups_and_groupings'] = $this->get_setting_value('keep_groups_and_groupings');
|
||||
restore_dbops::delete_course_content($this->get_courseid(), $options);
|
||||
}
|
||||
// If this is not a course restore or single activity restore (e.g. duplicate), inform the plan we are not
|
||||
// including all the activities for sure. This will affect any
|
||||
// task/step executed conditionally to stop processing information
|
||||
|
||||
@@ -71,6 +71,9 @@ class core_backup_external extends external_api {
|
||||
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
|
||||
require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
|
||||
|
||||
// Release session lock.
|
||||
\core\session\manager::write_close();
|
||||
|
||||
// Parameter validation.
|
||||
self::validate_parameters(
|
||||
self::get_async_backup_progress_parameters(),
|
||||
@@ -142,6 +145,9 @@ class core_backup_external extends external_api {
|
||||
* @since Moodle 3.7
|
||||
*/
|
||||
public static function get_async_backup_links_backup($filename, $contextid) {
|
||||
// Release session lock.
|
||||
\core\session\manager::write_close();
|
||||
|
||||
// Parameter validation.
|
||||
self::validate_parameters(
|
||||
self::get_async_backup_links_backup_parameters(),
|
||||
@@ -206,6 +212,9 @@ class core_backup_external extends external_api {
|
||||
* @since Moodle 3.7
|
||||
*/
|
||||
public static function get_async_backup_links_restore($backupid, $contextid) {
|
||||
// Release session lock.
|
||||
\core\session\manager::write_close();
|
||||
|
||||
// Parameter validation.
|
||||
self::validate_parameters(
|
||||
self::get_async_backup_links_restore_parameters(),
|
||||
|
||||
@@ -128,15 +128,6 @@ abstract class backup_check {
|
||||
// Now, if backup mode is hub or import, check userid has permissions for those modes
|
||||
// other modes will perform common checks only (backupxxxx capabilities in $typecapstocheck)
|
||||
switch ($mode) {
|
||||
case backup::MODE_HUB:
|
||||
if (!has_capability('moodle/backup:backuptargethub', $coursectx, $userid)) {
|
||||
$a = new stdclass();
|
||||
$a->userid = $userid;
|
||||
$a->courseid = $courseid;
|
||||
$a->capability = 'moodle/backup:backuptargethub';
|
||||
throw new backup_controller_exception('backup_user_missing_capability', $a);
|
||||
}
|
||||
break;
|
||||
case backup::MODE_IMPORT:
|
||||
if (!has_capability('moodle/backup:backuptargetimport', $coursectx, $userid)) {
|
||||
$a = new stdclass();
|
||||
|
||||
@@ -90,15 +90,6 @@ abstract class restore_check {
|
||||
// Now, if restore mode is hub or import, check userid has permissions for those modes
|
||||
// other modes will perform common checks only (restorexxxx capabilities in $typecapstocheck)
|
||||
switch ($mode) {
|
||||
case backup::MODE_HUB:
|
||||
if (!has_capability('moodle/restore:restoretargethub', $coursectx, $userid)) {
|
||||
$a = new stdclass();
|
||||
$a->userid = $userid;
|
||||
$a->courseid = $courseid;
|
||||
$a->capability = 'moodle/restore:restoretargethub';
|
||||
throw new restore_controller_exception('restore_user_missing_capability', $a);
|
||||
}
|
||||
break;
|
||||
case backup::MODE_IMPORT:
|
||||
if (!has_capability('moodle/restore:restoretargetimport', $coursectx, $userid)) {
|
||||
$a = new stdclass();
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
define(["jquery","core/ajax","core/str","core/notification","core/templates"],function(a,b,c,d,e){function f(b,c){var d=Math.round(c)+"%",e=a("#"+b+"_bar"),f=c.toFixed(2)+"%";e.attr("aria-valuenow",d),e.css("width",d),e.text(f)}function g(c){var f=a("#"+c+"_bar").parent().parent(),g=f.parent(),h=f.siblings(),i=h[1],j=a(i).text(),k=h[0],l=a(k).text();b.call([{methodname:"core_backup_get_async_backup_links_backup",args:{filename:l,contextid:n}}])[0].done(function(a){var b={filename:l,time:j,size:a.filesize,fileurl:a.fileurl,restoreurl:a.restoreurl};e.render("core/async_backup_progress_row",b).then(function(a,b){e.replaceNodeContents(g,a,b)}).fail(function(){d.exception(new Error("Failed to load table row"))})})}function h(c){var f=a("#"+c+"_bar").parent().parent(),g=f.parent(),h=f.siblings(),i=h[0],j=h[1],k=a(j).text();b.call([{methodname:"core_backup_get_async_backup_links_restore",args:{backupid:c,contextid:n}}])[0].done(function(b){var c=a(i).text(),f={resourcename:c,restoreurl:b.restoreurl,time:k};e.render("core/async_restore_progress_row",f).then(function(a,b){e.replaceNodeContents(g,a,b)}).fail(function(){d.exception(new Error("Failed to load table row"))})})}function i(e){var g,h=100*e.progress,i=a("#"+m+"_bar"),j=a("#"+m+"_status"),k=a("#"+m+"_detail"),l=a("#"+m+"_button");if(e.status==s){i.addClass("bg-success"),f(m,h);var r="async"+p+"processing";c.get_string(r,"backup").then(function(a){return j.text(a),a})["catch"](function(){d.exception(new Error("Failed to load string: backup "+r))})}else if(e.status==t){i.addClass("bg-danger"),i.removeClass("bg-success"),f(m,100);var v="async"+p+"error",w="async"+p+"errordetail";g=[{key:v,component:"backup"},{key:w,component:"backup"}],c.get_strings(g).then(function(a){return j.text(a[0]),k.text(a[1]),a})["catch"](function(){d.exception(new Error("Failed to load string"))}),a(".backup_progress").children("span").removeClass("backup_stage_current"),a(".backup_progress").children("span").last().addClass("backup_stage_current"),clearInterval(q)}else if(e.status==u){i.addClass("bg-success"),f(m,100);var x="async"+p+"complete";if(c.get_string(x,"backup").then(function(a){return j.text(a),a})["catch"](function(){d.exception(new Error("Failed to load string: backup "+x))}),"restore"==p)b.call([{methodname:"core_backup_get_async_backup_links_restore",args:{backupid:m,contextid:n}}])[0].done(function(a){var b="async"+p+"completedetail",e="async"+p+"completebutton",f=[{key:b,component:"backup",param:a.restoreurl},{key:e,component:"backup"}];c.get_strings(f).then(function(b){return k.html(b[0]),l.text(b[1]),l.attr("href",a.restoreurl),b})["catch"](function(){d.exception(new Error("Failed to load string"))})});else{var y="async"+p+"completedetail",z="async"+p+"completebutton";g=[{key:y,component:"backup",param:o},{key:z,component:"backup"}],c.get_strings(g).then(function(a){return k.html(a[0]),l.text(a[1]),l.attr("href",o),a})["catch"](function(){d.exception(new Error("Failed to load string"))})}a(".backup_progress").children("span").removeClass("backup_stage_current"),a(".backup_progress").children("span").last().addClass("backup_stage_current"),clearInterval(q)}}function j(b){b.forEach(function(b){var c=100*b.progress,d=b.backupid,e=a("#"+d+"_bar"),i=b.operation;b.status==s?(e.addClass("bg-success"),f(d,c)):b.status==t?(e.addClass("bg-danger"),e.addClass("complete"),a("#"+d+"_bar").removeClass("bg-success"),f(d,100)):b.status==u&&(e.addClass("bg-success"),e.addClass("complete"),f(d,100),"backup"==i?g(d):h(d))})}function k(){b.call([{methodname:"core_backup_get_async_backup_progress",args:{backupids:[m],contextid:n}}])[0].done(function(a){i(a[0])})}function l(){var c=[],d=a(".progress").find(".progress-bar").not(".complete");d.each(function(){c.push(this.id.substring(0,32))}),c.length>0?b.call([{methodname:"core_backup_get_async_backup_progress",args:{backupids:c,contextid:n}}])[0].done(function(a){j(a)}):clearInterval(r)}var m,n,o,p,q,r,s=800,t=900,u=1e3,v={},w=5e3;return v.asyncBackupAllStatus=function(a){n=a,r=setInterval(l,w)},v.asyncBackupStatus=function(b,c,d,e){m=b,n=c,o=d,p="backup"==e?"backup":"restore",a(".backup_progress").children("a").removeAttr("href"),q=setInterval(k,w)},v});
|
||||
define(["jquery","core/ajax","core/str","core/notification","core/templates"],function(a,b,c,d,e){function f(b,c){var d=Math.round(c)+"%",e=a("#"+b+"_bar"),f=c.toFixed(2)+"%";e.attr("aria-valuenow",d),e.css("width",d),e.text(f)}function g(c){var f=a("#"+c+"_bar").parent().parent(),g=f.parent(),h=f.siblings(),i=h[1],j=a(i).text(),k=h[0],l=a(k).text();b.call([{methodname:"core_backup_get_async_backup_links_backup",args:{filename:l,contextid:n}}])[0].done(function(a){var b={filename:l,time:j,size:a.filesize,fileurl:a.fileurl,restoreurl:a.restoreurl};e.render("core/async_backup_progress_row",b).then(function(a,b){e.replaceNodeContents(g,a,b)}).fail(function(){d.exception(new Error("Failed to load table row"))})})}function h(c){var f=a("#"+c+"_bar").parent().parent(),g=f.parent(),h=f.siblings(),i=h[0],j=h[1],k=a(j).text();b.call([{methodname:"core_backup_get_async_backup_links_restore",args:{backupid:c,contextid:n}}])[0].done(function(b){var c=a(i).text(),f={resourcename:c,restoreurl:b.restoreurl,time:k};e.render("core/async_restore_progress_row",f).then(function(a,b){e.replaceNodeContents(g,a,b)}).fail(function(){d.exception(new Error("Failed to load table row"))})})}function i(e){var g,h=100*e.progress,i=a("#"+m+"_bar"),j=a("#"+m+"_status"),k=a("#"+m+"_detail"),l=a("#"+m+"_button");if(e.status==s){i.addClass("bg-success"),f(m,h);var r="async"+p+"processing";c.get_string(r,"backup").then(function(a){return j.text(a),a})["catch"](function(){d.exception(new Error("Failed to load string: backup "+r))})}else if(e.status==t){i.addClass("bg-danger"),i.removeClass("bg-success"),f(m,100);var v="async"+p+"error",w="async"+p+"errordetail";g=[{key:v,component:"backup"},{key:w,component:"backup"}],c.get_strings(g).then(function(a){return j.text(a[0]),k.text(a[1]),a})["catch"](function(){d.exception(new Error("Failed to load string"))}),a(".backup_progress").children("span").removeClass("backup_stage_current"),a(".backup_progress").children("span").last().addClass("backup_stage_current"),clearInterval(q)}else if(e.status==u){i.addClass("bg-success"),f(m,100);var x="async"+p+"complete";if(c.get_string(x,"backup").then(function(a){return j.text(a),a})["catch"](function(){d.exception(new Error("Failed to load string: backup "+x))}),"restore"==p)b.call([{methodname:"core_backup_get_async_backup_links_restore",args:{backupid:m,contextid:n}}])[0].done(function(a){var b="async"+p+"completedetail",e="async"+p+"completebutton",f=[{key:b,component:"backup",param:a.restoreurl},{key:e,component:"backup"}];c.get_strings(f).then(function(b){return k.html(b[0]),l.text(b[1]),l.attr("href",a.restoreurl),b})["catch"](function(){d.exception(new Error("Failed to load string"))})});else{var y="async"+p+"completedetail",z="async"+p+"completebutton";g=[{key:y,component:"backup",param:o},{key:z,component:"backup"}],c.get_strings(g).then(function(a){return k.html(a[0]),l.text(a[1]),l.attr("href",o),a})["catch"](function(){d.exception(new Error("Failed to load string"))})}a(".backup_progress").children("span").removeClass("backup_stage_current"),a(".backup_progress").children("span").last().addClass("backup_stage_current"),clearInterval(q)}}function j(b){b.forEach(function(b){var c=100*b.progress,d=b.backupid,e=a("#"+d+"_bar"),i=b.operation;b.status==s?(e.addClass("bg-success"),f(d,c)):b.status==t?(e.addClass("bg-danger"),e.addClass("complete"),a("#"+d+"_bar").removeClass("bg-success"),f(d,100)):b.status==u&&(e.addClass("bg-success"),e.addClass("complete"),f(d,100),"backup"==i?g(d):h(d))})}function k(){b.call([{methodname:"core_backup_get_async_backup_progress",args:{backupids:[m],contextid:n}}])[0].done(function(a){i(a[0])})}function l(){var c=[],d=a(".progress").find(".progress-bar").not(".complete");d.each(function(){c.push(this.id.substring(0,32))}),c.length>0?b.call([{methodname:"core_backup_get_async_backup_progress",args:{backupids:c,contextid:n}}])[0].done(function(a){j(a)}):clearInterval(r)}var m,n,o,p,q,r,s=800,t=900,u=1e3,v={},w=15e3;return v.asyncBackupAllStatus=function(a){n=a,r=setInterval(l,w)},v.asyncBackupStatus=function(b,c,d,e){m=b,n=c,o=d,p="backup"==e?"backup":"restore",a(".backup_progress").children("a").removeAttr("href"),q=setInterval(k,w)},v});
|
||||
@@ -39,7 +39,7 @@ define(['jquery', 'core/ajax', 'core/str', 'core/notification', 'core/templates'
|
||||
* Module level variables.
|
||||
*/
|
||||
var Asyncbackup = {};
|
||||
var checkdelay = 5000; // How often we check for progress updates.
|
||||
var checkdelay = 15000; // How often we check for progress updates.
|
||||
var backupid; // The backup id to get the progress for.
|
||||
var contextid; // The course this backup progress is for.
|
||||
var restoreurl; // The URL to view course restores.
|
||||
|
||||
@@ -381,8 +381,10 @@ class core_backup_renderer extends plugin_renderer_base {
|
||||
$html .= $this->output->heading(get_string('importdatafrom'), 2, array('class' => 'header'));
|
||||
$html .= $this->backup_detail_pair(get_string('selectacourse', 'backup'), $this->render($courses));
|
||||
$attrs = array('type' => 'submit', 'value' => get_string('continue'), 'class' => 'btn btn-primary');
|
||||
$html .= html_writer::start_tag('div', array('class' => 'mt-3'));
|
||||
$html .= $this->backup_detail_pair('', html_writer::empty_tag('input', $attrs));
|
||||
$html .= html_writer::end_tag('div');
|
||||
$html .= html_writer::end_tag('div');
|
||||
$html .= html_writer::end_tag('form');
|
||||
$html .= html_writer::end_tag('div');
|
||||
return $html;
|
||||
@@ -764,7 +766,7 @@ class core_backup_renderer extends plugin_renderer_base {
|
||||
if ($component->get_count() === 0) {
|
||||
$output .= $this->output->notification(get_string('nomatchingcourses', 'backup'));
|
||||
|
||||
$output .= html_writer::start_tag('div', array('class' => 'ics-search'));
|
||||
$output .= html_writer::start_tag('div', array('class' => 'ics-search form-inline'));
|
||||
$attrs = array(
|
||||
'type' => 'text',
|
||||
'name' => restore_course_search::$VAR_SEARCH,
|
||||
@@ -776,7 +778,7 @@ class core_backup_renderer extends plugin_renderer_base {
|
||||
'type' => 'submit',
|
||||
'name' => 'searchcourses',
|
||||
'value' => get_string('search'),
|
||||
'class' => 'btn btn-secondary'
|
||||
'class' => 'btn btn-secondary ml-1'
|
||||
);
|
||||
$output .= html_writer::empty_tag('input', $attrs);
|
||||
$output .= html_writer::end_tag('div');
|
||||
@@ -822,7 +824,7 @@ class core_backup_renderer extends plugin_renderer_base {
|
||||
$output .= html_writer::table($table);
|
||||
$output .= html_writer::end_tag('div');
|
||||
|
||||
$output .= html_writer::start_tag('div', array('class' => 'ics-search'));
|
||||
$output .= html_writer::start_tag('div', array('class' => 'ics-search form-inline'));
|
||||
$attrs = array(
|
||||
'type' => 'text',
|
||||
'name' => restore_course_search::$VAR_SEARCH,
|
||||
@@ -833,7 +835,7 @@ class core_backup_renderer extends plugin_renderer_base {
|
||||
'type' => 'submit',
|
||||
'name' => 'searchcourses',
|
||||
'value' => get_string('search'),
|
||||
'class' => 'btn btn-secondary'
|
||||
'class' => 'btn btn-secondary ml-1'
|
||||
);
|
||||
$output .= html_writer::empty_tag('input', $attrs);
|
||||
$output .= html_writer::end_tag('div');
|
||||
|
||||
@@ -215,12 +215,7 @@ class restore_ui extends base_ui {
|
||||
if ($this->stage->get_stage() < self::STAGE_PROCESS) {
|
||||
throw new restore_ui_exception('restoreuifinalisedbeforeexecute');
|
||||
}
|
||||
if ($this->controller->get_target() == backup::TARGET_CURRENT_DELETING || $this->controller->get_target() == backup::TARGET_EXISTING_DELETING) {
|
||||
$options = array();
|
||||
$options['keep_roles_and_enrolments'] = $this->get_setting_value('keep_roles_and_enrolments');
|
||||
$options['keep_groups_and_groupings'] = $this->get_setting_value('keep_groups_and_groupings');
|
||||
restore_dbops::delete_course_content($this->controller->get_courseid(), $options);
|
||||
}
|
||||
|
||||
$this->controller->execute_plan();
|
||||
$this->progress = self::PROGRESS_EXECUTED;
|
||||
$this->stage = new restore_ui_stage_complete($this, $this->stage->get_params(), $this->controller->get_results());
|
||||
|
||||
@@ -112,7 +112,7 @@ class award_criteria_badge extends award_criteria {
|
||||
if ($this->id !== 0) {
|
||||
$selected = array_keys($this->params);
|
||||
}
|
||||
$settings = array('multiple' => 'multiple', 'size' => 20, 'class' => 'selectbadge');
|
||||
$settings = array('multiple' => 'multiple', 'size' => 20, 'class' => 'selectbadge', 'required' => 'required');
|
||||
$mform->addElement('select', 'badge_badges', get_string('addbadge', 'badges'), $select, $settings);
|
||||
$mform->addRule('badge_badges', get_string('requiredbadge', 'badges'), 'required');
|
||||
$mform->addHelpButton('badge_badges', 'addbadge', 'badges');
|
||||
@@ -243,7 +243,6 @@ class award_criteria_badge extends award_criteria {
|
||||
if ($this->method == BADGE_CRITERIA_AGGREGATION_ANY) {
|
||||
// User has received ANY of the required badges.
|
||||
$join = " LEFT JOIN {badge_issued} bi2 ON bi2.userid = u.id";
|
||||
$where = "AND (";
|
||||
$i = 0;
|
||||
foreach ($this->params as $param) {
|
||||
if ($i == 0) {
|
||||
@@ -254,7 +253,10 @@ class award_criteria_badge extends award_criteria {
|
||||
$params['badgeid'.$i] = $param['badge'];
|
||||
$i++;
|
||||
}
|
||||
$where .= ") ";
|
||||
// MDL-66032 Do not create expression if there are no badges in criteria.
|
||||
if (!empty($where)) {
|
||||
$where = ' AND (' . $where . ') ';
|
||||
}
|
||||
return array($join, $where, $params);
|
||||
} else {
|
||||
// User has received ALL of the required badges.
|
||||
|
||||
@@ -48,54 +48,11 @@ class block_community extends block_list {
|
||||
}
|
||||
|
||||
function get_content() {
|
||||
global $CFG, $OUTPUT, $USER;
|
||||
|
||||
$coursecontext = context::instance_by_id($this->instance->parentcontextid);
|
||||
|
||||
if (!has_capability('moodle/community:add', $coursecontext)
|
||||
or $this->content !== NULL) {
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
$this->content = new stdClass();
|
||||
$this->content->items = array();
|
||||
$this->content->icons = array();
|
||||
$this->content->footer = '';
|
||||
|
||||
if (!isloggedin()) {
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
$icon = $OUTPUT->pix_icon('i/group', get_string('group'));
|
||||
$addcourseurl = new moodle_url('/blocks/community/communitycourse.php',
|
||||
array('add' => true, 'courseid' => $this->page->course->id));
|
||||
$searchlink = html_writer::tag('a', $icon . get_string('addcourse', 'block_community'),
|
||||
array('href' => $addcourseurl->out(false)));
|
||||
$this->content->items[] = $searchlink;
|
||||
|
||||
require_once($CFG->dirroot . '/blocks/community/locallib.php');
|
||||
$communitymanager = new block_community_manager();
|
||||
$courses = $communitymanager->block_community_get_courses($USER->id);
|
||||
if ($courses) {
|
||||
$this->content->items[] = html_writer::empty_tag('hr');
|
||||
$this->content->icons[] = '';
|
||||
$this->content->items[] = get_string('mycommunities', 'block_community');
|
||||
$this->content->icons[] = '';
|
||||
foreach ($courses as $course) {
|
||||
//delete link
|
||||
$deleteicon = $OUTPUT->pix_icon('t/delete', get_string('removecommunitycourse', 'block_community'));
|
||||
$deleteurl = new moodle_url('/blocks/community/communitycourse.php',
|
||||
array('remove' => true,
|
||||
'courseid' => $this->page->course->id,
|
||||
'communityid' => $course->id, 'sesskey' => sesskey()));
|
||||
$deleteatag = html_writer::tag('a', $deleteicon, array('href' => $deleteurl));
|
||||
|
||||
$courselink = html_writer::tag('a', $course->coursename,
|
||||
array('href' => $course->courseurl));
|
||||
$this->content->items[] = $courselink . ' ' . $deleteatag;
|
||||
$this->content->icons[] = '';
|
||||
}
|
||||
}
|
||||
$this->content->items[] = get_string('functionalityremoved', 'error');
|
||||
|
||||
return $this->content;
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ $string['enroldownload_help'] = 'Some courses listed in the selected hub are bei
|
||||
Others are course templates provided for you to download and use on your own Moodle site.';
|
||||
$string['enrollable'] = 'courses I can enrol in';
|
||||
$string['enrollablecourses'] = 'Enrollable courses';
|
||||
$string['errorcourselisting'] = 'An error occurred when retrieving the course listing from the selected hub, please try again later. ({$a})';
|
||||
$string['errorcourselisting'] = 'Please note: It is no longer possible to search for community courses on moodle.net. Previously shared courses for download are now available on archive.moodle.net. See <a href="https://docs.moodle.org/en/Sunsetting_moodle.net">Sunsetting moodle.net</a> for further details.';
|
||||
$string['errorhublisting'] = 'An error occurred when retrieving the hub listing from Moodle.org, please try again later. ({$a})';
|
||||
$string['fileinfo'] = 'Language: {$a->lang} - License: {$a->license} - Time updated: {$a->timeupdated}';
|
||||
$string['hideall'] = 'Hide hubs';
|
||||
|
||||
@@ -50,8 +50,22 @@ function xmldb_block_recentlyaccesseditems_upgrade($oldversion, $block) {
|
||||
// Automatically generated Moodle v3.7.0 release upgrade line.
|
||||
// Put any upgrade step following this.
|
||||
if ($oldversion < 2019052001) {
|
||||
$sql = "courseid NOT IN (SELECT c.id from {course} c) OR cmid NOT IN (SELECT cm.id from {course_modules} cm)";
|
||||
$DB->delete_records_select("block_recentlyaccesseditems", $sql);
|
||||
// Query the items to be deleted as a list of IDs. We cannot delete directly from this as a
|
||||
// subquery because MySQL does not support delete with subqueries.
|
||||
$fordeletion = $DB->get_fieldset_sql("
|
||||
SELECT rai.id
|
||||
FROM {block_recentlyaccesseditems} rai
|
||||
LEFT JOIN {course} c ON c.id = rai.courseid
|
||||
LEFT JOIN {course_modules} cm ON cm.id = rai.cmid
|
||||
WHERE c.id IS NULL OR cm.id IS NULL");
|
||||
|
||||
// Delete the array in chunks of 500 (Oracle does not support more than 1000 parameters,
|
||||
// let's leave some leeway, there are likely only one chunk anyway).
|
||||
$chunks = array_chunk($fordeletion, 500);
|
||||
foreach ($chunks as $chunk) {
|
||||
$DB->delete_records_list('block_recentlyaccesseditems', 'id', $chunk);
|
||||
}
|
||||
|
||||
upgrade_block_savepoint(true, 2019052001, 'recentlyaccesseditems', false);
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -83,7 +83,7 @@ function blog_rss_print_link($context, $filtertype, $filterselect = 0, $tagid =
|
||||
|
||||
$url = blog_rss_get_url($context->id, $userid, $filtertype, $filterselect, $tagid);
|
||||
$rsspix = $OUTPUT->pix_icon('i/rss', get_string('rss'), 'core', array('title' => $tooltiptext));
|
||||
print '<div class="pull-xs-right"><a href="'. $url .'">' . $rsspix . '</a></div>';
|
||||
print '<div class="float-sm-right"><a href="'. $url .'">' . $rsspix . '</a></div>';
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Vendored
+202
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
+7
-6
@@ -1,14 +1,15 @@
|
||||
MongoDB PHP
|
||||
-----------
|
||||
|
||||
Downloaded from https://github.com/mongodb/mongo-php-library
|
||||
|
||||
Last commit on download: aac8e54009196f6544e50baf9b63dcf0eab3bbdf
|
||||
|
||||
This version (1.4) requires PHP mongodb extension >= 1.5
|
||||
Download from https://github.com/mongodb/mongo-php-library
|
||||
|
||||
Import procedure:
|
||||
|
||||
- Copy all the files and folders from the folder mongodb/src in this directory.
|
||||
- Copy the license file from the project root.
|
||||
- Update thirdpartylibs.xml with the latest version.
|
||||
|
||||
2019/03/14
|
||||
----------
|
||||
Last commit on download: aac8e54009196f6544e50baf9b63dcf0eab3bbdf
|
||||
|
||||
This version (1.4.2) requires PHP mongodb extension >= 1.5
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
<location>MongoDB</location>
|
||||
<name>MongoDB PHP Library</name>
|
||||
<license>Apache</license>
|
||||
<version>1.4</version>
|
||||
<version>1.4.2</version>
|
||||
<licenseversion>2.0</licenseversion>
|
||||
</library>
|
||||
</libraries>
|
||||
Vendored
+2
-1
@@ -150,7 +150,8 @@ class cachestore_redis extends cache_store implements cache_is_key_aware, cache_
|
||||
*/
|
||||
protected function new_redis($server, $prefix = '', $password = '') {
|
||||
$redis = new Redis();
|
||||
$port = null;
|
||||
// Check if it isn't a Unix socket to set default port.
|
||||
$port = ($server[0] === '/') ? null : 6379;
|
||||
if (strpos($server, ':')) {
|
||||
$serverconf = explode(':', $server);
|
||||
$server = $serverconf[0];
|
||||
|
||||
@@ -138,7 +138,9 @@ abstract class event_abstract_factory implements event_factory_interface {
|
||||
$module = new cm_info_proxy($dbrow->modulename, $dbrow->instance, $dbrow->courseid);
|
||||
}
|
||||
|
||||
$category = new coursecat_proxy($dbrow->categoryid);
|
||||
if ($dbrow->categoryid) {
|
||||
$category = new coursecat_proxy($dbrow->categoryid);
|
||||
}
|
||||
|
||||
$course = new std_proxy($dbrow->courseid, function($id) {
|
||||
return calendar_get_course_cached($this->coursecachereference, $id);
|
||||
|
||||
@@ -95,6 +95,7 @@ class event_mapper implements event_mapper_interface {
|
||||
|
||||
// Normalise for the legacy event because it wants zero rather than null.
|
||||
$properties->courseid = empty($properties->courseid) ? 0 : $properties->courseid;
|
||||
$properties->categoryid = empty($properties->categoryid) ? 0 : $properties->categoryid;
|
||||
$properties->groupid = empty($properties->groupid) ? 0 : $properties->groupid;
|
||||
$properties->userid = empty($properties->userid) ? 0 : $properties->userid;
|
||||
$properties->modulename = empty($properties->modulename) ? 0 : $properties->modulename;
|
||||
|
||||
@@ -676,7 +676,7 @@ class core_calendar_external extends external_api {
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete Calendar events.
|
||||
* Create calendar events.
|
||||
*
|
||||
* @param array $events A list of events to create.
|
||||
* @return array array of events created.
|
||||
@@ -1026,8 +1026,8 @@ class core_calendar_external extends external_api {
|
||||
public static function get_calendar_monthly_view_parameters() {
|
||||
return new external_function_parameters(
|
||||
[
|
||||
'year' => new external_value(PARAM_INT, 'Month to be viewed', VALUE_REQUIRED),
|
||||
'month' => new external_value(PARAM_INT, 'Year to be viewed', VALUE_REQUIRED),
|
||||
'year' => new external_value(PARAM_INT, 'Year to be viewed', VALUE_REQUIRED),
|
||||
'month' => new external_value(PARAM_INT, 'Month to be viewed', VALUE_REQUIRED),
|
||||
'courseid' => new external_value(PARAM_INT, 'Course being viewed', VALUE_DEFAULT, SITEID, NULL_ALLOWED),
|
||||
'categoryid' => new external_value(PARAM_INT, 'Category being viewed', VALUE_DEFAULT, null, NULL_ALLOWED),
|
||||
'includenavigation' => new external_value(
|
||||
|
||||
+42
-17
@@ -104,6 +104,11 @@ define('CALENDAR_IMPORT_FROM_FILE', 0);
|
||||
*/
|
||||
define('CALENDAR_IMPORT_FROM_URL', 1);
|
||||
|
||||
/**
|
||||
* CALENDAR_IMPORT_EVENT_UPDATED_SKIPPED - imported event was skipped
|
||||
*/
|
||||
define('CALENDAR_IMPORT_EVENT_SKIPPED', -1);
|
||||
|
||||
/**
|
||||
* CALENDAR_IMPORT_EVENT_UPDATED - imported event was updated
|
||||
*/
|
||||
@@ -2865,11 +2870,23 @@ function calendar_add_icalendar_event($event, $unused = null, $subscriptionid, $
|
||||
|
||||
if ($updaterecord = $DB->get_record('event', array('uuid' => $eventrecord->uuid,
|
||||
'subscriptionid' => $eventrecord->subscriptionid))) {
|
||||
$eventrecord->id = $updaterecord->id;
|
||||
$return = CALENDAR_IMPORT_EVENT_UPDATED; // Update.
|
||||
|
||||
// Compare iCal event data against the moodle event to see if something has changed.
|
||||
$result = array_diff((array) $eventrecord, (array) $updaterecord);
|
||||
|
||||
// Unset timemodified field because it's always going to be different.
|
||||
unset($result['timemodified']);
|
||||
|
||||
if (count($result)) {
|
||||
$eventrecord->id = $updaterecord->id;
|
||||
$return = CALENDAR_IMPORT_EVENT_UPDATED; // Update.
|
||||
} else {
|
||||
return CALENDAR_IMPORT_EVENT_SKIPPED;
|
||||
}
|
||||
} else {
|
||||
$return = CALENDAR_IMPORT_EVENT_INSERTED; // Insert.
|
||||
}
|
||||
|
||||
if ($createdevent = \calendar_event::create($eventrecord, false)) {
|
||||
if (!empty($event->properties['RRULE'])) {
|
||||
// Repeating events.
|
||||
@@ -3003,18 +3020,13 @@ function calendar_import_icalendar_events($ical, $unused = null, $subscriptionid
|
||||
$return = '';
|
||||
$eventcount = 0;
|
||||
$updatecount = 0;
|
||||
$skippedcount = 0;
|
||||
|
||||
// Large calendars take a while...
|
||||
if (!CLI_SCRIPT) {
|
||||
\core_php_time_limit::raise(300);
|
||||
}
|
||||
|
||||
// Mark all events in a subscription with a zero timestamp.
|
||||
if (!empty($subscriptionid)) {
|
||||
$sql = "UPDATE {event} SET timemodified = :time WHERE subscriptionid = :id";
|
||||
$DB->execute($sql, array('time' => 0, 'id' => $subscriptionid));
|
||||
}
|
||||
|
||||
// Grab the timezone from the iCalendar file to be used later.
|
||||
if (isset($ical->properties['X-WR-TIMEZONE'][0]->value)) {
|
||||
$timezone = $ical->properties['X-WR-TIMEZONE'][0]->value;
|
||||
@@ -3022,8 +3034,9 @@ function calendar_import_icalendar_events($ical, $unused = null, $subscriptionid
|
||||
$timezone = 'UTC';
|
||||
}
|
||||
|
||||
$return = '';
|
||||
$icaluuids = [];
|
||||
foreach ($ical->components['VEVENT'] as $event) {
|
||||
$icaluuids[] = $event->properties['UID'][0]->value;
|
||||
$res = calendar_add_icalendar_event($event, null, $subscriptionid, $timezone);
|
||||
switch ($res) {
|
||||
case CALENDAR_IMPORT_EVENT_UPDATED:
|
||||
@@ -3032,6 +3045,9 @@ function calendar_import_icalendar_events($ical, $unused = null, $subscriptionid
|
||||
case CALENDAR_IMPORT_EVENT_INSERTED:
|
||||
$eventcount++;
|
||||
break;
|
||||
case CALENDAR_IMPORT_EVENT_SKIPPED:
|
||||
$skippedcount++;
|
||||
break;
|
||||
case 0:
|
||||
$return .= '<p>' . get_string('erroraddingevent', 'calendar') . ': ';
|
||||
if (empty($event->properties['SUMMARY'])) {
|
||||
@@ -3044,18 +3060,27 @@ function calendar_import_icalendar_events($ical, $unused = null, $subscriptionid
|
||||
}
|
||||
}
|
||||
|
||||
$return .= "<p>" . get_string('eventsimported', 'calendar', $eventcount) . "</p> ";
|
||||
$return .= "<p>" . get_string('eventsupdated', 'calendar', $updatecount) . "</p>";
|
||||
|
||||
// Delete remaining zero-marked events since they're not in remote calendar.
|
||||
if (!empty($subscriptionid)) {
|
||||
$deletecount = $DB->count_records('event', array('timemodified' => 0, 'subscriptionid' => $subscriptionid));
|
||||
if (!empty($deletecount)) {
|
||||
$DB->delete_records('event', array('timemodified' => 0, 'subscriptionid' => $subscriptionid));
|
||||
$return .= "<p> " . get_string('eventsdeleted', 'calendar') . ": {$deletecount} </p>\n";
|
||||
$eventsuuids = $DB->get_records_menu('event', ['subscriptionid' => $subscriptionid], '', 'id, uuid');
|
||||
|
||||
$icaleventscount = count($icaluuids);
|
||||
$tobedeleted = [];
|
||||
if (count($eventsuuids) > $icaleventscount) {
|
||||
foreach ($eventsuuids as $eventid => $eventuuid) {
|
||||
if (!in_array($eventuuid, $icaluuids)) {
|
||||
$tobedeleted[] = $eventid;
|
||||
}
|
||||
}
|
||||
if (!empty($tobedeleted)) {
|
||||
$DB->delete_records_list('event', 'id', $tobedeleted);
|
||||
$return .= "<p> " . get_string('eventsdeleted', 'calendar') . ": " . count($tobedeleted) . "</p> ";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$return .= "<p>" . get_string('eventsimported', 'calendar', $eventcount) . "</p> ";
|
||||
$return .= "<p>" . get_string('eventsskipped', 'calendar', $skippedcount) . "</p> ";
|
||||
$return .= "<p>" . get_string('eventsupdated', 'calendar', $updatecount) . "</p>";
|
||||
return $return;
|
||||
}
|
||||
|
||||
|
||||
@@ -161,7 +161,7 @@ class core_calendar_renderer extends plugin_renderer_base {
|
||||
$deletelink = null;
|
||||
}
|
||||
|
||||
$commands = html_writer::start_tag('div', array('class' => 'commands pull-xs-right'));
|
||||
$commands = html_writer::start_tag('div', array('class' => 'commands float-sm-right'));
|
||||
$commands .= html_writer::start_tag('a', array('href' => $editlink));
|
||||
$str = get_string('tt_editevent', 'calendar');
|
||||
$commands .= $this->output->pix_icon('t/edit', $str);
|
||||
@@ -205,9 +205,9 @@ class core_calendar_renderer extends plugin_renderer_base {
|
||||
$output .= html_writer::tag('div', $event->courselink);
|
||||
}
|
||||
if (!empty($event->time)) {
|
||||
$output .= html_writer::tag('span', $event->time, array('class' => 'date pull-xs-right mr-1'));
|
||||
$output .= html_writer::tag('span', $event->time, array('class' => 'date float-sm-right mr-1'));
|
||||
} else {
|
||||
$attrs = array('class' => 'date pull-xs-right mr-1');
|
||||
$attrs = array('class' => 'date float-sm-right mr-1');
|
||||
$output .= html_writer::tag('span', calendar_time_representation($event->timestart), $attrs);
|
||||
}
|
||||
|
||||
|
||||
@@ -46,5 +46,9 @@
|
||||
{{> core_calendar/header}}
|
||||
{{> core_calendar/day_navigation }}
|
||||
{{> core/overlay_loading}}
|
||||
{{> core_calendar/event_list }}
|
||||
{{< core_calendar/event_list }}
|
||||
{{$noeventsmessage}}
|
||||
{{#str}} daywithnoevents, core_calendar {{/str}}
|
||||
{{/noeventsmessage}}
|
||||
{{/core_calendar/event_list}}
|
||||
</div>
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
{{/events}}
|
||||
{{^events}}
|
||||
<span class="calendar-information calendar-no-results">
|
||||
{{#str}}daywithnoevents, calendar{{/str}}
|
||||
{{$noeventsmessage}}{{/noeventsmessage}}
|
||||
</span>
|
||||
{{/events}}
|
||||
</div>
|
||||
@@ -34,5 +34,9 @@
|
||||
<div class="calendarwrapper" data-view="upcoming" data-context-id="{{defaulteventcontext}}" data-courseid="{{courseid}}">
|
||||
{{> core_calendar/header}}
|
||||
{{> core/overlay_loading}}
|
||||
{{> core_calendar/event_list }}
|
||||
{{< core_calendar/event_list }}
|
||||
{{$noeventsmessage}}
|
||||
{{#str}} noupcomingevents, core_calendar {{/str}}
|
||||
{{/noeventsmessage}}
|
||||
{{/core_calendar/event_list}}
|
||||
</div>
|
||||
|
||||
@@ -1231,7 +1231,7 @@ class api {
|
||||
$result = array();
|
||||
|
||||
// TODO We could improve the performance of this into one single query.
|
||||
$coursemodulecompetencies = course_competency::list_course_module_competencies($cm->id);
|
||||
$coursemodulecompetencies = course_module_competency::list_course_module_competencies($cm->id);
|
||||
$competencies = course_module_competency::list_competencies($cm->id);
|
||||
|
||||
// Build the return values.
|
||||
|
||||
@@ -2757,6 +2757,16 @@ class core_competency_api_testcase extends advanced_testcase {
|
||||
$result = api::list_course_module_competencies_in_course_module($cm->id);
|
||||
$this->assertEquals($result[0]->get('competencyid'), $c->get('id'));
|
||||
$this->assertEquals($result[1]->get('competencyid'), $c2->get('id'));
|
||||
|
||||
// Now get the course competency and coursemodule competency together.
|
||||
$result = api::list_course_module_competencies($cm->id);
|
||||
// Now we should have an array and each element of the array should have a competency and
|
||||
// a coursemodulecompetency.
|
||||
foreach ($result as $instance) {
|
||||
$cmc = $instance['coursemodulecompetency'];
|
||||
$c = $instance['competency'];
|
||||
$this->assertEquals($cmc->get('competencyid'), $c->get('id'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "7.5.*",
|
||||
"phpunit/dbunit": "4.0.*",
|
||||
"moodlehq/behat-extension": "3.37.0",
|
||||
"moodlehq/behat-extension": "3.37.2",
|
||||
"mikey179/vfsstream": "^1.6",
|
||||
"instaclick/php-webdriver": "dev-local as 1.x-dev"
|
||||
}
|
||||
|
||||
Generated
+206
-208
File diff suppressed because it is too large
Load Diff
Vendored
+1
-1
@@ -1 +1 @@
|
||||
define(["jquery","core/ajax"],function(a,b){var c=function(a,c,d,e){var f={classification:a};"undefined"!=typeof c&&(f.limit=c),"undefined"!=typeof d&&(f.offset=d),"undefined"!=typeof e&&(f.sort=e);var g={methodname:"core_course_get_enrolled_courses_by_timeline_classification",args:f};return b.call([g])[0]},d=function(a,c,d,e){var f={};"undefined"!=typeof a&&(f.limit=c),"undefined"!=typeof c&&(f.limit=c),"undefined"!=typeof d&&(f.offset=d),"undefined"!=typeof e&&(f.sort=e);var g={methodname:"core_course_get_recent_courses",args:f};return b.call([g])[0]};return{getEnrolledCoursesByTimelineClassification:c,getLastAccessedCourses:d}});
|
||||
define(["jquery","core/ajax"],function(a,b){var c=function(a,c,d,e){var f={classification:a};"undefined"!=typeof c&&(f.limit=c),"undefined"!=typeof d&&(f.offset=d),"undefined"!=typeof e&&(f.sort=e);var g={methodname:"core_course_get_enrolled_courses_by_timeline_classification",args:f};return b.call([g])[0]},d=function(a,c,d,e){var f={};"undefined"!=typeof a&&(f.userid=a),"undefined"!=typeof c&&(f.limit=c),"undefined"!=typeof d&&(f.offset=d),"undefined"!=typeof e&&(f.sort=e);var g={methodname:"core_course_get_recent_courses",args:f};return b.call([g])[0]};return{getEnrolledCoursesByTimelineClassification:c,getLastAccessedCourses:d}});
|
||||
@@ -71,7 +71,7 @@ define(['jquery', 'core/ajax'], function($, Ajax) {
|
||||
var args = {};
|
||||
|
||||
if (typeof userid !== 'undefined') {
|
||||
args.limit = limit;
|
||||
args.userid = userid;
|
||||
}
|
||||
|
||||
if (typeof limit !== 'undefined') {
|
||||
|
||||
@@ -68,8 +68,10 @@ class activities_due extends \core_analytics\local\indicator\binary {
|
||||
*/
|
||||
protected function calculate_sample($sampleid, $sampleorigin, $starttime = false, $endtime = false) {
|
||||
|
||||
$user = $this->retrieve('user', $sampleid);
|
||||
|
||||
$actionevents = \core_calendar_external::get_calendar_action_events_by_timesort($starttime, $endtime, 0, 1,
|
||||
true, $sampleid);
|
||||
true, $user->id);
|
||||
|
||||
if ($actionevents->events) {
|
||||
|
||||
|
||||
+5
-1
@@ -166,7 +166,11 @@ if ($editform->is_cancelled()) {
|
||||
|
||||
if (!empty($CFG->creatornewroleid) and !is_viewing($context, NULL, 'moodle/role:assign') and !is_enrolled($context, NULL, 'moodle/role:assign')) {
|
||||
// Deal with course creators - enrol them internally with default role.
|
||||
enrol_try_internal_enrol($course->id, $USER->id, $CFG->creatornewroleid);
|
||||
if (user_can_assign($context, $CFG->creatornewroleid)) {
|
||||
enrol_try_internal_enrol($course->id, $USER->id, $CFG->creatornewroleid);
|
||||
} else {
|
||||
enrol_try_internal_enrol($course->id, $USER->id);
|
||||
}
|
||||
}
|
||||
|
||||
// The URL to take them to if they chose save and display.
|
||||
|
||||
@@ -36,6 +36,9 @@ class format_singleactivity extends format_base {
|
||||
/** @var cm_info the current activity. Use get_activity() to retrieve it. */
|
||||
private $activity = false;
|
||||
|
||||
/** @var int The category ID guessed from the form data. */
|
||||
private $categoryid = false;
|
||||
|
||||
/**
|
||||
* The URL to use for the specified course
|
||||
*
|
||||
@@ -145,6 +148,30 @@ class format_singleactivity extends format_base {
|
||||
*/
|
||||
public function course_format_options($foreditform = false) {
|
||||
static $courseformatoptions = false;
|
||||
|
||||
$fetchtypes = $courseformatoptions === false;
|
||||
$fetchtypes = $fetchtypes || ($foreditform && !isset($courseformatoptions['activitytype']['label']));
|
||||
|
||||
if ($fetchtypes) {
|
||||
$availabletypes = $this->get_supported_activities();
|
||||
if ($this->course) {
|
||||
// The course exists. Test against the course.
|
||||
$testcontext = context_course::instance($this->course->id);
|
||||
} else if ($this->categoryid) {
|
||||
// The course does not exist yet, but we have a category ID that we can test against.
|
||||
$testcontext = context_coursecat::instance($this->categoryid);
|
||||
} else {
|
||||
// The course does not exist, and we somehow do not have a category. Test capabilities against the system context.
|
||||
$testcontext = context_system::instance();
|
||||
}
|
||||
foreach (array_keys($availabletypes) as $activity) {
|
||||
$capability = "mod/{$activity}:addinstance";
|
||||
if (!has_capability($capability, $testcontext)) {
|
||||
unset($availabletypes[$activity]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($courseformatoptions === false) {
|
||||
$config = get_config('format_singleactivity');
|
||||
$courseformatoptions = array(
|
||||
@@ -153,9 +180,13 @@ class format_singleactivity extends format_base {
|
||||
'type' => PARAM_TEXT,
|
||||
),
|
||||
);
|
||||
|
||||
if (!empty($availabletypes) && !isset($availabletypes[$config->activitytype])) {
|
||||
$courseformatoptions['activitytype']['default'] = array_keys($availabletypes)[0];
|
||||
}
|
||||
}
|
||||
|
||||
if ($foreditform && !isset($courseformatoptions['activitytype']['label'])) {
|
||||
$availabletypes = $this->get_supported_activities();
|
||||
$courseformatoptionsedit = array(
|
||||
'activitytype' => array(
|
||||
'label' => new lang_string('activitytype', 'format_singleactivity'),
|
||||
@@ -183,6 +214,11 @@ class format_singleactivity extends format_base {
|
||||
*/
|
||||
public function create_edit_form_elements(&$mform, $forsection = false) {
|
||||
global $PAGE;
|
||||
|
||||
if (!$this->course && $submitvalues = $mform->getSubmitValues()) {
|
||||
$this->categoryid = $submitvalues['category'];
|
||||
}
|
||||
|
||||
$elements = parent::create_edit_form_elements($mform, $forsection);
|
||||
if (!$forsection && ($course = $PAGE->course) && !empty($course->format) &&
|
||||
$course->format !== 'site' && $course->format !== 'singleactivity') {
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
@format @format_singleactivity
|
||||
Feature: Courses can be created in Single Activity mode
|
||||
In order to create a single activity course
|
||||
As a manager
|
||||
I need to create courses and set default values on them
|
||||
|
||||
Scenario: Create a course as a custom course creator
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| kevin | Kevin | the | kevin@example.com |
|
||||
And the following "roles" exist:
|
||||
| shortname | name | archetype |
|
||||
| creator | Creator | |
|
||||
And the following "system role assigns" exist:
|
||||
| user | role | contextlevel |
|
||||
| kevin | creator | System |
|
||||
And I log in as "admin"
|
||||
And I set the following system permissions of "Creator" role:
|
||||
| capability | permission |
|
||||
| moodle/course:create | Allow |
|
||||
| moodle/course:update | Allow |
|
||||
| moodle/course:manageactivities | Allow |
|
||||
| moodle/course:viewparticipants | Allow |
|
||||
| moodle/role:assign | Allow |
|
||||
| mod/quiz:addinstance | Allow |
|
||||
And I log out
|
||||
And I log in as "kevin"
|
||||
And I am on site homepage
|
||||
When I press "Add a new course"
|
||||
And I set the following fields to these values:
|
||||
| Course full name | My first course |
|
||||
| Course short name | myfirstcourse |
|
||||
| Format | Single activity format |
|
||||
And I press "Update format"
|
||||
Then I should see "Quiz" in the "Type of activity" "field"
|
||||
And I should not see "Forum" in the "Type of activity" "field"
|
||||
And I press "Save and display"
|
||||
And I should see "Adding a new Quiz"
|
||||
+22
-3
@@ -1289,16 +1289,36 @@ function course_module_flag_for_async_deletion($cmid) {
|
||||
* Checks whether the given course has any course modules scheduled for adhoc deletion.
|
||||
*
|
||||
* @param int $courseid the id of the course.
|
||||
* @param bool $onlygradable whether to check only gradable modules or all modules.
|
||||
* @return bool true if the course contains any modules pending deletion, false otherwise.
|
||||
*/
|
||||
function course_modules_pending_deletion($courseid) {
|
||||
function course_modules_pending_deletion($courseid, bool $onlygradable = false) : bool {
|
||||
if (empty($courseid)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($onlygradable) {
|
||||
// Fetch modules with grade items.
|
||||
if (!$coursegradeitems = grade_item::fetch_all(['itemtype' => 'mod', 'courseid' => $courseid])) {
|
||||
// Return early when there is none.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$modinfo = get_fast_modinfo($courseid);
|
||||
foreach ($modinfo->get_cms() as $module) {
|
||||
if ($module->deletioninprogress == '1') {
|
||||
return true;
|
||||
if ($onlygradable) {
|
||||
// Check if the module being deleted is in the list of course modules with grade items.
|
||||
foreach ($coursegradeitems as $coursegradeitem) {
|
||||
if ($coursegradeitem->itemmodule == $module->modname && $coursegradeitem->iteminstance == $module->instance) {
|
||||
// The module being deleted is within the gradable modules.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -3994,7 +4014,6 @@ function course_get_user_administration_options($course, $context) {
|
||||
$options->outcomes = !empty($CFG->enableoutcomes) && has_capability('moodle/course:update', $context);
|
||||
$options->badges = !empty($CFG->enablebadges);
|
||||
$options->import = has_capability('moodle/restore:restoretargetimport', $context);
|
||||
$options->publish = !empty($CFG->enablecoursepublishing) && has_capability('moodle/course:publish', $context);
|
||||
$options->reset = has_capability('moodle/course:reset', $context);
|
||||
$options->roles = has_capability('moodle/role:switchroles', $context);
|
||||
} else {
|
||||
|
||||
@@ -69,8 +69,11 @@ if ($courseid) {
|
||||
$course = null;
|
||||
$courseid = null;
|
||||
$topchildren = core_course_category::top()->get_children();
|
||||
if (empty($topchildren)) {
|
||||
throw new moodle_exception('cannotviewcategory', 'error');
|
||||
}
|
||||
$category = reset($topchildren);
|
||||
$categoryid = $category ? $category->id : 0;
|
||||
$categoryid = $category->id;
|
||||
$context = context_coursecat::instance($category->id);
|
||||
$url->param('categoryid', $category->id);
|
||||
}
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
<?php
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// This file is part of Moodle - http://moodle.org/ //
|
||||
// Moodle - Modular Object-Oriented Dynamic Learning Environment //
|
||||
// //
|
||||
// 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/>. //
|
||||
// //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* This page display the publication backup form
|
||||
*
|
||||
* @package course
|
||||
* @subpackage publish
|
||||
* @author Jerome Mouneyrac <jerome@mouneyrac.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL
|
||||
* @copyright (C) 1999 onwards Martin Dougiamas http://dougiamas.com
|
||||
*/
|
||||
|
||||
define('NO_OUTPUT_BUFFERING', true);
|
||||
|
||||
require_once('../../config.php');
|
||||
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
|
||||
require_once($CFG->dirroot . '/backup/moodle2/backup_plan_builder.class.php');
|
||||
require_once($CFG->libdir . '/filelib.php');
|
||||
|
||||
|
||||
//retrieve initial page parameters
|
||||
$id = required_param('id', PARAM_INT);
|
||||
$hubcourseid = required_param('hubcourseid', PARAM_INT);
|
||||
|
||||
//some permissions and parameters checking
|
||||
$course = $DB->get_record('course', array('id'=>$id), '*', MUST_EXIST);
|
||||
require_login($course);
|
||||
|
||||
$context = context_course::instance($course->id);
|
||||
if (empty($CFG->enablecoursepublishing) || !has_capability('moodle/course:publish', $context) || !confirm_sesskey()) {
|
||||
throw new moodle_exception('nopermission');
|
||||
}
|
||||
|
||||
//page settings
|
||||
$PAGE->set_url('/course/publish/backup.php');
|
||||
$PAGE->set_pagelayout('incourse');
|
||||
$PAGE->set_title(get_string('course') . ': ' . $course->fullname);
|
||||
$PAGE->set_heading($course->fullname);
|
||||
|
||||
//BEGIN backup processing
|
||||
$backupid = optional_param('backup', false, PARAM_ALPHANUM);
|
||||
if (!($bc = backup_ui::load_controller($backupid))) {
|
||||
$bc = new backup_controller(backup::TYPE_1COURSE, $id, backup::FORMAT_MOODLE,
|
||||
backup::INTERACTIVE_YES, backup::MODE_HUB, $USER->id);
|
||||
}
|
||||
$backup = new backup_ui($bc,
|
||||
array('id' => $id, 'hubcourseid' => $hubcourseid, 'huburl' => HUB_MOODLEORGHUBURL, 'hubname' => 'Moodle.net'));
|
||||
$backup->process();
|
||||
if ($backup->get_stage() == backup_ui::STAGE_FINAL) {
|
||||
$backup->execute();
|
||||
} else {
|
||||
$backup->save_controller();
|
||||
}
|
||||
|
||||
if ($backup->get_stage() !== backup_ui::STAGE_COMPLETE) {
|
||||
$renderer = $PAGE->get_renderer('core', 'backup');
|
||||
echo $OUTPUT->header();
|
||||
echo $OUTPUT->heading(get_string('publishcourseon', 'hub', 'Moodle.net'), 3, 'main');
|
||||
if ($backup->enforce_changed_dependencies()) {
|
||||
debugging('Your settings have been altered due to unmet dependencies', DEBUG_DEVELOPER);
|
||||
}
|
||||
echo $renderer->progress_bar($backup->get_progress_bar());
|
||||
echo $backup->display($renderer);
|
||||
echo $OUTPUT->footer();
|
||||
die();
|
||||
}
|
||||
|
||||
//$backupfile = $backup->get_stage_results();
|
||||
$backupfile = $bc->get_results();
|
||||
$backupfile = $backupfile['backup_destination'];
|
||||
//END backup processing
|
||||
|
||||
//display the sending file page
|
||||
echo $OUTPUT->header();
|
||||
echo $OUTPUT->heading(get_string('sendingcourse', 'hub'), 3, 'main');
|
||||
$renderer = $PAGE->get_renderer('core', 'course');
|
||||
echo $renderer->sendingbackupinfo($backupfile);
|
||||
if (ob_get_level()) {
|
||||
ob_flush();
|
||||
}
|
||||
flush();
|
||||
|
||||
//send backup file to the hub
|
||||
\core\hub\publication::upload_course_backup($hubcourseid, $backupfile);
|
||||
|
||||
//delete the temp backup file from user_tohub aera
|
||||
$backupfile->delete();
|
||||
$bc->destroy();
|
||||
|
||||
//Output sending success
|
||||
echo $renderer->sentbackupinfo($id, HUB_MOODLEORGHUBURL, 'Moodle.net');
|
||||
|
||||
echo $OUTPUT->footer();
|
||||
@@ -1,36 +0,0 @@
|
||||
<?php
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// This file is part of Moodle - http://moodle.org/ //
|
||||
// Moodle - Modular Object-Oriented Dynamic Learning Environment //
|
||||
// //
|
||||
// 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/>. //
|
||||
// //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* @package course
|
||||
* @subpackage publish
|
||||
* @author Jerome Mouneyrac <jerome@mouneyrac.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL
|
||||
* @copyright (C) 1999 onwards Martin Dougiamas http://dougiamas.com
|
||||
*
|
||||
* The forms used for course publication
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
debugging('Support for alternative hubs has been removed from Moodle in 3.4. For communication with moodle.net ' .
|
||||
'see lib/classes/hub/ .', DEBUG_DEVELOPER);
|
||||
@@ -1,99 +0,0 @@
|
||||
<?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/>.
|
||||
|
||||
/*
|
||||
* @package course
|
||||
* @subpackage publish
|
||||
* @author Jerome Mouneyrac <jerome@mouneyrac.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL
|
||||
* @copyright (C) 1999 onwards Martin Dougiamas http://dougiamas.com
|
||||
*
|
||||
* The user selects if he wants to publish the course on Moodle.org hub or
|
||||
* on a specific hub. The site must be registered on a hub to be able to
|
||||
* publish a course on it.
|
||||
*/
|
||||
|
||||
require('../../config.php');
|
||||
|
||||
$courseid = required_param('id', PARAM_INT); // Course id.
|
||||
$publicationid = optional_param('publicationid', 0, PARAM_INT); // Id of course publication to unpublish.
|
||||
|
||||
require_login($courseid);
|
||||
$shortname = format_string($COURSE->shortname);
|
||||
|
||||
$PAGE->set_url('/course/publish/index.php', array('id' => $courseid));
|
||||
$PAGE->set_pagelayout('incourse');
|
||||
$PAGE->set_title(get_string('publish', 'core_hub') . ': ' . $COURSE->fullname);
|
||||
$PAGE->set_heading($COURSE->fullname);
|
||||
|
||||
$context = context_course::instance($courseid);
|
||||
if (empty($CFG->enablecoursepublishing) || !has_capability('moodle/course:publish', $context)) {
|
||||
throw new moodle_exception('nopermission');
|
||||
}
|
||||
|
||||
// If the site is not registered display an error page.
|
||||
if (!\core\hub\registration::is_registered()) {
|
||||
echo $OUTPUT->header();
|
||||
echo $OUTPUT->heading(get_string('publishcourseon', 'hub', 'Moodle.net'), 3, 'main');
|
||||
echo $OUTPUT->box(get_string('notregisteredonhub', 'hub'));
|
||||
if (has_capability('moodle/site:config', context_system::instance())) {
|
||||
echo $OUTPUT->single_button(new moodle_url('/admin/registration/index.php'), get_string('register', 'admin'));
|
||||
}
|
||||
echo $OUTPUT->footer();
|
||||
die();
|
||||
}
|
||||
|
||||
// When hub listing status is requested update statuses of all published courses.
|
||||
$updatestatusid = optional_param('updatestatusid', false, PARAM_INT);
|
||||
if (!empty($updatestatusid) && confirm_sesskey()) {
|
||||
if (core\hub\publication::get_publication($updatestatusid, $courseid)) {
|
||||
core\hub\publication::request_status_update();
|
||||
redirect($PAGE->url);
|
||||
}
|
||||
}
|
||||
|
||||
$renderer = $PAGE->get_renderer('core', 'course');
|
||||
|
||||
// Unpublish course.
|
||||
if ($publication = \core\hub\publication::get_publication($publicationid, $courseid)) {
|
||||
$confirm = optional_param('confirm', 0, PARAM_BOOL);
|
||||
if ($confirm && confirm_sesskey()) {
|
||||
\core\hub\publication::unpublish($publication);
|
||||
} else {
|
||||
// Display confirmation page for unpublishing.
|
||||
$publication = \core\hub\publication::get_publication($publicationid, $courseid, MUST_EXIST);
|
||||
$publication->courseshortname = format_string($COURSE->shortname);
|
||||
echo $OUTPUT->header();
|
||||
echo $OUTPUT->heading(get_string('unpublishcourse', 'hub', $shortname), 3, 'main');
|
||||
echo $renderer->confirmunpublishing($publication);
|
||||
echo $OUTPUT->footer();
|
||||
die();
|
||||
}
|
||||
}
|
||||
|
||||
// List current publications and "Publish" buttons.
|
||||
echo $OUTPUT->header();
|
||||
|
||||
echo $OUTPUT->heading(get_string('publishcourse', 'hub', $shortname), 3, 'main');
|
||||
echo $renderer->publicationselector($courseid);
|
||||
|
||||
$publications = \core\hub\publication::get_course_publications($courseid);
|
||||
if (!empty($publications)) {
|
||||
echo $OUTPUT->heading(get_string('publishedon', 'hub'), 3, 'main');
|
||||
echo $renderer->registeredonhublisting($courseid, $publications);
|
||||
}
|
||||
|
||||
echo $OUTPUT->footer();
|
||||
@@ -1,192 +0,0 @@
|
||||
<?php
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// This file is part of Moodle - http://moodle.org/ //
|
||||
// Moodle - Modular Object-Oriented Dynamic Learning Environment //
|
||||
// //
|
||||
// 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/>. //
|
||||
// //
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/*
|
||||
* @package course
|
||||
* @subpackage publish
|
||||
* @author Jerome Mouneyrac <jerome@mouneyrac.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL
|
||||
* @copyright (C) 1999 onwards Martin Dougiamas http://dougiamas.com
|
||||
*
|
||||
* This page display the publication metadata form
|
||||
*/
|
||||
|
||||
require_once('../../config.php');
|
||||
require_once($CFG->libdir . '/filelib.php');
|
||||
|
||||
|
||||
//check user access capability to this page
|
||||
$id = required_param('id', PARAM_INT);
|
||||
|
||||
$course = $DB->get_record('course', array('id' => $id), '*', MUST_EXIST);
|
||||
require_login($course);
|
||||
|
||||
//page settings
|
||||
$PAGE->set_url('/course/publish/metadata.php', array('id' => $course->id));
|
||||
$PAGE->set_pagelayout('incourse');
|
||||
$PAGE->set_title(get_string('course') . ': ' . $course->fullname);
|
||||
$PAGE->set_heading($course->fullname);
|
||||
|
||||
$context = context_course::instance($course->id);
|
||||
if (empty($CFG->enablecoursepublishing) || !has_capability('moodle/course:publish', $context)) {
|
||||
throw new moodle_exception('nopermission');
|
||||
}
|
||||
|
||||
// Retrieve hub name and hub url.
|
||||
require_sesskey();
|
||||
|
||||
// Set the publication form.
|
||||
$advertise = optional_param('advertise', false, PARAM_BOOL);
|
||||
$publicationid = optional_param('publicationid', false, PARAM_INT);
|
||||
$formparams = array('course' => $course, 'advertise' => $advertise);
|
||||
if ($publicationid) {
|
||||
$publication = \core\hub\publication::get_publication($publicationid, $course->id, MUST_EXIST);
|
||||
$formparams['publication'] = $publication;
|
||||
$advertise = $formparams['advertise'] = $publication->enrollable;
|
||||
}
|
||||
$share = !$advertise;
|
||||
$coursepublicationform = new \core\hub\course_publication_form('', $formparams);
|
||||
$fromform = $coursepublicationform->get_data();
|
||||
|
||||
if (!empty($fromform)) {
|
||||
|
||||
// Retrieve the course information.
|
||||
$courseinfo = new stdClass();
|
||||
$courseinfo->fullname = $fromform->name;
|
||||
$courseinfo->shortname = $fromform->courseshortname;
|
||||
$courseinfo->description = $fromform->description;
|
||||
$courseinfo->language = $fromform->language;
|
||||
$courseinfo->publishername = $fromform->publishername;
|
||||
$courseinfo->publisheremail = $fromform->publisheremail;
|
||||
$courseinfo->contributornames = $fromform->contributornames;
|
||||
$courseinfo->coverage = $fromform->coverage;
|
||||
$courseinfo->creatorname = $fromform->creatorname;
|
||||
$courseinfo->licenceshortname = $fromform->licence;
|
||||
$courseinfo->subject = $fromform->subject;
|
||||
$courseinfo->audience = $fromform->audience;
|
||||
$courseinfo->educationallevel = $fromform->educationallevel;
|
||||
$creatornotes = $fromform->creatornotes;
|
||||
$courseinfo->creatornotes = $creatornotes['text'];
|
||||
$courseinfo->creatornotesformat = $creatornotes['format'];
|
||||
$courseinfo->sitecourseid = $id;
|
||||
if (!empty($fromform->deletescreenshots)) {
|
||||
$courseinfo->deletescreenshots = $fromform->deletescreenshots;
|
||||
}
|
||||
if ($share) {
|
||||
$courseinfo->demourl = $fromform->demourl;
|
||||
$courseinfo->enrollable = false;
|
||||
} else {
|
||||
$courseinfo->courseurl = $fromform->courseurl;
|
||||
$courseinfo->enrollable = true;
|
||||
}
|
||||
|
||||
// Retrieve the outcomes of this course.
|
||||
require_once($CFG->libdir . '/grade/grade_outcome.php');
|
||||
$outcomes = grade_outcome::fetch_all_available($id);
|
||||
if (!empty($outcomes)) {
|
||||
foreach ($outcomes as $outcome) {
|
||||
$sentoutcome = new stdClass();
|
||||
$sentoutcome->fullname = $outcome->fullname;
|
||||
$courseinfo->outcomes[] = $sentoutcome;
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve the content information from the course.
|
||||
$coursecontext = context_course::instance($course->id);
|
||||
$courseblocks = \core\hub\publication::get_block_instances_by_context($coursecontext->id);
|
||||
|
||||
if (!empty($courseblocks)) {
|
||||
$blockname = '';
|
||||
foreach ($courseblocks as $courseblock) {
|
||||
if ($courseblock->blockname != $blockname) {
|
||||
if (!empty($blockname)) {
|
||||
$courseinfo->contents[] = $content;
|
||||
}
|
||||
|
||||
$blockname = $courseblock->blockname;
|
||||
$content = new stdClass();
|
||||
$content->moduletype = 'block';
|
||||
$content->modulename = $courseblock->blockname;
|
||||
$content->contentcount = 1;
|
||||
} else {
|
||||
$content->contentcount = $content->contentcount + 1;
|
||||
}
|
||||
}
|
||||
$courseinfo->contents[] = $content;
|
||||
}
|
||||
|
||||
$activities = get_fast_modinfo($course, $USER->id);
|
||||
foreach ($activities->instances as $activityname => $activitydetails) {
|
||||
$content = new stdClass();
|
||||
$content->moduletype = 'activity';
|
||||
$content->modulename = $activityname;
|
||||
$content->contentcount = count($activities->instances[$activityname]);
|
||||
$courseinfo->contents[] = $content;
|
||||
}
|
||||
|
||||
// Save into screenshots field the references to the screenshot content hash
|
||||
// (it will be like a unique id from the hub perspective).
|
||||
if (!empty($fromform->deletescreenshots) or $share) {
|
||||
$courseinfo->screenshots = 0;
|
||||
} else {
|
||||
$courseinfo->screenshots = $fromform->existingscreenshotnumber;
|
||||
}
|
||||
$files = [];
|
||||
if (!empty($fromform->screenshots)) {
|
||||
$fs = get_file_storage();
|
||||
$files = $fs->get_area_files(context_user::instance($USER->id)->id, 'user', 'draft', $fromform->screenshots,
|
||||
'filepath, filename', false);
|
||||
$files = array_filter($files, function(stored_file $file) {
|
||||
return $file->is_valid_image();
|
||||
});
|
||||
$courseinfo->screenshots += count($files);
|
||||
}
|
||||
|
||||
// PUBLISH ACTION.
|
||||
$hubcourseid = \core\hub\publication::publish_course($courseinfo, $files);
|
||||
|
||||
// Redirect to the backup process page.
|
||||
if ($share) {
|
||||
$params = array('sesskey' => sesskey(), 'id' => $id, 'hubcourseid' => $hubcourseid);
|
||||
$backupprocessurl = new moodle_url("/course/publish/backup.php", $params);
|
||||
redirect($backupprocessurl);
|
||||
} else {
|
||||
// Redirect to the index publis page.
|
||||
redirect(new moodle_url('/course/publish/index.php', ['id' => $id]),
|
||||
get_string('coursepublished', 'hub', 'Moodle.net'), null, \core\output\notification::NOTIFY_SUCCESS);
|
||||
}
|
||||
}
|
||||
|
||||
// OUTPUT SECTION.
|
||||
|
||||
echo $OUTPUT->header();
|
||||
echo $OUTPUT->heading(get_string('publishcourseon', 'hub', 'Moodle.net'), 3, 'main');
|
||||
|
||||
// Display hub information (logo, name, description).
|
||||
$renderer = $PAGE->get_renderer('core', 'course');
|
||||
if ($hubinfo = \core\hub\registration::get_moodlenet_info()) {
|
||||
echo $renderer->hubinfo($hubinfo);
|
||||
}
|
||||
|
||||
// Display metadata form.
|
||||
$coursepublicationform->display();
|
||||
echo $OUTPUT->footer();
|
||||
@@ -2105,128 +2105,6 @@ class core_course_renderer extends plugin_renderer_base {
|
||||
return $this->output->render_from_template('core_course/activity_navigation', $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the selector to advertise or publish a course
|
||||
* @param int $courseid
|
||||
*/
|
||||
public function publicationselector($courseid) {
|
||||
$text = '';
|
||||
|
||||
$advertiseurl = new moodle_url("/course/publish/metadata.php",
|
||||
array('sesskey' => sesskey(), 'id' => $courseid, 'advertise' => true));
|
||||
$advertisebutton = new single_button($advertiseurl, get_string('advertise', 'hub'));
|
||||
$text .= $this->output->render($advertisebutton);
|
||||
$text .= html_writer::tag('div', get_string('advertisepublication_help', 'hub'),
|
||||
array('class' => 'publishhelp'));
|
||||
|
||||
$text .= html_writer::empty_tag('br'); // TODO Delete.
|
||||
|
||||
$uploadurl = new moodle_url("/course/publish/metadata.php",
|
||||
array('sesskey' => sesskey(), 'id' => $courseid, 'share' => true));
|
||||
$uploadbutton = new single_button($uploadurl, get_string('share', 'hub'));
|
||||
$text .= $this->output->render($uploadbutton);
|
||||
$text .= html_writer::tag('div', get_string('sharepublication_help', 'hub'),
|
||||
array('class' => 'publishhelp'));
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the listing of hub where a course is registered on
|
||||
* @param int $courseid
|
||||
* @param array $publications
|
||||
*/
|
||||
public function registeredonhublisting($courseid, $publications) {
|
||||
global $CFG;
|
||||
$table = new html_table();
|
||||
$table->head = array(get_string('type', 'hub'),
|
||||
get_string('date'), get_string('status', 'hub'), get_string('operation', 'hub'));
|
||||
$table->size = array('20%', '30%', '%20', '%25');
|
||||
|
||||
$brtag = html_writer::empty_tag('br');
|
||||
|
||||
foreach ($publications as $publication) {
|
||||
|
||||
$params = array('id' => $publication->courseid, 'publicationid' => $publication->id);
|
||||
$cancelurl = new moodle_url("/course/publish/index.php", $params);
|
||||
$cancelbutton = new single_button($cancelurl, get_string('removefromhub', 'hub'));
|
||||
$cancelbutton->class = 'centeredbutton';
|
||||
$cancelbuttonhtml = $this->output->render($cancelbutton);
|
||||
|
||||
if ($publication->enrollable) {
|
||||
$params = array('sesskey' => sesskey(), 'id' => $publication->courseid, 'publicationid' => $publication->id);
|
||||
$updateurl = new moodle_url("/course/publish/metadata.php", $params);
|
||||
$updatebutton = new single_button($updateurl, get_string('update', 'hub'));
|
||||
$updatebutton->class = 'centeredbutton';
|
||||
$updatebuttonhtml = $this->output->render($updatebutton);
|
||||
|
||||
$operations = $updatebuttonhtml . $brtag . $cancelbuttonhtml;
|
||||
} else {
|
||||
$operations = $cancelbuttonhtml;
|
||||
}
|
||||
|
||||
// If the publication check time if bigger than May 2010, it has been checked.
|
||||
if ($publication->timechecked > 1273127954) {
|
||||
if ($publication->status == 0) {
|
||||
$status = get_string('statusunpublished', 'hub');
|
||||
} else {
|
||||
$status = get_string('statuspublished', 'hub');
|
||||
if (!empty($publication->link)) {
|
||||
$status = html_writer::link($publication->link, $status);
|
||||
}
|
||||
}
|
||||
|
||||
$status .= $brtag . html_writer::tag('a', get_string('updatestatus', 'hub'),
|
||||
array('href' => $CFG->wwwroot . '/course/publish/index.php?id='
|
||||
. $courseid . "&updatestatusid=" . $publication->id
|
||||
. "&sesskey=" . sesskey())) .
|
||||
$brtag . get_string('lasttimechecked', 'hub') . ": "
|
||||
. format_time(time() - $publication->timechecked);
|
||||
} else {
|
||||
$status = get_string('neverchecked', 'hub') . $brtag
|
||||
. html_writer::tag('a', get_string('updatestatus', 'hub'),
|
||||
array('href' => $CFG->wwwroot . '/course/publish/index.php?id='
|
||||
. $courseid . "&updatestatusid=" . $publication->id
|
||||
. "&sesskey=" . sesskey()));
|
||||
}
|
||||
// Add button cells.
|
||||
$cells = array($publication->enrollable ?
|
||||
get_string('advertised', 'hub') : get_string('shared', 'hub'),
|
||||
userdate($publication->timepublished,
|
||||
get_string('strftimedatetimeshort')), $status, $operations);
|
||||
$row = new html_table_row($cells);
|
||||
$table->data[] = $row;
|
||||
}
|
||||
|
||||
$contenthtml = html_writer::table($table);
|
||||
|
||||
return $contenthtml;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display unpublishing confirmation page
|
||||
* @param stdClass $publication
|
||||
* $publication->courseshortname
|
||||
* $publication->courseid
|
||||
* $publication->hubname
|
||||
* $publication->huburl
|
||||
* $publication->id
|
||||
*/
|
||||
public function confirmunpublishing($publication) {
|
||||
$optionsyes = array('sesskey' => sesskey(), 'id' => $publication->courseid,
|
||||
'hubcourseid' => $publication->hubcourseid,
|
||||
'cancel' => true, 'publicationid' => $publication->id, 'confirm' => true);
|
||||
$optionsno = array('sesskey' => sesskey(), 'id' => $publication->courseid);
|
||||
$publication->hubname = html_writer::tag('a', 'Moodle.net',
|
||||
array('href' => HUB_MOODLEORGHUBURL));
|
||||
$formcontinue = new single_button(new moodle_url("/course/publish/index.php",
|
||||
$optionsyes), get_string('unpublish', 'hub'), 'post');
|
||||
$formcancel = new single_button(new moodle_url("/course/publish/index.php",
|
||||
$optionsno), get_string('cancel'), 'get');
|
||||
return $this->output->confirm(get_string('unpublishconfirmation', 'hub', $publication),
|
||||
$formcontinue, $formcancel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display waiting information about backup size during uploading backup process
|
||||
* @param object $backupfile the backup stored_file
|
||||
@@ -2240,23 +2118,6 @@ class core_course_renderer extends plugin_renderer_base {
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display upload successfull message and a button to the publish index page
|
||||
* @param int $id the course id
|
||||
* @return $html string
|
||||
*/
|
||||
public function sentbackupinfo($id) {
|
||||
$html = html_writer::tag('div', get_string('sent', 'hub'),
|
||||
array('class' => 'courseuploadtextinfo'));
|
||||
$publishindexurl = new moodle_url('/course/publish/index.php',
|
||||
array('sesskey' => sesskey(), 'id' => $id,
|
||||
'published' => true));
|
||||
$continue = $this->output->render(
|
||||
new single_button($publishindexurl, get_string('continue')));
|
||||
$html .= html_writer::tag('div', $continue, array('class' => 'sharecoursecontinue'));
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hub information (logo - name - description - link)
|
||||
* @param object $hubinfo
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
}
|
||||
}
|
||||
}}
|
||||
<div class="mt-2 mb-1 activity-navigation">
|
||||
<div class="mt-5 mb-1 activity-navigation">
|
||||
{{< core/columns-1to1to1}}
|
||||
{{$column1}}
|
||||
<div class="float-left">
|
||||
|
||||
@@ -68,3 +68,48 @@ Feature: Managers can create courses
|
||||
| id_enddate_day | 24 |
|
||||
| id_enddate_month | October |
|
||||
| id_enddate_year | 2016 |
|
||||
|
||||
Scenario: Create a course as a custom course creator
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| kevin | Kevin | the | kevin@example.com |
|
||||
And the following "roles" exist:
|
||||
| shortname | name | archetype |
|
||||
| creator | Creator | |
|
||||
And the following "system role assigns" exist:
|
||||
| user | role | contextlevel |
|
||||
| kevin | creator | System |
|
||||
And I log in as "admin"
|
||||
And I set the following system permissions of "Creator" role:
|
||||
| capability | permission |
|
||||
| moodle/course:create | Allow |
|
||||
| moodle/course:manageactivities | Allow |
|
||||
| moodle/course:viewparticipants | Allow |
|
||||
| moodle/role:assign | Allow |
|
||||
And I log out
|
||||
And I log in as "kevin"
|
||||
And I am on site homepage
|
||||
When I press "Add a new course"
|
||||
And I set the following fields to these values:
|
||||
| Course full name | My first course |
|
||||
| Course short name | myfirstcourse |
|
||||
And I press "Save and display"
|
||||
And I follow "Participants"
|
||||
Then I should see "Kevin the"
|
||||
And I should not see "Teacher"
|
||||
And I log out
|
||||
Given I log in as "admin"
|
||||
And I define the allowed role assignments for the "Creator" role as:
|
||||
| Teacher | Assignable |
|
||||
And I log out
|
||||
And I log in as "kevin"
|
||||
And I am on site homepage
|
||||
And I turn editing mode on
|
||||
When I press "Add a new course"
|
||||
And I set the following fields to these values:
|
||||
| Course full name | My second course |
|
||||
| Course short name | mysecondcourse |
|
||||
And I press "Save and display"
|
||||
And I follow "Participants"
|
||||
Then I should see "Kevin the"
|
||||
And I should see "Teacher"
|
||||
|
||||
@@ -3193,7 +3193,6 @@ class core_course_courselib_testcase extends advanced_testcase {
|
||||
$this->assertFalse($adminoptions->outcomes);
|
||||
$this->assertTrue($adminoptions->badges);
|
||||
$this->assertTrue($adminoptions->import);
|
||||
$this->assertFalse($adminoptions->publish);
|
||||
$this->assertTrue($adminoptions->reset);
|
||||
$this->assertTrue($adminoptions->roles);
|
||||
}
|
||||
@@ -3225,7 +3224,6 @@ class core_course_courselib_testcase extends advanced_testcase {
|
||||
$this->assertFalse($adminoptions->outcomes);
|
||||
$this->assertTrue($adminoptions->badges);
|
||||
$this->assertFalse($adminoptions->import);
|
||||
$this->assertFalse($adminoptions->publish);
|
||||
$this->assertFalse($adminoptions->reset);
|
||||
$this->assertFalse($adminoptions->roles);
|
||||
|
||||
@@ -5092,4 +5090,50 @@ class core_course_courselib_testcase extends advanced_testcase {
|
||||
$this->assertCount(3, $result);
|
||||
$this->assertArrayNotHasKey($courses[0]->id, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for test_course_modules_pending_deletion.
|
||||
*
|
||||
* @return array An array of arrays contain test data
|
||||
*/
|
||||
public function provider_course_modules_pending_deletion() {
|
||||
return [
|
||||
'Non-gradable activity, check all' => [['forum'], 0, false, true],
|
||||
'Gradable activity, check all' => [['assign'], 0, false, true],
|
||||
'Non-gradable activity, check gradables' => [['forum'], 0, true, false],
|
||||
'Gradable activity, check gradables' => [['assign'], 0, true, true],
|
||||
'Non-gradable within multiple, check all' => [['quiz', 'forum', 'assign'], 1, false, true],
|
||||
'Non-gradable within multiple, check gradables' => [['quiz', 'forum', 'assign'], 1, true, false],
|
||||
'Gradable within multiple, check all' => [['quiz', 'forum', 'assign'], 2, false, true],
|
||||
'Gradable within multiple, check gradables' => [['quiz', 'forum', 'assign'], 2, true, true],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the function course_modules_pending_deletion.
|
||||
*
|
||||
* @param string[] $modules A complete list aff all available modules before deletion
|
||||
* @param int $indextodelete The index of the module in the $modules array that we want to test with
|
||||
* @param bool $gradable The value to pass to the gradable argument of the course_modules_pending_deletion function
|
||||
* @param bool $expected The expected result
|
||||
* @dataProvider provider_course_modules_pending_deletion
|
||||
*/
|
||||
public function test_course_modules_pending_deletion(array $modules, int $indextodelete, bool $gradable, bool $expected) {
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Ensure recyclebin is enabled.
|
||||
set_config('coursebinenable', true, 'tool_recyclebin');
|
||||
|
||||
// Create course and modules.
|
||||
$generator = $this->getDataGenerator();
|
||||
$course = $generator->create_course();
|
||||
|
||||
$moduleinstances = [];
|
||||
foreach ($modules as $module) {
|
||||
$moduleinstances[] = $generator->create_module($module, array('course' => $course->id));
|
||||
}
|
||||
|
||||
course_delete_module($moduleinstances[$indextodelete]->cmid, true); // Try to delete the instance asynchronously.
|
||||
$this->assertEquals($expected, course_modules_pending_deletion($course->id, $gradable));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2334,12 +2334,11 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
|
||||
$this->assertFalse($adminoptions->outcomes);
|
||||
$this->assertFalse($adminoptions->badges);
|
||||
$this->assertFalse($adminoptions->import);
|
||||
$this->assertFalse($adminoptions->publish);
|
||||
$this->assertFalse($adminoptions->reset);
|
||||
$this->assertFalse($adminoptions->roles);
|
||||
$this->assertFalse($adminoptions->editcompletion);
|
||||
} else {
|
||||
$this->assertCount(15, $course['options']);
|
||||
$this->assertCount(14, $course['options']);
|
||||
$this->assertFalse($adminoptions->update);
|
||||
$this->assertFalse($adminoptions->filters);
|
||||
$this->assertFalse($adminoptions->reports);
|
||||
@@ -2351,7 +2350,6 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
|
||||
$this->assertFalse($adminoptions->outcomes);
|
||||
$this->assertTrue($adminoptions->badges);
|
||||
$this->assertFalse($adminoptions->import);
|
||||
$this->assertFalse($adminoptions->publish);
|
||||
$this->assertFalse($adminoptions->reset);
|
||||
$this->assertFalse($adminoptions->roles);
|
||||
$this->assertFalse($adminoptions->editcompletion);
|
||||
|
||||
@@ -28,6 +28,7 @@ defined('MOODLE_INTERNAL') || die();
|
||||
global $CFG;
|
||||
require_once(__DIR__ . '/../../lib/completionlib.php');
|
||||
require_once(__DIR__ . '/../../completion/criteria/completion_criteria_self.php');
|
||||
require_once(__DIR__ . '/../../analytics/tests/fixtures/test_target_course_users.php');
|
||||
|
||||
/**
|
||||
* Unit tests for core_course indicators.
|
||||
@@ -313,4 +314,30 @@ class core_course_indicators_testcase extends advanced_testcase {
|
||||
// Page social is level 1 (the lower level).
|
||||
$this->assertEquals($indicator::get_min_value(), $values[$cm2->id][0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* test_activities_due
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function test_activities_due() {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
$this->setAdminuser();
|
||||
|
||||
$course1 = $this->getDataGenerator()->create_course();
|
||||
$user1 = $this->getDataGenerator()->create_user();
|
||||
$this->getDataGenerator()->enrol_user($user1->id, $course1->id, 'student');
|
||||
|
||||
$target = \core_analytics\manager::get_target('test_target_course_users');
|
||||
$indicators = array('\core_course\analytics\indicator\activities_due');
|
||||
foreach ($indicators as $key => $indicator) {
|
||||
$indicators[$key] = \core_analytics\manager::get_indicator($indicator);
|
||||
}
|
||||
|
||||
$model = \core_analytics\model::create($target, $indicators);
|
||||
$model->enable('\core\analytics\time_splitting\single_range');
|
||||
$model->train();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,6 +131,157 @@ class core_course_restore_testcase extends advanced_testcase {
|
||||
return $this->restore_course($backupid, 0, $userid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore a course.
|
||||
*
|
||||
* @param int $backupid The backup ID.
|
||||
* @param int $courseid The course ID to restore in, or 0.
|
||||
* @param int $userid The ID of the user performing the restore.
|
||||
* @param int $target THe target of the restore.
|
||||
*
|
||||
* @return stdClass The updated course object.
|
||||
*/
|
||||
protected function async_restore_course($backupid, $courseid, $userid, $target) {
|
||||
global $DB;
|
||||
|
||||
if (!$courseid) {
|
||||
$target = backup::TARGET_NEW_COURSE;
|
||||
$categoryid = $DB->get_field_sql("SELECT MIN(id) FROM {course_categories}");
|
||||
$courseid = restore_dbops::create_new_course('Tmp', 'tmp', $categoryid);
|
||||
}
|
||||
|
||||
$rc = new restore_controller($backupid, $courseid, backup::INTERACTIVE_NO, backup::MODE_ASYNC, $userid, $target);
|
||||
$target == backup::TARGET_NEW_COURSE ?: $rc->get_plan()->get_setting('overwrite_conf')->set_value(true);
|
||||
$this->assertTrue($rc->execute_precheck());
|
||||
|
||||
$restoreid = $rc->get_restoreid();
|
||||
$rc->destroy();
|
||||
|
||||
// Create the adhoc task.
|
||||
$asynctask = new \core\task\asynchronous_restore_task();
|
||||
$asynctask->set_blocking(false);
|
||||
$asynctask->set_custom_data(array('backupid' => $restoreid));
|
||||
\core\task\manager::queue_adhoc_task($asynctask);
|
||||
|
||||
// We are expecting trace output during this test.
|
||||
$this->expectOutputRegex("/$restoreid/");
|
||||
|
||||
// Execute adhoc task.
|
||||
$now = time();
|
||||
$task = \core\task\manager::get_next_adhoc_task($now);
|
||||
$this->assertInstanceOf('\\core\\task\\asynchronous_restore_task', $task);
|
||||
$task->execute();
|
||||
\core\task\manager::adhoc_task_complete($task);
|
||||
|
||||
$course = $DB->get_record('course', array('id' => $rc->get_courseid()));
|
||||
|
||||
return $course;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore a course to an existing course.
|
||||
*
|
||||
* @param int $backupid The backup ID.
|
||||
* @param int $courseid The course ID to restore in.
|
||||
* @param int $userid The ID of the user performing the restore.
|
||||
* @param int $target The type of restore we are performing.
|
||||
* @return stdClass The updated course object.
|
||||
*/
|
||||
protected function async_restore_to_existing_course($backupid, $courseid,
|
||||
$userid = 2, $target = backup::TARGET_CURRENT_ADDING) {
|
||||
return $this->async_restore_course($backupid, $courseid, $userid, $target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore a course to a new course.
|
||||
*
|
||||
* @param int $backupid The backup ID.
|
||||
* @param int $userid The ID of the user performing the restore.
|
||||
* @return stdClass The new course object.
|
||||
*/
|
||||
protected function async_restore_to_new_course($backupid, $userid = 2) {
|
||||
return $this->async_restore_course($backupid, 0, $userid, 0);
|
||||
}
|
||||
|
||||
public function test_async_restore_existing_idnumber_in_new_course() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$dg = $this->getDataGenerator();
|
||||
$c1 = $dg->create_course(['idnumber' => 'ABC']);
|
||||
$backupid = $this->backup_course($c1->id);
|
||||
$c2 = $this->async_restore_to_new_course($backupid);
|
||||
|
||||
// The ID number is set empty.
|
||||
$this->assertEquals('', $c2->idnumber);
|
||||
}
|
||||
|
||||
public function test_async_restore_course_info_in_existing_course() {
|
||||
global $DB;
|
||||
$this->resetAfterTest();
|
||||
$dg = $this->getDataGenerator();
|
||||
|
||||
$this->assertEquals(1, get_config('restore', 'restore_merge_course_shortname'));
|
||||
$this->assertEquals(1, get_config('restore', 'restore_merge_course_fullname'));
|
||||
$this->assertEquals(1, get_config('restore', 'restore_merge_course_startdate'));
|
||||
|
||||
$startdate = mktime(12, 0, 0, 7, 1, 2016); // 01-Jul-2016.
|
||||
|
||||
// Create two courses with different start dates,in each course create a chat that opens 1 week after the course start date.
|
||||
$c1 = $dg->create_course(['shortname' => 'SN', 'fullname' => 'FN', 'summary' => 'DESC', 'summaryformat' => FORMAT_MOODLE,
|
||||
'startdate' => $startdate]);
|
||||
$chat1 = $dg->create_module('chat', ['name' => 'First', 'course' => $c1->id, 'chattime' => $c1->startdate + 1 * WEEKSECS]);
|
||||
$c2 = $dg->create_course(['shortname' => 'A', 'fullname' => 'B', 'summary' => 'C', 'summaryformat' => FORMAT_PLAIN,
|
||||
'startdate' => $startdate + 2 * WEEKSECS]);
|
||||
$chat2 = $dg->create_module('chat', ['name' => 'Second', 'course' => $c2->id, 'chattime' => $c2->startdate + 1 * WEEKSECS]);
|
||||
$backupid = $this->backup_course($c1->id);
|
||||
|
||||
// The information is restored but adapted because names are already taken.
|
||||
$c2 = $this->async_restore_to_existing_course($backupid, $c2->id);
|
||||
$this->assertEquals('SN_1', $c2->shortname);
|
||||
$this->assertEquals('FN copy 1', $c2->fullname);
|
||||
$this->assertEquals('DESC', $c2->summary);
|
||||
$this->assertEquals(FORMAT_MOODLE, $c2->summaryformat);
|
||||
$this->assertEquals($startdate, $c2->startdate);
|
||||
|
||||
// Now course c2 has two chats - one ('Second') was already there and one ('First') was restored from the backup.
|
||||
// Their dates are exactly the same as they were in the original modules.
|
||||
$restoredchat1 = $DB->get_record('chat', ['name' => 'First', 'course' => $c2->id]);
|
||||
$restoredchat2 = $DB->get_record('chat', ['name' => 'Second', 'course' => $c2->id]);
|
||||
$this->assertEquals($chat1->chattime, $restoredchat1->chattime);
|
||||
$this->assertEquals($chat2->chattime, $restoredchat2->chattime);
|
||||
}
|
||||
|
||||
public function test_async_restore_course_info_in_existing_course_delete_first() {
|
||||
global $DB;
|
||||
$this->resetAfterTest();
|
||||
$dg = $this->getDataGenerator();
|
||||
|
||||
$this->assertEquals(1, get_config('restore', 'restore_merge_course_shortname'));
|
||||
$this->assertEquals(1, get_config('restore', 'restore_merge_course_fullname'));
|
||||
$this->assertEquals(1, get_config('restore', 'restore_merge_course_startdate'));
|
||||
|
||||
$startdate = mktime(12, 0, 0, 7, 1, 2016); // 01-Jul-2016.
|
||||
|
||||
// Create two courses with different start dates,in each course create a chat that opens 1 week after the course start date.
|
||||
$c1 = $dg->create_course(['shortname' => 'SN', 'fullname' => 'FN', 'summary' => 'DESC', 'summaryformat' => FORMAT_MOODLE,
|
||||
'startdate' => $startdate]);
|
||||
$chat1 = $dg->create_module('chat', ['name' => 'First', 'course' => $c1->id, 'chattime' => $c1->startdate + 1 * WEEKSECS]);
|
||||
$c2 = $dg->create_course(['shortname' => 'A', 'fullname' => 'B', 'summary' => 'C', 'summaryformat' => FORMAT_PLAIN,
|
||||
'startdate' => $startdate + 2 * WEEKSECS]);
|
||||
$chat2 = $dg->create_module('chat', ['name' => 'Second', 'course' => $c2->id, 'chattime' => $c2->startdate + 1 * WEEKSECS]);
|
||||
$backupid = $this->backup_course($c1->id);
|
||||
|
||||
// The information is restored and the existing course settings is modified.
|
||||
$c2 = $this->async_restore_to_existing_course($backupid, $c2->id, 2, backup::TARGET_CURRENT_DELETING);
|
||||
$this->assertEquals(FORMAT_MOODLE, $c2->summaryformat);
|
||||
|
||||
// Now course2 should have a new forum with the original forum deleted.
|
||||
$restoredchat1 = $DB->get_record('chat', ['name' => 'First', 'course' => $c2->id]);
|
||||
$restoredchat2 = $DB->get_record('chat', ['name' => 'Second', 'course' => $c2->id]);
|
||||
$this->assertEquals($chat1->chattime, $restoredchat1->chattime);
|
||||
$this->assertEmpty($restoredchat2);
|
||||
}
|
||||
|
||||
public function test_restore_existing_idnumber_in_new_course() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
|
||||
@@ -89,6 +89,11 @@ class writer extends \core\dataformat\base {
|
||||
public function write_record($record, $rownum) {
|
||||
$rowheight = 0;
|
||||
|
||||
// If $record is an object convert it to an array.
|
||||
if (is_object($record)) {
|
||||
$record = (array)$record;
|
||||
}
|
||||
|
||||
foreach ($record as $cell) {
|
||||
$rowheight = max($rowheight, $this->pdf->getStringHeight($this->colwidth, $cell, false, true, '', 1));
|
||||
}
|
||||
@@ -99,12 +104,19 @@ class writer extends \core\dataformat\base {
|
||||
$this->print_heading();
|
||||
}
|
||||
|
||||
$total = count($record);
|
||||
$counter = 1;
|
||||
foreach ($record as $cell) {
|
||||
$nextposition = ($counter == $total) ? 1 : 0;
|
||||
// Get the last key for this record.
|
||||
end($record);
|
||||
$lastkey = key($record);
|
||||
|
||||
// Reset the record pointer.
|
||||
reset($record);
|
||||
|
||||
// Loop through each element.
|
||||
foreach ($record as $key => $cell) {
|
||||
// Determine whether we're at the last element of the record.
|
||||
$nextposition = ($lastkey === $key) ? 1 : 0;
|
||||
// Write the element.
|
||||
$this->pdf->Multicell($this->colwidth, $rowheight, $cell, 1, 'L', false, $nextposition);
|
||||
$counter++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -124,7 +124,7 @@ class converter implements \core_files\converter_interface {
|
||||
$uploadurl;
|
||||
// Google returns a location header with the location for the upload.
|
||||
foreach ($headers as $header) {
|
||||
if (strpos($header, 'Location:') === 0) {
|
||||
if (stripos($header, 'Location:') === 0) {
|
||||
$uploadurl = trim(substr($header, strpos($header, ':') + 1));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,7 +194,9 @@ class converter implements \core_files\converter_interface {
|
||||
|
||||
$conversions = conversion::get_conversions_for_file($testdocx, $format);
|
||||
foreach ($conversions as $conversion) {
|
||||
$conversion->delete();
|
||||
if ($conversion->get('id')) {
|
||||
$conversion->delete();
|
||||
}
|
||||
}
|
||||
|
||||
$conversion = new conversion(0, (object) [
|
||||
|
||||
@@ -23,6 +23,7 @@ Feature: Converting rubric score to grades
|
||||
| activity | name | intro | course | idnumber | grade | advancedgradingmethod_submissions |
|
||||
| assign | Test assignment 1 | Test | C1 | assign1 | <grade> | rubric |
|
||||
When I log in as "teacher1"
|
||||
And I change window size to "large"
|
||||
And I am on "Course 1" course homepage with editing mode on
|
||||
And I go to "Test assignment 1" advanced grading definition page
|
||||
And I set the following fields to these values:
|
||||
|
||||
@@ -26,6 +26,7 @@ Feature: Rubrics can have levels with negative scores
|
||||
And the following "activities" exist:
|
||||
| activity | name | intro | course | idnumber | grade | advancedgradingmethod_submissions |
|
||||
| assign | Test assignment 1 | Test | C1 | assign1 | 100 | rubric |
|
||||
And I change window size to "large"
|
||||
When I log in as "teacher1"
|
||||
And I am on "Course 1" course homepage with editing mode on
|
||||
And I go to "Test assignment 1" advanced grading definition page
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user