Merge branch 'MDL-58196-master' of git://github.com/ankitagarwal/moodle
authorAndrew Nicols <andrew@nicols.co.uk>
Mon, 31 Jul 2017 07:17:49 +0000 (15:17 +0800)
committerAndrew Nicols <andrew@nicols.co.uk>
Mon, 31 Jul 2017 07:17:49 +0000 (15:17 +0800)
admin/tool/analytics/classes/output/models_list.php
admin/tool/analytics/model.php
analytics/classes/model.php
lib/blocklib.php
lib/dml/mariadb_native_moodle_database.php
lib/dml/mysqli_native_moodle_database.php
mod/data/classes/external/content_exporter.php
user/action_redir.php
user/index.php
user/tests/behat/bulk_editenrolment.feature [new file with mode: 0644]

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 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 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 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 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 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"