Merge branch 'MDL-59612-master' of git://github.com/junpataleta/moodle
authorDavid Monllao <davidm@moodle.com>
Mon, 31 Jul 2017 11:59:55 +0000 (13:59 +0200)
committerDavid Monllao <davidm@moodle.com>
Mon, 31 Jul 2017 11:59:55 +0000 (13:59 +0200)
60 files changed:
admin/tool/analytics/classes/output/models_list.php
admin/tool/analytics/model.php
admin/tool/behat/tests/behat/data_generators.feature
admin/tool/monitor/tests/behat/rule.feature
analytics/classes/model.php
availability/condition/completion/classes/frontend.php
availability/condition/completion/lang/en/availability_completion.php
availability/condition/completion/yui/build/moodle-availability_completion-form/moodle-availability_completion-form-debug.js
availability/condition/completion/yui/build/moodle-availability_completion-form/moodle-availability_completion-form-min.js
availability/condition/completion/yui/build/moodle-availability_completion-form/moodle-availability_completion-form.js
availability/condition/completion/yui/src/form/js/form.js
availability/condition/grade/tests/behat/availability_grade.feature
availability/tests/behat/edit_availability.feature
backup/util/ui/tests/behat/restore_moodle2_courses.feature
blocks/activity_modules/tests/behat/block_activity_modules.feature
blocks/activity_results/tests/behat/addunsupportedactivity.feature
blocks/blog_recent/tests/behat/block_blog_recent_course.feature
blocks/calendar_month/tests/behat/block_calendar_month.feature
blocks/completionstatus/tests/behat/block_completionstatus_activity_completion.feature
calendar/tests/behat/calendar_lookahead.feature
completion/tests/behat/bulk_edit_activity_completion.feature
completion/tests/behat/default_activity_completion.feature
course/tests/behat/navigate_course_list.feature
course/tests/behat/paged_course_navigation.feature
enrol/meta/tests/behat/enrol_meta.feature
enrol/tests/behat/enrol_user.feature
grade/tests/behat/grade_minmax.feature
lib/blocklib.php
lib/classes/oauth2/api.php
lib/db/install.xml
lib/db/upgrade.php
lib/ddl/mysql_sql_generator.php
lib/dml/mariadb_native_moodle_database.php
lib/dml/mysqli_native_moodle_database.php
lib/outputrenderers.php
mod/book/tests/behat/edit_tags.feature
mod/data/classes/external.php
mod/data/classes/external/content_exporter.php
mod/data/locallib.php
mod/data/tests/behat/completion_condition_entries.feature
mod/data/tests/externallib_test.php
mod/feedback/tests/behat/anonymous.feature
mod/feedback/tests/behat/coursemapping.feature
mod/feedback/tests/behat/multipleattempt.feature
mod/forum/tests/behat/posts_ordering_blog.feature
mod/glossary/tests/behat/edit_tags.feature
mod/lesson/tests/behat/duplicate_lesson_page.feature
mod/lesson/tests/behat/import_fillintheblank_question.feature
mod/lesson/tests/behat/import_images.feature
mod/lesson/tests/behat/questions_images.feature
mod/quiz/mod_form.php
mod/quiz/tests/behat/completion_condition_attempts_used.feature
mod/quiz/tests/behat/completion_condition_passing_grade.feature
theme/boost/scss/moodle/search.scss
user/action_redir.php
user/index.php
user/tests/behat/bulk_editenrolment.feature [new file with mode: 0644]
user/tests/behat/course_preference.feature
user/tests/behat/set_default_homepage.feature
version.php

index 181b48d..36f1feb 100644 (file)
@@ -117,6 +117,20 @@ class models_list implements \renderable, \templatable {
                 $actionsmenu->add($icon);
             }
 
+            // Enable / disable.
+            if ($model->is_enabled()) {
+                $action = 'disable';
+                $text = get_string('disable');
+                $icontype = 't/block';
+            } else {
+                $action = 'enable';
+                $text = get_string('enable');
+                $icontype = 'i/checked';
+            }
+            $url = new \moodle_url('model.php', array('action' => $action, 'id' => $model->get_id()));
+            $icon = new \action_menu_link_secondary($url, new \pix_icon($icontype, $text), $text);
+            $actionsmenu->add($icon);
+
             // Evaluate machine-learning-based models.
             if ($model->get_indicators() && !$model->is_static()) {
                 $url = new \moodle_url('model.php', array('action' => 'evaluate', 'id' => $model->get_id()));
index 2c64179..9ef3c3d 100644 (file)
@@ -51,6 +51,13 @@ switch ($action) {
     case 'log':
         $title = get_string('viewlog', 'tool_analytics');
         break;
+    case 'enable':
+        $title = get_string('enable');
+        break;
+    case 'disable':
+        $title = get_string('disable');
+        break;
+
     default:
         throw new moodle_exception('errorunknownaction', 'analytics');
 }
@@ -63,6 +70,14 @@ $PAGE->set_heading($title);
 
 switch ($action) {
 
+    case 'enable':
+        $model->enable();
+        redirect(new \moodle_url('/admin/tool/analytics/index.php'));
+
+    case 'disable':
+        $model->update(0, false, false);
+        redirect(new \moodle_url('/admin/tool/analytics/index.php'));
+
     case 'edit':
 
         if ($model->is_static()) {
index d8a325b..0e62104 100644 (file)
@@ -221,7 +221,7 @@ Feature: Set up contextual data for tests
     And I should see "Test workshop name"
     And I follow "Test assignment name"
     And I should see "Test assignment description"
-    And I follow "C1"
+    And I am on "Course 1" course homepage
     And I follow "Test assignment name with scale"
     And I follow "Edit settings"
     And the field "Type" matches value "Scale"
@@ -312,7 +312,6 @@ Feature: Set up contextual data for tests
       | fullname | course | gradecategory |
       | Grade sub category 2 | C1 | Grade category 1 |
     When I log in as "admin"
-    And I am on course index
     And I am on "Course 1" course homepage
     And I navigate to "View > Grader report" in the course gradebook
     Then I should see "Grade category 1"
index a38a75d..de818df 100644 (file)
@@ -17,7 +17,6 @@ Feature: tool_monitor_rule
     And I log in as "admin"
     And I navigate to "Event monitoring rules" node in "Site administration > Reports"
     And I click on "Enable" "link"
-    And I am on site homepage
     And I am on "Course 1" course homepage
     And I navigate to "Event monitoring rules" node in "Course administration > Reports"
     And I press "Add a new rule"
index 79352cb..985d655 100644 (file)
@@ -395,20 +395,30 @@ class model {
      * Updates the model.
      *
      * @param int|bool $enabled
-     * @param \core_analytics\local\indicator\base[] $indicators
-     * @param string $timesplittingid
+     * @param \core_analytics\local\indicator\base[]|false $indicators False to respect current indicators
+     * @param string|false $timesplittingid False to respect current time splitting method
      * @return void
      */
-    public function update($enabled, $indicators, $timesplittingid = '') {
+    public function update($enabled, $indicators = false, $timesplittingid = '') {
         global $USER, $DB;
 
         \core_analytics\manager::check_can_manage_models();
 
         $now = time();
 
-        $indicatorclasses = self::indicator_classes($indicators);
+        if ($indicators !== false) {
+            $indicatorclasses = self::indicator_classes($indicators);
+            $indicatorsstr = json_encode($indicatorclasses);
+        } else {
+            // Respect current value.
+            $indicatorsstr = $this->model->indicators;
+        }
+
+        if ($timesplittingid === false) {
+            // Respect current value.
+            $timesplittingid = $this->model->timesplitting;
+        }
 
-        $indicatorsstr = json_encode($indicatorclasses);
         if ($this->model->timesplitting !== $timesplittingid ||
                 $this->model->indicators !== $indicatorsstr) {
             // We update the version of the model so different time splittings are not mixed up.
@@ -900,7 +910,7 @@ class model {
     /**
      * Enabled the model using the provided time splitting method.
      *
-     * @param string $timesplittingid
+     * @param string|false $timesplittingid False to respect the current time splitting method.
      * @return void
      */
     public function enable($timesplittingid = false) {
index 8b425d7..7427328 100644 (file)
@@ -65,8 +65,9 @@ class frontend extends \core_availability\frontend {
                 // Add each course-module if it has completion turned on and is not
                 // the one currently being edited.
                 if ($othercm->completion && (empty($cm) || $cm->id != $id) && !$othercm->deletioninprogress) {
-                    $cms[] = (object)array('id' => $id, 'name' =>
-                            format_string($othercm->name, true, array('context' => $context)));
+                    $cms[] = (object)array('id' => $id,
+                        'name' => format_string($othercm->name, true, array('context' => $context)),
+                        'completiongradeitemnumber' => $othercm->completiongradeitemnumber);
                 }
             }
 
index aa41e64..938cbab 100644 (file)
@@ -24,6 +24,7 @@
 
 $string['description'] = 'Require students to complete (or not complete) another activity.';
 $string['error_selectcmid'] = 'You must select an activity for the completion condition.';
+$string['error_selectcmidpassfail'] = 'You must select an activity with "Require grade" completion condition set.';
 $string['label_cm'] = 'Activity or resource';
 $string['label_completion'] = 'Required completion status';
 $string['missing'] = '(Missing activity)';
index 4885548..5212364 100644 (file)
Binary files a/availability/condition/completion/yui/build/moodle-availability_completion-form/moodle-availability_completion-form-debug.js and b/availability/condition/completion/yui/build/moodle-availability_completion-form/moodle-availability_completion-form-debug.js differ
index 36efbb5..10be26c 100644 (file)
Binary files a/availability/condition/completion/yui/build/moodle-availability_completion-form/moodle-availability_completion-form-min.js and b/availability/condition/completion/yui/build/moodle-availability_completion-form/moodle-availability_completion-form-min.js differ
index 4885548..5212364 100644 (file)
Binary files a/availability/condition/completion/yui/build/moodle-availability_completion-form/moodle-availability_completion-form.js and b/availability/condition/completion/yui/build/moodle-availability_completion-form/moodle-availability_completion-form.js differ
index e7b418e..77c9cf1 100644 (file)
@@ -76,4 +76,14 @@ M.availability_completion.form.fillErrors = function(errors, node) {
     if (cmid === 0) {
         errors.push('availability_completion:error_selectcmid');
     }
+    var e = parseInt(node.one('select[name=e]').get('value'), 10);
+    if (((e === 2) || (e === 3))) {
+        this.cms.forEach(function(cm) {
+            if (cm.id === cmid) {
+                if (cm.completiongradeitemnumber === null) {
+                    errors.push('availability_completion:error_selectcmidpassfail');
+                }
+            }
+        });
+    }
 };
index a984525..546e378 100644 (file)
@@ -96,7 +96,7 @@ Feature: availability_grade
     And I click on "Add submission" "button"
     And I set the field "Online text" to "Q"
     And I click on "Save changes" "button"
-    And I follow "C1"
+    And I am on "Course 1" course homepage
 
     # None of the pages should appear (check assignment though).
     Then I should not see "P2" in the "region-main" "region"
index ce0392e..3c35227 100644 (file)
@@ -36,7 +36,7 @@ Feature: edit_availability
     And I add a "Page" to section "1"
     Then "Restrict access" "fieldset" should not exist
 
-    Given I follow "C1"
+    Given I am on "Course 1" course homepage
     When I edit the section "1"
     Then "Restrict access" "fieldset" should not exist
 
@@ -47,7 +47,7 @@ Feature: edit_availability
     And I add a "Page" to section "1"
     Then "Restrict access" "fieldset" should exist
 
-    Given I follow "C1"
+    Given I am on "Course 1" course homepage
     When I edit the section "1"
     Then "Restrict access" "fieldset" should exist
 
index a2886be..d34b8d5 100644 (file)
@@ -169,8 +169,7 @@ Feature: Restore Moodle 2 course backups
     When I backup "Course 1" course using this options:
       | Initial |  Include enrolled users | 0 |
       | Confirmation | Filename | test_backup.mbz |
-    And I am on site homepage
-    And I follow "Course 2"
+    And I am on "Course 2" course homepage
     And I navigate to "Restore" node in "Course administration"
     And I merge "test_backup.mbz" backup into the current course after deleting it's contents using this options:
       | Schema | Overwrite course configuration | Yes |
@@ -199,8 +198,7 @@ Feature: Restore Moodle 2 course backups
     When I backup "Course 1" course using this options:
       | Initial |  Include enrolled users | 0 |
       | Confirmation | Filename | test_backup.mbz |
-    And I am on site homepage
-    And I follow "Course 2"
+    And I am on "Course 2" course homepage
     And I navigate to "Restore" node in "Course administration"
     And I merge "test_backup.mbz" backup into the current course after deleting it's contents using this options:
       | Schema | Overwrite course configuration | No |
@@ -229,8 +227,7 @@ Feature: Restore Moodle 2 course backups
     When I backup "Course 1" course using this options:
       | Initial |  Include enrolled users | 0 |
       | Confirmation | Filename | test_backup.mbz |
-    And I am on site homepage
-    And I follow "Course 4"
+    And I am on "Course 4" course homepage
     And I navigate to "Restore" node in "Course administration"
     And I merge "test_backup.mbz" backup into the current course after deleting it's contents using this options:
       | Schema | Overwrite course configuration | No |
index 2f24252..63e6dba 100644 (file)
@@ -109,7 +109,6 @@ Feature: Block activity modules
       | workshop   | Test workshop name     | Test workshop description     | C1     | workshop1   |
 
     When I log in as "admin"
-    And I am on course index
     And I am on "Course 1" course homepage with editing mode on
     And I add the "Activities" block
     And I click on "Assignments" "link" in the "Activities" "block"
index 5103f23..982510d 100644 (file)
@@ -22,7 +22,7 @@ Feature: The activity results block doesn't display student scores for unsupport
       | Assignment name | Test assignment |
       | Description | Offline text |
       | assignsubmission_file_enabled | 0 |
-    And I follow "C1"
+    And I am on "Course 1" course homepage
     And I add the "Activity results" block
     And I configure the "Activity results" block
     And I set the following fields to these values:
index 67ee460..8814cc4 100644 (file)
@@ -32,7 +32,7 @@ Feature: Students can use the recent blog entries block to view recent entries o
     And I press "Save changes"
     Then I should see "S1 First Blog"
     And I should see "This is my awesome blog!"
-    And I follow "C1"
+    And I am on "Course 1" course homepage
     And I should see "S1 First Blog"
     And I follow "S1 First Blog"
     And I should see "This is my awesome blog!"
@@ -47,7 +47,7 @@ Feature: Students can use the recent blog entries block to view recent entries o
       | Blog entry body | This is my awesome blog! |
     And I press "Save changes"
     And I wait "1" seconds
-    And I follow "C1"
+    And I am on "Course 1" course homepage
     And I follow "Add an entry about this course"
     # Blog 2 of 5
     And I set the following fields to these values:
@@ -57,7 +57,7 @@ Feature: Students can use the recent blog entries block to view recent entries o
     And I wait "1" seconds
     And I should see "S1 Second Blog"
     And I should see "This is my awesome blog!"
-    And I follow "C1"
+    And I am on "Course 1" course homepage
     And I follow "Add an entry about this course"
     # Blog 3 of 5
     And I set the following fields to these values:
@@ -67,7 +67,7 @@ Feature: Students can use the recent blog entries block to view recent entries o
     And I wait "1" seconds
     And I should see "S1 Third Blog"
     And I should see "This is my awesome blog!"
-    And I follow "C1"
+    And I am on "Course 1" course homepage
     And I follow "Add an entry about this course"
     # Blog 4 of 5
     And I set the following fields to these values:
@@ -77,7 +77,7 @@ Feature: Students can use the recent blog entries block to view recent entries o
     And I wait "1" seconds
     And I should see "S1 Fourth Blog"
     And I should see "This is my awesome blog!"
-    And I follow "C1"
+    And I am on "Course 1" course homepage
     And I follow "Add an entry about this course"
     # Blog 5 of 5
     And I set the following fields to these values:
@@ -86,7 +86,7 @@ Feature: Students can use the recent blog entries block to view recent entries o
     And I press "Save changes"
     And I should see "S1 Fifth Blog"
     And I should see "This is my awesome blog!"
-    When I follow "C1"
+    When I am on "Course 1" course homepage
     And I should not see "S1 First Blog"
     And I should see "S1 Second Blog"
     And I should see "S1 Third Blog"
index db6f058..15e30e1 100644 (file)
@@ -81,7 +81,6 @@ Feature: Enable the calendar block in a course and test it's functionality
     And I create a calendar event with form data:
       | id_eventtype | User |
       | id_name | User Event |
-    When I am on homepage
     And I am on "Course 1" course homepage
     And I follow "Hide course events"
     And I hover over today in the calendar
@@ -96,8 +95,7 @@ Feature: Enable the calendar block in a course and test it's functionality
     And I create a calendar event with form data:
       | id_eventtype | User |
       | id_name | User Event |
-    When I am on homepage
-    And I am on "Course 1" course homepage
+    When I am on "Course 1" course homepage
     And I hover over today in the calendar
     Then I should see "User Event"
 
@@ -113,8 +111,7 @@ Feature: Enable the calendar block in a course and test it's functionality
     And I create a calendar event with form data:
       | id_eventtype | User |
       | id_name | User Event |
-    When I am on homepage
-    And I am on "Course 1" course homepage
+    When I am on "Course 1" course homepage
     And I follow "Hide user events"
     And I hover over today in the calendar
     Then I should not see "User Event"
index a92c042..d41fe98 100644 (file)
@@ -60,7 +60,7 @@ Feature: Enable Block Completion in a course using activity completion
     When I log in as "student1"
     And I am on "Course 1" course homepage
     And I follow "Test page name"
-    And I follow "C1"
+    And I am on "Course 1" course homepage
     Then I should see "Status: Pending" in the "Course completion status" "block"
     And I should see "0 of 1" in the "Activity completion" "table_row"
     And I trigger cron
index 819d03e..2f82882 100644 (file)
@@ -26,7 +26,7 @@ Feature: Limit displayed upcoming events
     And I create a calendar event:
       | Type of event     | course |
       | Event title       | Two months away event |
-    When I follow "C1"
+    When I am on "Course 1" course homepage
     Then I should not see "Two months away event"
     And I am on site homepage
     And I follow "Preferences" in the user menu
index c4d0352..4d72d35 100644 (file)
@@ -27,9 +27,7 @@ Feature: Allow teachers to bulk edit activity completion rules in a course.
       | assign | C1 | a3 | Test assignment three | Submit something! | 150 |
       | assign | C1 | a4 | Test assignment four | Submit nothing! | 150 |
     And I log in as "teacher1"
-    And I am on site homepage
-    And I follow "Course 1"
-    And I turn editing mode on
+    And I am on "Course 1" course homepage with editing mode on
     And I navigate to "Edit settings" in current page administration
     And I set the following fields to these values:
       | Enable completion tracking | Yes |
index 13aeb24..cc32275 100644 (file)
@@ -24,9 +24,7 @@ Feature: Allow teachers to edit the default activity completion rules in a cours
       | activity | course | idnumber | name | intro | grade |
       | assign | C1 | a1 | Test assignment one | Submit something! | 300 |
     And I log in as "teacher1"
-    And I am on site homepage
-    And I follow "Course 1"
-    And I turn editing mode on
+    And I am on "Course 1" course homepage with editing mode on
     And I navigate to "Edit settings" in current page administration
     And I set the following fields to these values:
       | Enable completion tracking | Yes |
index acd8219..cce5ffa 100644 (file)
@@ -26,7 +26,7 @@ Feature: Browse course list and return back from enrolment page
     Then I should see "Courses" in the ".breadcrumb-nav" "css_element"
     And I click on "Courses" "link" in the ".breadcrumb-nav" "css_element"
     And I follow "Sample category"
-    And I follow "Course 1"
+    And I am on "Course 1" course homepage
     And I press "Continue"
     And I should see "Sample category" in the ".breadcrumb-nav" "css_element"
 
@@ -45,7 +45,7 @@ Feature: Browse course list and return back from enrolment page
     And I open my profile in edit mode
     And I expand "Courses" node
     And I expand "Sample category" node
-    And I follow "Course 1"
+    And I am on "Course 1" course homepage
     And I press "Continue"
     Then I should see "Edit profile" in the ".breadcrumb-nav" "css_element"
 
index 0f929bd..3e88929 100644 (file)
@@ -12,9 +12,9 @@ Feature: Course paged mode
     And I log in as "admin"
     And I am on "Course 1" course homepage
     Then I click on <section2> "link" in the <section2> "section"
-    And I follow "C1"
+    And I am on "Course 1" course homepage
     And I click on <section3> "link" in the <section3> "section"
-    And I follow "C1"
+    And I am on "Course 1" course homepage
     And I click on <section1> "link" in the <section1> "section"
     And I should see <section1> in the "div.single-section" "css_element"
     And I should see <section2> in the ".single-section span.mdl-right" "css_element"
@@ -45,9 +45,9 @@ Feature: Course paged mode
     And I log in as "admin"
     And I am on "Course 1" course homepage
     Then I click on <section2> "link" in the <section2> "section"
-    And I follow "C1"
+    And I am on "Course 1" course homepage
     And I click on <section3> "link" in the <section3> "section"
-    And I follow "C1"
+    And I am on "Course 1" course homepage
     And I click on <section1> "link" in the <section1> "section"
     And I should see <section1> in the "div.single-section" "css_element"
     And I should see <section2> in the ".single-section span.mdl-right" "css_element"
index 4478229..bc0f917 100644 (file)
@@ -96,8 +96,7 @@ Feature: Enrolments are synchronised with meta courses
     And I press "Next"
     And I press "Perform restore"
     And I trigger cron
-    And I am on course index
-    And I follow "Course 4"
+    And I am on "Course 4" course homepage
     And I navigate to "Enrolment methods" node in "Course administration > Users"
     Then I should see "Course meta link (Course 1)"
     And I should see "Course meta link (Course 2)"
index aa88075..294ee58 100644 (file)
@@ -12,7 +12,6 @@ Feature: User can be enrolled into a course
       | fullname   | shortname |
       | Course 001 | C001      |
     And I log in as "admin"
-    And I am on course index
     And I am on "Course 001" course homepage
 
   Scenario: User can be enrolled without javascript
index 13f1b75..b1535e0 100644 (file)
@@ -24,8 +24,7 @@ Feature: We can choose what min or max grade to use when aggregating grades.
     And I log in as "admin"
     And I set the following administration settings values:
       | grade_minmaxtouse | Min and max grades as specified in grade item settings |
-    And I am on site homepage
-    And I follow "C1"
+    And I am on "C1" course homepage
     And I navigate to "Setup > Gradebook setup" in the course gradebook
     And I press "Add grade item"
     And I set the following fields to these values:
@@ -66,7 +65,7 @@ Feature: We can choose what min or max grade to use when aggregating grades.
       | Aggregation          | Natural |
     And I log out
     And I log in as "teacher1"
-    And I follow "C1"
+    And I am on "C1" course homepage
     And I navigate to "View > Grader report" in the course gradebook
     And I turn editing mode on
     And I give the grade "75.00" to the user "Student 1" for the grade item "MI 1"
index acdaab3..62b8bf5 100644 (file)
@@ -728,6 +728,8 @@ class block_manager {
         $ccjoin = "LEFT JOIN {context} ctx ON (ctx.instanceid = bi.id AND ctx.contextlevel = :contextlevel)";
 
         $systemcontext = context_system::instance();
+        list($bpcontext, $bpcontextidparams) = $DB->get_in_or_equal(array($context->id, $systemcontext->id),
+                SQL_PARAMS_NAMED, 'bpcontextid');
         $params = array(
             'contextlevel' => CONTEXT_BLOCK,
             'subpage1' => $this->page->subpage,
@@ -761,7 +763,7 @@ class block_manager {
                 FROM {block_instances} bi
                 JOIN {block} b ON bi.blockname = b.name
                 LEFT JOIN {block_positions} bp ON bp.blockinstanceid = bi.id
-                                                  AND bp.contextid = :contextid1
+                                                  AND bp.contextid $bpcontext
                                                   AND bp.pagetype = :pagetype
                                                   AND bp.subpage = :subpage1
                 $ccjoin
@@ -779,7 +781,8 @@ class block_manager {
                     COALESCE(bp.weight, bi.defaultweight),
                     bi.id";
 
-        $allparams = $params + $parentcontextparams + $pagetypepatternparams + $requiredbythemeparams + $requiredbythemenotparams;
+        $allparams = $params + $parentcontextparams + $pagetypepatternparams + $requiredbythemeparams;
+        $allparams = $allparams + $requiredbythemenotparams + $bpcontextidparams;
         $blockinstances = $DB->get_recordset_sql($sql, $allparams);
 
         $this->birecordsbyregion = $this->prepare_per_region_arrays();
index 966ea3f..a27e605 100644 (file)
@@ -752,7 +752,7 @@ class api {
         $record->issuerid = $issuer->get('id');
         $record->refreshtoken = $refreshtoken;
         $record->grantedscopes = $scopes;
-        $record->email = $userinfo['email'];
+        $record->email = isset($userinfo['email']) ? $userinfo['email'] : '';
         $record->username = $userinfo['username'];
 
         $systemaccount = new system_account(0, $record);
index e947ca7..b83f318 100644 (file)
         <FIELD NAME="issuerid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="The id of the oauth 2 identity issuer"/>
         <FIELD NAME="refreshtoken" TYPE="text" NOTNULL="true" SEQUENCE="false" COMMENT="The refresh token used to request access tokens."/>
         <FIELD NAME="grantedscopes" TYPE="text" NOTNULL="true" SEQUENCE="false" COMMENT="The scopes that this system account has been granted access to."/>
-        <FIELD NAME="email" TYPE="text" NOTNULL="true" SEQUENCE="false" COMMENT="The email that was connected to this issuer."/>
+        <FIELD NAME="email" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="The email that was connected to this issuer."/>
         <FIELD NAME="username" TYPE="text" NOTNULL="true" SEQUENCE="false" COMMENT="The username that was connected as a system account to this issue."/>
       </FIELDS>
       <KEYS>
index ca24cd9..3120399 100644 (file)
@@ -2214,5 +2214,17 @@ function xmldb_main_upgrade($oldversion) {
         upgrade_main_savepoint(true, 2017072000.02);
     }
 
+    if ($oldversion < 2017072700.01) {
+        // Changing nullability of field email on table oauth2_system_account to null.
+        $table = new xmldb_table('oauth2_system_account');
+        $field = new xmldb_field('email', XMLDB_TYPE_TEXT, null, null, null, null, null, 'grantedscopes');
+
+        // Launch change of nullability for field email.
+        $dbman->change_field_notnull($table, $field);
+
+        // Main savepoint reached.
+        upgrade_main_savepoint(true, 2017072700.01);
+    }
+
     return true;
 }
index b3ecee8..efc610c 100644 (file)
@@ -329,13 +329,10 @@ class mysql_sql_generator extends sql_generator {
      * @return array of sql statements
      */
     public function getCreateTempTableSQL($xmldb_table) {
-        $engine = $this->mdb->get_dbengine();
         // Do we know collation?
         $collation = $this->mdb->get_dbcollation();
         $this->temptables->add_temptable($xmldb_table->getName());
 
-        $rowformat = $this->mdb->get_row_format_sql($engine, $collation);
-
         $sqlarr = parent::getCreateTableSQL($xmldb_table);
 
         // Let's inject the extra MySQL tweaks.
@@ -347,7 +344,7 @@ class mysql_sql_generator extends sql_generator {
                     if (strpos($collation, 'utf8_') === 0) {
                         $sqlarr[$i] .= " DEFAULT CHARACTER SET utf8";
                     }
-                    $sqlarr[$i] .= " DEFAULT COLLATE $collation $rowformat";
+                    $sqlarr[$i] .= " DEFAULT COLLATE $collation ROW_FORMAT=DYNAMIC";
                 }
             }
         }
index ffc7796..48debee 100644 (file)
@@ -88,6 +88,12 @@ class mariadb_native_moodle_database extends mysqli_native_moodle_database {
         return array('description'=>$this->mysqli->server_info, 'version'=>$version);
     }
 
+    protected function has_breaking_change_quoted_defaults() {
+        $version = $this->get_server_info()['version'];
+        // Breaking change since 10.2.7: MDEV-13132.
+        return version_compare($version, '10.2.7', '>=');
+    }
+
     /**
      * It is time to require transactions everywhere.
      *
index 15926ee..fee8b73 100644 (file)
@@ -715,7 +715,7 @@ class mysqli_native_moodle_database extends moodle_database {
                 $rawcolumn->numeric_scale            = null;
                 $rawcolumn->is_nullable              = $rawcolumn->null; unset($rawcolumn->null);
                 $rawcolumn->column_default           = $rawcolumn->default; unset($rawcolumn->default);
-                $rawcolumn->column_key               = $rawcolumn->key; unset($rawcolumn->default);
+                $rawcolumn->column_key               = $rawcolumn->key; unset($rawcolumn->key);
 
                 if (preg_match('/(enum|varchar)\((\d+)\)/i', $rawcolumn->column_type, $matches)) {
                     $rawcolumn->data_type = $matches[1];
@@ -783,6 +783,14 @@ class mysqli_native_moodle_database extends moodle_database {
         return $structure;
     }
 
+    /**
+     * Indicates whether column information retrieved from `information_schema.columns` has default values quoted or not.
+     * @return boolean True when default values are quoted (breaking change); otherwise, false.
+     */
+    protected function has_breaking_change_quoted_defaults() {
+        return false;
+    }
+
     /**
      * Returns moodle column info for raw column from information schema.
      * @param stdClass $rawcolumn
@@ -794,7 +802,11 @@ class mysqli_native_moodle_database extends moodle_database {
         $info->name           = $rawcolumn->column_name;
         $info->type           = $rawcolumn->data_type;
         $info->meta_type      = $this->mysqltype2moodletype($rawcolumn->data_type);
-        $info->default_value  = $rawcolumn->column_default;
+        if ($this->has_breaking_change_quoted_defaults()) {
+            $info->default_value = trim($rawcolumn->column_default, "'");
+        } else {
+            $info->default_value = $rawcolumn->column_default;
+        }
         $info->has_default    = !is_null($rawcolumn->column_default);
         $info->not_null       = ($rawcolumn->is_nullable === 'NO');
         $info->primary_key    = ($rawcolumn->column_key === 'PRI');
index 6074824..9c9c316 100644 (file)
@@ -3239,7 +3239,7 @@ EOD;
             array('role' => 'button', 'tabindex' => 0));
         $formattrs = array('class' => 'search-input-form', 'action' => $CFG->wwwroot . '/search/index.php');
         $inputattrs = array('type' => 'text', 'name' => 'q', 'placeholder' => get_string('search', 'search'),
-            'size' => 13, 'tabindex' => -1, 'id' => 'id_q_' . $id);
+            'size' => 13, 'tabindex' => -1, 'id' => 'id_q_' . $id, 'class' => 'form-control');
 
         $contents = html_writer::tag('label', get_string('enteryoursearchquery', 'search'),
             array('for' => 'id_q_' . $id, 'class' => 'accesshide')) . html_writer::tag('input', '', $inputattrs);
index 8a23482..938b07f 100644 (file)
@@ -17,8 +17,7 @@ Feature: Edited book chapters handle tags correctly
       | teacher1 | C1 | editingteacher |
       | student1 | C1 | student |
     And I log in as "teacher1"
-    And I am on "Course 1" course homepage
-    And I turn editing mode on
+    And I am on "Course 1" course homepage with editing mode on
     And I add a "Book" to section "1" and I fill the form with:
       | Name | Test book |
       | Description | A book about dreams! |
index e71c98c..65670ef 100644 (file)
@@ -596,7 +596,7 @@ class mod_data_external extends external_api {
 
         $params = array('databaseid' => $databaseid);
         $params = self::validate_parameters(self::get_fields_parameters(), $params);
-        $warnings = array();
+        $fields = $warnings = array();
 
         list($database, $course, $cm, $context) = self::validate_database($params['databaseid']);
 
index f7806eb..3e41e9e 100644 (file)
@@ -70,12 +70,12 @@ class content_exporter extends exporter {
                 'null' => NULL_ALLOWED,
             ),
             'content3' => array(
-                'type' => PARAM_BOOL,
+                'type' => PARAM_RAW,
                 'description' => 'Contents.',
                 'null' => NULL_ALLOWED,
             ),
             'content4' => array(
-                'type' => PARAM_BOOL,
+                'type' => PARAM_RAW,
                 'description' => 'Contents.',
                 'null' => NULL_ALLOWED,
             ),
index 89624ef..5be0b56 100644 (file)
@@ -1006,15 +1006,17 @@ function data_search_entries($data, $cm, $context, $mode, $currentgroup, $search
 
     $recordids = data_get_all_recordids($data->id, $initialselect, $initialparams);
     $newrecordids = data_get_advance_search_ids($recordids, $searcharray, $data->id);
-    $totalcount = count($newrecordids);
     $selectdata = $where . $groupselect . $approveselect;
 
     if (!empty($advanced)) {
         $advancedsearchsql = data_get_advanced_search_sql($sort, $data, $newrecordids, $selectdata, $sortorder);
         $sqlselect = $advancedsearchsql['sql'];
         $allparams = array_merge($allparams, $advancedsearchsql['params']);
+        $totalcount = count($newrecordids);
     } else {
         $sqlselect  = "SELECT $what $fromsql $sortorder";
+        $sqlcountselect  = "SELECT $count $fromsql $sortorder";
+        $totalcount = $DB->count_records_sql($sqlcountselect, $allparams);
     }
 
     // Work out the paging numbers and counts.
index d9da1db..4f74d2c 100644 (file)
@@ -17,8 +17,7 @@ Feature: Set entries required as a completion condition for a data item
       | teacher1 | C1 | editingteacher |
       | student1 | C1 | student |
     And I log in as "teacher1"
-    And I follow "C1"
-    And I turn editing mode on
+    And I am on "Course 1" course homepage with editing mode on
     And I add a "Database" to section "1" and I fill the form with:
       | Name | Test database name |
       | Description | Test database description |
@@ -26,38 +25,38 @@ Feature: Set entries required as a completion condition for a data item
       | completionview | 0 |
       | completionentriesenabled | checked |
       | completionentries        | 2 |
-    And I follow "Course 1"
+    And I am on "Course 1" course homepage
     And I add a "Text input" field to "Test database name" database and I fill the form with:
       | Field name | Test field name |
-    And I follow "C1"
+    And I am on "Course 1" course homepage
     And I log out
     When I log in as "student1"
-    And I follow "C1"
+    And I am on "Course 1" course homepage
     And I add an entry to "Test database name" database with:
       | Test field name | Student original entry |
     And I press "Save and view"
-    And I follow "C1"
+    And I am on "Course 1" course homepage
     And I log out
     And I log in as "teacher1"
-    And I follow "C1"
+    And I am on "Course 1" course homepage
     #One entry is not enough to mark as complete
     And "Student 1" user has not completed "Test database name" activity
     And I log out
     When I log in as "student1"
-    And I follow "C1"
+    And I am on "Course 1" course homepage
     And I add an entry to "Test database name" database with:
       | Test field name | Student second entry |
     And I press "Save and view"
     And I log out
     And I log in as "teacher1"
-    And I follow "C1"
+    And I am on "Course 1" course homepage
     Then "Student 1" user has completed "Test database name" activity
-    And I follow "Course 1"
+    And I am on "Course 1" course homepage
     And I follow "Test database name"
     And I navigate to "Edit settings" in current page administration
     And I press "Unlock completion"
     And I set the field "completionentries" to "1"
     And I press "Save and display"
-    And I follow "C1"
+    And I am on "Course 1" course homepage
     Then "Student 1" user has completed "Test database name" activity
     And I log out
index 1b61d09..5a37334 100644 (file)
@@ -659,6 +659,18 @@ class mod_data_external_testcase extends externallib_advanced_testcase {
         }
     }
 
+    /**
+     * Test get_fields_database_without_fields.
+     */
+    public function test_get_fields_database_without_fields() {
+
+        $this->setUser($this->student1);
+        $result = mod_data_external::get_fields($this->database->id);
+        $result = external_api::clean_returnvalue(mod_data_external::get_fields_returns(), $result);
+
+        $this->assertEmpty($result['fields']);
+    }
+
     /**
      * Test search_entries.
      */
@@ -666,8 +678,20 @@ class mod_data_external_testcase extends externallib_advanced_testcase {
         global $DB;
         list($entry11, $entry12, $entry13, $entry21) = self::populate_database_with_entries();
 
-        // First do a normal text search as student 1. I should see my two group entries.
         $this->setUser($this->student1);
+        // Empty search, it should return all the visible entries.
+        $result = mod_data_external::search_entries($this->database->id, 0, false);
+        $result = external_api::clean_returnvalue(mod_data_external::search_entries_returns(), $result);
+        $this->assertCount(2, $result['entries']);
+        $this->assertEquals(2, $result['totalcount']);
+
+        // Search for something that does not exists.
+        $result = mod_data_external::search_entries($this->database->id, 0, false, 'abc');
+        $result = external_api::clean_returnvalue(mod_data_external::search_entries_returns(), $result);
+        $this->assertCount(0, $result['entries']);
+        $this->assertEquals(0, $result['totalcount']);
+
+        // Search by text matching all the entries.
         $result = mod_data_external::search_entries($this->database->id, 0, false, 'text');
         $result = external_api::clean_returnvalue(mod_data_external::search_entries_returns(), $result);
         $this->assertCount(2, $result['entries']);
index 849b945..cfe497c 100644 (file)
@@ -247,7 +247,7 @@ Feature: Anonymous feedback
 
   Scenario: Collecting new non-anonymous feedback from a previously anonymous feedback activity
     When I log in as "teacher"
-    And I follow "Course 1"
+    And I am on "Course 1" course homepage
     And I follow "Course feedback"
     And I navigate to "Edit settings" in current page administration
     And I set the following fields to these values:
@@ -260,7 +260,7 @@ Feature: Anonymous feedback
       | Maximum characters accepted | 200                    |
     And I log out
     When I log in as "user1"
-    And I follow "Course 1"
+    And I am on "Course 1" course homepage
     And I follow "Course feedback"
     And I follow "Answer the questions..."
     And I set the following fields to these values:
@@ -269,7 +269,7 @@ Feature: Anonymous feedback
     And I log out
     # Switch to non-anon responses.
     And I log in as "teacher"
-    And I follow "Course 1"
+    And I am on "Course 1" course homepage
     And I follow "Course feedback"
     And I navigate to "Edit settings" in current page administration
     And I set the following fields to these values:
@@ -278,7 +278,7 @@ Feature: Anonymous feedback
     And I log out
     # Now leave a non-anon feedback as user1
     When I log in as "user1"
-    And I follow "Course 1"
+    And I am on "Course 1" course homepage
     And I follow "Course feedback"
     And I follow "Answer the questions..."
     And I set the following fields to these values:
@@ -287,7 +287,7 @@ Feature: Anonymous feedback
     And I log out
     # Now check the responses are correct.
     When I log in as "teacher"
-    And I follow "Course 1"
+    And I am on "Course 1" course homepage
     And I follow "Course feedback"
     And I follow "Show responses"
     And I should see "Anonymous entries (1)"
index 12fba55..d4a0315 100644 (file)
@@ -81,7 +81,7 @@ Feature: Mapping courses in a feedback
       | this is a simple multiple choice | option d |
     And I press "Submit your answers"
     And I press "Continue"
-    And I follow "Course 1"
+    And I am on "Course 1" course homepage
     And I click on "Course feedback" "link" in the "Feedback" "block"
     And I follow "Answer the questions..."
     And I should not see "Acceptance test site" in the ".feedback_form" "css_element"
index aa226bf..4087dd6 100644 (file)
@@ -25,7 +25,7 @@ Feature: Non anonymous feedback with multiple submissions
 
   Scenario: Completing a feedback second time
     When I log in as "teacher"
-    And I follow "Course 1"
+    And I am on "Course 1" course homepage
     And I follow "Course feedback"
     And I click on "Edit questions" "link" in the "[role=main]" "css_element"
     And I add a "Short text answer" question to the feedback with:
@@ -39,7 +39,7 @@ Feature: Non anonymous feedback with multiple submissions
       | Maximum characters accepted | 200        |
     And I log out
     And I log in as "user1"
-    And I follow "Course 1"
+    And I am on "Course 1" course homepage
     And I follow "Course feedback"
     And I follow "Answer the questions..."
     And I set the following fields to these values:
@@ -50,7 +50,7 @@ Feature: Non anonymous feedback with multiple submissions
     And I press "Submit your answers"
     And I log out
     And I log in as "user1"
-    And I follow "Course 1"
+    And I am on "Course 1" course homepage
     And I follow "Course feedback"
     And I follow "Answer the questions..."
     Then the field "first" matches value "111"
index 91c5105..95a227b 100644 (file)
@@ -67,7 +67,6 @@ Feature: Blog posts are always displayed in reverse chronological order
       | Message | Reply to the first post |
     And I press "Post to forum"
     And I wait to be redirected
-    And I am on site homepage
     And I am on "Course 1" course homepage
     And I follow "Course blog forum"
     #
index 61d7c65..3812ace 100644 (file)
@@ -17,8 +17,7 @@ Feature: Edited glossary entries handle tags correctly
       | teacher1 | C1 | editingteacher |
       | student1 | C1 | student |
     And I log in as "teacher1"
-    And I am on "Course 1" course homepage
-    And I turn editing mode on
+    And I am on "Course 1" course homepage with editing mode on
     And I add a "Glossary" to section "1" and I fill the form with:
       | Name | Test glossary |
       | Description | A glossary about dreams! |
index 53ff09f..3cbc37b 100644 (file)
@@ -20,8 +20,7 @@ Feature: In a lesson activity, a teacher can duplicate a lesson page
     And I follow "Manage private files"
     And I upload "mod/lesson/tests/fixtures/moodle_logo.jpg" file to "Files" filemanager
     And I click on "Save changes" "button"
-    When I am on homepage
-    And I am on "Course 1" course homepage with editing mode on
+    When I am on "Course 1" course homepage with editing mode on
     And I add a "Lesson" to section "1" and I fill the form with:
       | Name | Test lesson name |
       | Description | Test lesson description |
index 9fa478c..80d26e1 100644 (file)
@@ -17,8 +17,7 @@ Feature: In a lesson activity, teacher can import blackboard fill in the blank q
       | teacher1 | C1 | editingteacher |
       | student1 | C1 | student |
     And I log in as "teacher1"
-    When I am on homepage
-    And I am on "Course 1" course homepage with editing mode on
+    When I am on "Course 1" course homepage with editing mode on
     And I add a "Lesson" to section "1" and I fill the form with:
       | Name | Test lesson name |
       | Description | Test lesson description |
index c9f996c..88adddc 100644 (file)
@@ -17,8 +17,7 @@ Feature: In a lesson activity, teacher can import embedded images in questions a
       | teacher1 | C1 | editingteacher |
       | student1 | C1 | student |
     And I log in as "teacher1"
-    When I am on homepage
-    And I am on "Course 1" course homepage with editing mode on
+    When I am on "Course 1" course homepage with editing mode on
     And I add a "Lesson" to section "1" and I fill the form with:
       | Name | Test lesson name |
       | Description | Test lesson description |
index ce3af1a..0dc725c 100644 (file)
@@ -20,8 +20,7 @@ Feature: In a lesson activity, teacher can add embedded images in questions answ
     And I follow "Manage private files"
     And I upload "mod/lesson/tests/fixtures/moodle_logo.jpg" file to "Files" filemanager
     And I click on "Save changes" "button"
-    When I am on homepage
-    And I am on "Course 1" course homepage with editing mode on
+    When I am on "Course 1" course homepage with editing mode on
     And I add a "Lesson" to section "1" and I fill the form with:
       | Name | Test lesson name |
       | Description | Test lesson description |
index 615c66f..6a97bd1 100644 (file)
@@ -514,6 +514,11 @@ class mod_quiz_mod_form extends moodleform_mod {
                 $toform[$name] = $value;
             }
         }
+
+        // Completion settings check.
+        if (empty($toform['completionusegrade'])) {
+            $toform['completionpass'] = 0; // Forced unchecked.
+        }
     }
 
     public function validation($data, $files) {
@@ -618,7 +623,7 @@ class mod_quiz_mod_form extends moodleform_mod {
         $group = array();
         $group[] = $mform->createElement('advcheckbox', 'completionpass', null, get_string('completionpass', 'quiz'),
                 array('group' => 'cpass'));
-
+        $mform->disabledIf('completionpass', 'completionusegrade', 'notchecked');
         $group[] = $mform->createElement('advcheckbox', 'completionattemptsexhausted', null,
                 get_string('completionattemptsexhausted', 'quiz'),
                 array('group' => 'cattempts'));
index 735b7ed..58e1399 100644 (file)
@@ -40,14 +40,14 @@ Feature: Set a quiz to be marked complete when the student uses all attempts all
     And I set the field "False" to "1"
     And I press "Finish attempt ..."
     And I press "Submit all and finish"
-    And I follow "C1"
+    And I am on "Course 1" course homepage
     And the "Test quiz name" "quiz" activity with "auto" completion should be marked as not complete
     And I follow "Test quiz name"
     And I press "Re-attempt quiz"
     And I set the field "False" to "1"
     And I press "Finish attempt ..."
     And I press "Submit all and finish"
-    And I follow "C1"
+    And I am on "Course 1" course homepage
     Then "Completed: Test quiz name" "icon" should exist in the "li.modtype_quiz" "css_element"
     And I log out
     And I log in as "teacher1"
index f8e783c..7afbec5 100644 (file)
@@ -40,7 +40,7 @@ Feature: Set a quiz to be marked complete when the student passes
     And I set the field "True" to "1"
     And I press "Finish attempt ..."
     And I press "Submit all and finish"
-    And I follow "C1"
+    And I am on "Course 1" course homepage
     Then "Completed: Test quiz name" "icon" should exist in the "li.modtype_quiz" "css_element"
     And I log out
     And I log in as "teacher1"
index a125f5d..615d18d 100644 (file)
@@ -36,6 +36,7 @@
 
 .search-input-wrapper > form > input {
     margin: 0;
+    height: 23px;
 }
 
 .search-input-wrapper form.expanded {
index 0e9ae10..d5eed25 100644 (file)
 
 require_once("../config.php");
 
-$formaction = required_param('formaction', PARAM_FILE);
+$formaction = required_param('formaction', PARAM_LOCALURL);
 $id = required_param('id', PARAM_INT);
 
 $PAGE->set_url('/user/action_redir.php', array('formaction' => $formaction, 'id' => $id));
+list($formaction) = explode('?', $formaction, 2);
 
 // Add every page will be redirected by this script.
 $actions = array(
         'messageselect.php',
         'addnote.php',
         'groupaddnote.php',
+        'bulkchange.php'
         );
 
 if (array_search($formaction, $actions) === false) {
@@ -44,4 +46,120 @@ if (!confirm_sesskey()) {
     print_error('confirmsesskeybad');
 }
 
-require_once($formaction);
+if ($formaction == 'bulkchange.php') {
+    // Backwards compatibility for enrolment plugins bulk change functionality.
+    // This awful code is adapting from the participant page with it's param names and values
+    // to the values expected by the bulk enrolment changes forms.
+    $formaction = required_param('formaction', PARAM_URL);
+    require_once($CFG->dirroot . '/enrol/locallib.php');
+
+    $url = new moodle_url($formaction);
+    // Get the enrolment plugin type and bulk action from the url.
+    $plugin = $url->param('plugin');
+    $operationname = $url->param('operation');
+
+    $course = $DB->get_record('course', array('id' => $id), '*', MUST_EXIST);
+    $context = context_course::instance($id);
+    $PAGE->set_context($context);
+
+    $instances = enrol_get_instances($course->id, false);
+    $instance = false;
+    foreach ($instances as $oneinstance) {
+        if ($oneinstance->enrol == $plugin) {
+            $instance = $oneinstance;
+            break;
+        }
+    }
+    if (!$instance) {
+        print_error('errorwithbulkoperation', 'enrol');
+    }
+
+    $manager = new course_enrolment_manager($PAGE, $course, $instance->id);
+    $plugins = $manager->get_enrolment_plugins();
+
+    if (!isset($plugins[$plugin])) {
+        print_error('errorwithbulkoperation', 'enrol');
+    }
+
+    $plugin = $plugins[$plugin];
+
+    $operations = $plugin->get_bulk_operations($manager);
+
+    if (!isset($operations[$operationname])) {
+        print_error('errorwithbulkoperation', 'enrol');
+    }
+    $operation = $operations[$operationname];
+
+    $userids = optional_param_array('userid', array(), PARAM_INT);
+    $default = new moodle_url('/user/index.php', ['id' => $course->id]);
+    $returnurl = new moodle_url(optional_param('returnto', $default, PARAM_URL));
+
+    if (empty($userids)) {
+        $userids = optional_param_array('bulkuser', array(), PARAM_INT);
+    }
+    if (empty($userids)) {
+        // The first time list hack.
+        if (empty($userids) and $post = data_submitted()) {
+            foreach ($post as $k => $v) {
+                if (preg_match('/^user(\d+)$/', $k, $m)) {
+                    $userids[] = $m[1];
+                }
+            }
+        }
+    }
+
+    if (empty($userids)) {
+        redirect($returnurl, get_string('noselectedusers', 'bulkusers'));
+    }
+
+    $users = $manager->get_users_enrolments($userids);
+
+    // We may have users from any kind of enrolment, we need to filter for the enrolment plugin matching the bulk action.
+    $matchesplugin = function($user) use ($plugin) {
+        foreach ($user->enrolments as $enrolment) {
+            if ($enrolment->enrolmentplugin->get_name() == $plugin->get_name()) {
+                return true;
+            }
+        }
+        return false;
+    };
+    $users = array_filter($users, $matchesplugin);
+
+    if (empty($users)) {
+        redirect($returnurl, get_string('noselectedusers', 'bulkusers'));
+    }
+
+    // Get the form for the bulk operation.
+    $mform = $operation->get_form($PAGE->url, array('users' => $users));
+    // If the mform is false then attempt an immediate process. This may be an immediate action that
+    // doesn't require user input OR confirmation.... who know what but maybe one day.
+    if ($mform === false) {
+        if ($operation->process($manager, $users, new stdClass)) {
+            redirect($returnurl);
+        } else {
+            print_error('errorwithbulkoperation', 'enrol');
+        }
+    }
+    // Check if the bulk operation has been cancelled.
+    if ($mform->is_cancelled()) {
+        redirect($returnurl);
+    }
+    if ($mform->is_submitted() && $mform->is_validated() && confirm_sesskey()) {
+        if ($operation->process($manager, $users, $mform->get_data())) {
+            redirect($returnurl);
+        }
+    }
+
+    $pagetitle = get_string('bulkuseroperation', 'enrol');
+
+    $PAGE->set_title($pagetitle);
+    $PAGE->set_heading($pagetitle);
+    echo $OUTPUT->header();
+    echo $OUTPUT->heading($operation->get_title());
+    $mform->display();
+    echo $OUTPUT->footer();
+    exit();
+
+} else {
+    require_once($formaction);
+}
index f5edc82..b83eb53 100644 (file)
@@ -363,6 +363,22 @@ if ($bulkoperations) {
         $displaylist['groupaddnote.php'] = get_string('groupaddnewnote', 'notes');
     }
 
+    $plugins = $manager->get_enrolment_plugins();
+    foreach ($plugins as $plugin) {
+        $bulkoperations = $plugin->get_bulk_operations($manager);
+
+        $pluginoptions = [];
+        foreach ($bulkoperations as $key => $bulkoperation) {
+            $params = ['plugin' => $plugin->get_name(), 'operation' => $key];
+            $url = new moodle_url('bulkchange.php', $params);
+            $pluginoptions[$url->out(false)] = $bulkoperation->get_title();
+        }
+        if (!empty($pluginoptions)) {
+            $name = get_string('pluginname', 'enrol_' . $plugin->get_name());
+            $displaylist[] = [$name => $pluginoptions];
+        }
+    }
+
     echo $OUTPUT->help_icon('withselectedusers');
     echo html_writer::tag('label', get_string("withselectedusers"), array('for' => 'formactionid'));
     echo html_writer::select($displaylist, 'formaction', '', array('' => 'choosedots'), array('id' => 'formactionid'));
diff --git a/user/tests/behat/bulk_editenrolment.feature b/user/tests/behat/bulk_editenrolment.feature
new file mode 100644 (file)
index 0000000..7a85f72
--- /dev/null
@@ -0,0 +1,45 @@
+@core @core_user
+Feature: Bulk enrolments
+  In order to manage a course site
+  As a teacher
+  I need to be able to bulk edit enrolments
+
+  Background:
+    Given the following "users" exist:
+      | username | firstname | lastname | email |
+      | student1 | Student | 1 | student1@example.com |
+      | student2 | Student | 2 | student2@example.com |
+      | teacher1 | Teacher | 1 | teacher1@example.com |
+    And the following "courses" exist:
+      | fullname | shortname | format |
+      | Course 1 | C1 | topics |
+    And the following "course enrolments" exist:
+      | user | course | role |
+      | student1 | C1 | student |
+      | student2 | C1 | student |
+      | teacher1 | C1 | editingteacher |
+
+  @javascript
+  Scenario: Bulk edit enrolments
+    When I log in as "admin"
+    And I am on "Course 1" course homepage
+    And I follow "Participants"
+    And I press "Select all"
+    And I set the field "With selected users..." to "Edit selected user enrolments"
+    And I set the field "Alter status" to "Suspended"
+    And I press "Save changes"
+    Then I should see "Suspended" in the "Teacher 1" "table_row"
+    Then I should see "Suspended" in the "Student 1" "table_row"
+    And I should see "Suspended" in the "Student 2" "table_row"
+
+  @javascript
+  Scenario: Bulk delete enrolments
+    When I log in as "admin"
+    And I am on "Course 1" course homepage
+    And I follow "Participants"
+    And I press "Select all"
+    And I set the field "With selected users..." to "Delete selected user enrolments"
+    And I press "Unenrol users"
+    Then I should not see "Student 1"
+    And I should not see "Student 2"
+    And I should not see "Teacher 1"
index c14ca65..6d0ed76 100644 (file)
@@ -17,8 +17,7 @@ Feature: As a user, "Course preferences" allows me to set my course preference(s
     # See that the "activity chooser" is enabled by default.
     Given the field "enableactivitychooser" matches value "1"
     # See that the "activity chooser" is actually shown by default in course page.
-    When I am on homepage
-    And I am on "Course 1" course homepage
+    When I am on "Course 1" course homepage
     And I should not see "Add an activity or resource" in the "Topic 1" "section"
     And I turn editing mode on
     Then I should see "Add an activity or resource" in the "Topic 1" "section"
@@ -28,8 +27,7 @@ Feature: As a user, "Course preferences" allows me to set my course preference(s
   Scenario: As a user, "activity chooser" should be disabled when I uncheck it in "Course preferences"
     Given I set the field "enableactivitychooser" to "0"
     And I press "Save changes"
-    When I am on homepage
-    And I am on "Course 1" course homepage
+    When I am on "Course 1" course homepage
     And I should not see "Add a resource..." in the "Topic 1" "section"
     And I turn editing mode on
     Then I should see "Add a resource..." in the "Topic 1" "section"
index 58d99cc..8da6610 100644 (file)
@@ -33,6 +33,5 @@ Feature: Set the site home page and dashboard as the default home page
     And I follow "Dashboard"
     And I follow "Make this my default home page"
     And I should not see "Make this my default home page"
-    And I am on site homepage
-    When I am on "Course 1" course homepage
+    And I am on "Course 1" course homepage
     Then "Dashboard" "text" should exist in the ".breadcrumb-nav" "css_element"
index 7ae262a..cf1bb36 100644 (file)
@@ -29,7 +29,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$version  = 2017072700.00;              // YYYYMMDD      = weekly release date of this DEV branch.
+$version  = 2017072700.01;              // YYYYMMDD      = weekly release date of this DEV branch.
                                         //         RR    = release increments - 00 in DEV branches.
                                         //           .XX = incremental changes.