diff --git a/lang/en/deprecated.txt b/lang/en/deprecated.txt
index b912982899b..c7b255937e6 100644
--- a/lang/en/deprecated.txt
+++ b/lang/en/deprecated.txt
@@ -159,3 +159,7 @@ privacy,core_hub
privacy_help,core_hub
configloginhttps,core_admin
loginhttps,core_admin
+groupaddnewnote,core_notes
+selectnotestate,core_notes
+extendenrol,core
+groupextendenrol,core
diff --git a/lang/en/message.php b/lang/en/message.php
index 64a7588c064..91eac4707d9 100644
--- a/lang/en/message.php
+++ b/lang/en/message.php
@@ -111,6 +111,10 @@ $string['send'] = 'Send';
$string['sendingvia'] = 'Sending "{$a->provider}" via "{$a->processor}"';
$string['sendingviawhen'] = 'Sending "{$a->provider}" via "{$a->processor}" when {$a->state}';
$string['sendmessage'] = 'Send message';
+$string['sendbulkmessage'] = 'Send message to {$a} people';
+$string['sendbulkmessagesingle'] = 'Send message to 1 person';
+$string['sendbulkmessagesent'] = 'Message sent to {$a} people';
+$string['sendbulkmessagesentsingle'] = 'Message sent to 1 person';
$string['sendmessageto'] = 'Send message to {$a}';
$string['sendmessagetopopup'] = 'Send message to {$a} - new window';
$string['settings'] = 'Settings';
diff --git a/lang/en/moodle.php b/lang/en/moodle.php
index 71e29cd774b..f1eb2524416 100644
--- a/lang/en/moodle.php
+++ b/lang/en/moodle.php
@@ -793,7 +793,6 @@ $string['expand'] = 'Expand';
$string['expandall'] = 'Expand all';
$string['expandcategory'] = 'Expand {$a}';
$string['explanation'] = 'Explanation';
-$string['extendenrol'] = 'Extend enrolment (individual)';
$string['extendperiod'] = 'Extended period';
$string['failedloginattempts'] = '{$a->attempts} failed logins since your last login';
$string['feedback'] = 'Feedback';
@@ -904,7 +903,6 @@ $string['gravatarenabled'] = 'Gravatar ha
$string['group'] = 'Group';
$string['groupadd'] = 'Add new group';
$string['groupaddusers'] = 'Add selected to group';
-$string['groupextendenrol'] = 'Extend enrolment (common)';
$string['groupfor'] = 'for group';
$string['groupinfo'] = 'Info about selected group';
$string['groupinfoedit'] = 'Edit group settings';
@@ -2165,3 +2163,5 @@ $string['sectionusedefaultname'] = 'Use default section name';
// Deprecated since Moodle 3.4.
$string['publish'] = 'Publish';
+$string['extendenrol'] = 'Extend enrolment (individual)';
+$string['groupextendenrol'] = 'Extend enrolment (common)';
diff --git a/lang/en/notes.php b/lang/en/notes.php
index 1146eac9a9b..c2b388dedff 100644
--- a/lang/en/notes.php
+++ b/lang/en/notes.php
@@ -24,6 +24,10 @@
*/
$string['addnewnote'] = 'Add a new note';
+$string['addbulknote'] = 'Add a new note to {$a} people';
+$string['addbulknotesingle'] = 'Add a new note to 1 person';
+$string['addbulknotedone'] = 'Note added to {$a} people';
+$string['addbulknotedonesingle'] = 'Note added to 1 person';
$string['addnewnoteselect'] = 'Select users to write notes about';
$string['bynameondate'] = 'by {$a->name} - {$a->date}';
$string['configenablenotes'] = 'Enable storing of notes about individual users.';
@@ -39,7 +43,6 @@ $string['eventnotecreated'] = 'Note created';
$string['eventnoteupdated'] = 'Note updated';
$string['eventnotedeleted'] = 'Note deleted';
$string['eventnotesviewed'] = 'Notes viewed';
-$string['groupaddnewnote'] = 'Add a common note';
$string['invalidid'] = 'Invalid note ID specified';
$string['invaliduserid'] = 'Invalid user id: {$a}';
$string['myprofileownnotes'] = 'My notes';
@@ -61,8 +64,10 @@ $string['publishstate_help'] = 'A note\'s context determines who can see the not
* Personal - The note will be visible only to you
* Course - The note will be visible to teachers in this course
* Site - The note will be visible to teachers in all courses';
-$string['selectnotestate'] = "Select note state";
$string['site'] = 'site';
$string['sitenotes'] = 'Site notes';
$string['unknown'] = 'unknown';
+// Deprecated since Moodle 3.4
+$string['groupaddnewnote'] = 'Add a common note';
+$string['selectnotestate'] = "Select note state";
diff --git a/lib/amd/build/auto_rows.min.js b/lib/amd/build/auto_rows.min.js
index 30ec60f6a82..5666dc3d1a1 100644
--- a/lib/amd/build/auto_rows.min.js
+++ b/lib/amd/build/auto_rows.min.js
@@ -1 +1 @@
-define(["jquery"],function(a){var b={ELEMENT:"[data-auto-rows]"},c={ROW_CHANGE:"autorows:rowchange"},d=function(a){var b=a.attr("rows"),c=a.attr("data-max-rows"),d=a.height(),e=a.innerHeight(),f=e-d;a.height("1px");var g=a[0].scrollHeight,h=(g-f)/(d/b);return a.css("height",""),c&&h>=c?c:h},e=function(e){a(e).on("input propertychange",b.ELEMENT,function(b){var f=a(b.target),g=f.attr("rows"),h=d(f);h!=g&&(f.attr("rows",h),a(e).trigger(c.ROW_CHANGE))})};return{init:e,events:c}});
\ No newline at end of file
+define(["jquery"],function(a){var b={ELEMENT:"[data-auto-rows]"},c={ROW_CHANGE:"autorows:rowchange"},d=function(a){var b=a.attr("rows"),c=a.data("min-rows"),d=a.attr("data-max-rows"),e=a.height(),f=a.innerHeight(),g=f-e;a.height("1px");var h=a[0].scrollHeight,i=(h-g)/(e/b);return a.css("height",""),i=d?d:i},e=function(b){var e=a(b.target),f=e.data("min-rows"),g=e.attr("rows");"undefined"==typeof f&&e.data("min-rows",g);var h=d(e);h!=g&&(e.attr("rows",h),e.trigger(c.ROW_CHANGE))},f=function(c){a(c).data("auto-rows")?a(c).on("input propertychange",e.bind(this)):a(c).on("input propertychange",b.ELEMENT,e.bind(this))};return{init:f,events:c}});
\ No newline at end of file
diff --git a/lib/amd/src/auto_rows.js b/lib/amd/src/auto_rows.js
index 1532ad2d791..ab7a876469a 100644
--- a/lib/amd/src/auto_rows.js
+++ b/lib/amd/src/auto_rows.js
@@ -42,6 +42,7 @@ define(['jquery'], function($) {
*/
var calculateRows = function(element) {
var currentRows = element.attr('rows');
+ var minRows = element.data('min-rows');
var maxRows = element.attr('data-max-rows');
var height = element.height();
@@ -58,13 +59,38 @@ define(['jquery'], function($) {
// based on the row attribute.
element.css('height', '');
- if (maxRows && rows >= maxRows) {
+ if (rows < minRows) {
+ return minRows;
+ } else if (maxRows && rows >= maxRows) {
return maxRows;
} else {
return rows;
}
};
+ /**
+ * Listener for change events to trigger resizing of the element.
+ *
+ * @method changeListener
+ * @param {Event} e The triggered event.
+ * @private
+ */
+ var changeListener = function(e) {
+ var element = $(e.target);
+ var minRows = element.data('min-rows');
+ var currentRows = element.attr('rows');
+
+ if (typeof minRows === "undefined") {
+ element.data('min-rows', currentRows);
+ }
+ var rows = calculateRows(element);
+
+ if (rows != currentRows) {
+ element.attr('rows', rows);
+ element.trigger(EVENTS.ROW_CHANGE);
+ }
+ };
+
/**
* Add the event listeners for all text areas within the given element.
*
@@ -73,16 +99,11 @@ define(['jquery'], function($) {
* @public
*/
var init = function(root) {
- $(root).on('input propertychange', SELECTORS.ELEMENT, function(e) {
- var element = $(e.target);
- var currentRows = element.attr('rows');
- var rows = calculateRows(element);
-
- if (rows != currentRows) {
- element.attr('rows', rows);
- $(root).trigger(EVENTS.ROW_CHANGE);
- }
- });
+ if ($(root).data('auto-rows')) {
+ $(root).on('input propertychange', changeListener.bind(this));
+ } else {
+ $(root).on('input propertychange', SELECTORS.ELEMENT, changeListener.bind(this));
+ }
};
return /** @module core/auto_rows */ {
diff --git a/lib/db/services.php b/lib/db/services.php
index 34425f6262a..3bef679ab53 100644
--- a/lib/db/services.php
+++ b/lib/db/services.php
@@ -1034,6 +1034,7 @@ $functions = array(
'classpath' => 'notes/externallib.php',
'description' => 'Create notes',
'type' => 'write',
+ 'ajax' => true,
'capabilities' => 'moodle/notes:manage',
'services' => array(MOODLE_OFFICIAL_MOBILE_SERVICE),
),
diff --git a/notes/lib.php b/notes/lib.php
index b21f1f6514e..041e61392ba 100644
--- a/notes/lib.php
+++ b/notes/lib.php
@@ -113,6 +113,11 @@ function note_save(&$note) {
if (empty($note->publishstate)) {
$note->publishstate = NOTES_STATE_PUBLIC;
}
+
+ if (empty(trim($note->content))) {
+ // Don't save empty notes.
+ return false;
+ }
// Save data.
if (empty($note->id)) {
// Insert new note.
diff --git a/notes/tests/behat/participants_notes.feature b/notes/tests/behat/participants_notes.feature
index aae0dc5cf6c..58f7c2a0f2d 100644
--- a/notes/tests/behat/participants_notes.feature
+++ b/notes/tests/behat/participants_notes.feature
@@ -1,4 +1,4 @@
-@core @core_notes
+@core @core_notes @javascript
Feature: Add notes to course participants
In order to share information with other staff
As a teacher
@@ -34,15 +34,19 @@ Feature: Add notes to course participants
And I am on "Course 1" course homepage
And I follow "Participants"
And I set the field with xpath "//tr[contains(normalize-space(.), 'Student 1')]//input[@type='checkbox']" to "1"
+ And I choose "Add a new note" from the participants page bulk action menu
+ And I set the field "bulk-note" to "Student 1 needs to pick up his game"
+ And I press "Add a new note to 1 person"
+ And I set the field with xpath "//tr[contains(normalize-space(.), 'Student 1')]//input[@type='checkbox']" to "0"
And I set the field with xpath "//tr[contains(normalize-space(.), 'Student 2')]//input[@type='checkbox']" to "1"
+ And I choose "Add a new note" from the participants page bulk action menu
+ And I set the field "bulk-note" to ""
+ And I press "Add a new note to 1 person"
+ And I set the field with xpath "//tr[contains(normalize-space(.), 'Student 2')]//input[@type='checkbox']" to "0"
And I set the field with xpath "//tr[contains(normalize-space(.), 'Student 3')]//input[@type='checkbox']" to "1"
- And I set the field "With selected users..." to "Add a new note"
- And I press "OK"
- # Add a note to student 1, but leave student 2 empty and student 3 with space.
- When I set the field with xpath "//tr[contains(normalize-space(.), 'Student 1')]//textarea" to "Student 1 needs to pick up his game"
- And I set the field with xpath "//tr[contains(normalize-space(.), 'Student 2')]//textarea" to ""
- And I set the field with xpath "//tr[contains(normalize-space(.), 'Student 3')]//textarea" to " "
- And I press "Save changes"
+ And I choose "Add a new note" from the participants page bulk action menu
+ And I set the field "bulk-note" to " "
+ And I press "Add a new note to 1 person"
And I follow "Student 1"
And I follow "Notes"
# Student 1 has note from Teacher
diff --git a/report/participation/index.php b/report/participation/index.php
index 0400e6e5037..ef66eef7fd7 100644
--- a/report/participation/index.php
+++ b/report/participation/index.php
@@ -25,6 +25,7 @@
require('../../config.php');
require_once($CFG->dirroot.'/lib/tablelib.php');
+require_once($CFG->dirroot.'/notes/lib.php');
require_once($CFG->dirroot.'/report/participation/locallib.php');
define('DEFAULT_PAGE_SIZE', 20);
@@ -336,7 +337,7 @@ if (!empty($instanceid) && !empty($roleid)) {
echo '
'.get_string('counteditems', '', $a).'
'."\n";
if (!empty($CFG->messaging)) {
- echo ''."\n";
- $PAGE->requires->js_init_call('M.report_participation.init');
+ $options = new stdClass();
+ $options->courseid = $course->id;
+ $options->noteStateNames = note_get_state_names();
+ $options->stateHelpIcon = $OUTPUT->help_icon('publishstate', 'notes');
+ $PAGE->requires->js_call_amd('core_user/participants', 'init', [$options]);
}
echo ''."\n";
}
diff --git a/report/participation/module.js b/report/participation/module.js
deleted file mode 100644
index d3984392c17..00000000000
--- a/report/participation/module.js
+++ /dev/null
@@ -1,44 +0,0 @@
-
-M.report_participation = {};
-
-M.report_participation.init = function(Y) {
-
- Y.on('submit', function(e) {
- Y.one('#formactionselect').get('options').each(function() {
- if (this.get('selected') && this.get('value') == '') {
- // no action selected
- e.preventDefault();
- }
- });
- var ok = false;
- Y.all('input.usercheckbox').each(function() {
- if (this.get('checked')) {
- ok = true;
- }
- });
- if (!ok) {
- // no checkbox selected
- e.preventDefault();
- }
- }, '#studentsform');
-
- Y.on('click', function(e) {
- Y.all('input.usercheckbox').each(function() {
- this.set('checked', 'checked');
- });
- }, '#checkall');
-
- Y.on('click', function(e) {
- Y.all('input.usercheckbox').each(function() {
- this.set('checked', '');
- });
- }, '#checknone');
-
- Y.on('click', function(e) {
- Y.all('input.usercheckbox').each(function() {
- if (this.get('value') == 0) {
- this.set('checked', 'checked');
- }
- });
- }, '#checknos');
-};
\ No newline at end of file
diff --git a/report/participation/tests/behat/message_participants.feature b/report/participation/tests/behat/message_participants.feature
index 88aae4ed420..a9a9d7e56d8 100644
--- a/report/participation/tests/behat/message_participants.feature
+++ b/report/participation/tests/behat/message_participants.feature
@@ -46,12 +46,8 @@ Feature: Use the particiaption report to message groups of students
And I should see "No" in the "Student 2" "table_row"
And I should see "No" in the "Student 3" "table_row"
When I press "Select all 'No'"
- And I set the field "With selected users..." to "Send a message"
- And I press "OK"
- Then I should see "Added 2 new recipients"
- And I should see "Student 2" in the "Currently selected users" "table"
- And I should see "Student 3" in the "Currently selected users" "table"
- And I should not see "Student 1" in the "Currently selected users" "table"
+ And I choose "Send a message" from the participants page bulk action menu
+ Then I should see "Send message to 2 people"
Scenario: Ensure no message options when messaging is disabled
Given I log in as "admin"
diff --git a/user/action_redir.php b/user/action_redir.php
index 2401cec5139..53f6618fe28 100644
--- a/user/action_redir.php
+++ b/user/action_redir.php
@@ -30,13 +30,8 @@ $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'
- );
+// This page now only handles the bulk enrolment change actions, other actions are done with ajax.
+$actions = array('bulkchange.php');
if (array_search($formaction, $actions) === false) {
print_error('unknownuseraction');
@@ -175,5 +170,5 @@ if ($formaction == 'bulkchange.php') {
exit();
} else {
- require_once($formaction);
+ throw new coding_exception('invalidaction');
}
diff --git a/user/addnote.php b/user/addnote.php
deleted file mode 100644
index d4f764be86e..00000000000
--- a/user/addnote.php
+++ /dev/null
@@ -1,118 +0,0 @@
-.
-
-/**
- * This file allows you to add a note for a user
- *
- * @copyright 1999 Martin Dougiamas http://dougiamas.com
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- * @package core_user
- */
-
-require_once("../config.php");
-require_once($CFG->dirroot .'/notes/lib.php');
-
-$id = required_param('id', PARAM_INT); // Course id.
-$users = optional_param_array('userid', array(), PARAM_INT); // Array of user id.
-$contents = optional_param_array('contents', array(), PARAM_RAW); // Array of user notes.
-$states = optional_param_array('states', array(), PARAM_ALPHA); // Array of notes states.
-$PAGE->set_url('/user/addnote.php', array('id' => $id));
-
-if (! $course = $DB->get_record('course', array('id' => $id))) {
- print_error('invalidcourseid');
-}
-
-$context = context_course::instance($id);
-require_login($course);
-
-// To create notes the current user needs a capability.
-require_capability('moodle/notes:manage', $context);
-
-if (empty($CFG->enablenotes)) {
- print_error('notesdisabled', 'notes');
-}
-
-if (!empty($users) && confirm_sesskey()) {
- if (count($users) != count($contents) || count($users) != count($states)) {
- print_error('invalidformdata', '', $CFG->wwwroot.'/user/index.php?id='.$id);
- }
-
- $note = new stdClass();
- $note->courseid = $id;
- $note->format = FORMAT_PLAIN;
- foreach ($users as $k => $v) {
- $user = $DB->get_record('user', array('id' => $v));
- $content = trim($contents[$k]);
- if (!$user || empty($content)) {
- continue;
- }
- $note->id = 0;
- $note->content = $content;
- $note->publishstate = $states[$k];
- $note->userid = $v;
- note_save($note);
- }
- redirect("$CFG->wwwroot/user/index.php?id=$id");
-}
-
-// Print headers.
-$straddnote = get_string('addnewnote', 'notes');
-
-$PAGE->navbar->add($straddnote);
-$PAGE->set_title("$course->shortname: ".get_string('extendenrol'));
-$PAGE->set_heading($course->fullname);
-
-echo $OUTPUT->header();
-// This will contain all available the based On select options, but we'll disable some on them on a per user basis.
-echo $OUTPUT->heading($straddnote);
-echo '';
-echo $OUTPUT->footer();
-
diff --git a/user/amd/build/participants.min.js b/user/amd/build/participants.min.js
new file mode 100644
index 00000000000..6831b7adf05
--- /dev/null
+++ b/user/amd/build/participants.min.js
@@ -0,0 +1 @@
+define(["jquery","core/str","core/modal_factory","core/modal_events","core/templates","core/notification","core/ajax"],function(a,b,c,d,e,f,g){var h={BULKACTIONSELECT:"#formactionid",BULKUSERCHECKBOXES:"input.usercheckbox",BULKUSERNOSCHECKBOXES:"input.usercheckbox[value='0']",BULKUSERSELECTEDCHECKBOXES:"input.usercheckbox:checked",BULKACTIONFORM:"#participantsform",CHECKALLBUTTON:"#checkall",CHECKALLNOSBUTTON:"#checkallnos",CHECKALLONPAGEBUTTON:"#checkallonpage",CHECKNONEBUTTON:"#checknone"},i=function(a){this.courseId=a.courseid,this.noteStateNames=a.noteStateNames,this.stateHelpIcon=a.stateHelpIcon,this.attachEventListeners()};return i.prototype.modal=null,i.prototype.courseId=-1,i.prototype.noteStateNames={},i.prototype.stateHelpIcon="",i.prototype.attachEventListeners=function(){a(h.BULKACTIONSELECT).on("change",function(b){var c=a(b.target).val();if(c.indexOf("#")!==-1){b.preventDefault();var d=[];a(h.BULKUSERSELECTEDCHECKBOXES).each(function(b,c){var e=a(c).attr("name"),f=e.replace("user","");d.push(f)}),"#messageselect"==c?this.showSendMessage(d).fail(f.exception):"#addgroupnote"==c&&this.showAddNote(d).fail(f.exception),a(h.BULKACTIONSELECT+' option[value=""]').prop("selected","selected")}else""!==c&&(a(h.BULKUSERSELECTEDCHECKBOXES).length>0?a(h.BULKACTIONFORM).submit():a(h.BULKACTIONSELECT+' option[value=""]').prop("selected","selected"))}.bind(this)),a(h.CHECKALLBUTTON).on("click",function(){var b=a(this).data("showallink");b&&(window.location=b)}),a(h.CHECKALLNOSBUTTON).on("click",function(){a(h.BULKUSERNOSCHECKBOXES).prop("checked",!0)}),a(h.CHECKALLONPAGEBUTTON).on("click",function(){a(h.BULKUSERCHECKBOXES).prop("checked",!0)}),a(h.CHECKNONEBUTTON).on("click",function(){a(h.BULKUSERCHECKBOXES).prop("checked",!1)})},i.prototype.showAddNote=function(f){if(0==f.length)return a.Deferred().resolve().promise();var g=[];for(var i in this.noteStateNames)g.push({value:i,label:this.noteStateNames[i]});var j={stateNames:g,stateHelpIcon:this.stateHelpIcon},k=null;return k=1==f.length?b.get_string("addbulknotesingle","core_notes"):b.get_string("addbulknote","core_notes",f.length),a.when(c.create({type:c.types.SAVE_CANCEL,body:e.render("core_user/add_bulk_note",j)}),k).then(function(b,c){return this.modal=b,this.modal.setTitle(c),this.modal.setSaveButtonText(c),this.modal.getRoot().on(d.hidden,function(){var b=a("#user-notifications [role=alert]");b.length?b.focus():a(h.BULKACTIONSELECT).focus(),this.modal.getRoot().remove()}.bind(this)),this.modal.getRoot().on(d.save,this.submitAddNote.bind(this,f)),this.modal.show(),this.modal}.bind(this))},i.prototype.submitAddNote=function(a){var c=this.modal.getRoot().find("form textarea").val(),d=this.modal.getRoot().find("form select").val(),e=[],h=0;for(h=0;h.
+
+/**
+ * Some UI stuff for participants page.
+ * This is also used by the report/participants/index.php because it has the same functionality.
+ *
+ * @module core_user/participants
+ * @package core_user
+ * @copyright 2017 Damyon Wiese
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+define(['jquery', 'core/str', 'core/modal_factory', 'core/modal_events', 'core/templates', 'core/notification', 'core/ajax'],
+ function($, Str, ModalFactory, ModalEvents, Templates, Notification, Ajax) {
+
+ var SELECTORS = {
+ BULKACTIONSELECT: "#formactionid",
+ BULKUSERCHECKBOXES: "input.usercheckbox",
+ BULKUSERNOSCHECKBOXES: "input.usercheckbox[value='0']",
+ BULKUSERSELECTEDCHECKBOXES: "input.usercheckbox:checked",
+ BULKACTIONFORM: "#participantsform",
+ CHECKALLBUTTON: "#checkall",
+ CHECKALLNOSBUTTON: "#checkallnos",
+ CHECKALLONPAGEBUTTON: "#checkallonpage",
+ CHECKNONEBUTTON: "#checknone"
+ };
+
+ /**
+ * Constructor
+ *
+ * @param {Object} options Object containing options. Contextid is required.
+ * Each call to templates.render gets it's own instance of this class.
+ */
+ var Participants = function(options) {
+
+ this.courseId = options.courseid;
+ this.noteStateNames = options.noteStateNames;
+ this.stateHelpIcon = options.stateHelpIcon;
+
+ this.attachEventListeners();
+ };
+ // Class variables and functions.
+
+ /**
+ * @var {Modal} modal
+ * @private
+ */
+ Participants.prototype.modal = null;
+
+ /**
+ * @var {int} courseId
+ * @private
+ */
+ Participants.prototype.courseId = -1;
+
+ /**
+ * @var {Object} noteStateNames
+ * @private
+ */
+ Participants.prototype.noteStateNames = {};
+
+ /**
+ * @var {String} stateHelpIcon
+ * @private
+ */
+ Participants.prototype.stateHelpIcon = "";
+
+ /**
+ * Private method
+ *
+ * @method attachEventListeners
+ * @private
+ */
+ Participants.prototype.attachEventListeners = function() {
+ $(SELECTORS.BULKACTIONSELECT).on('change', function(e) {
+ var action = $(e.target).val();
+ if (action.indexOf('#') !== -1) {
+ e.preventDefault();
+
+ var ids = [];
+ $(SELECTORS.BULKUSERSELECTEDCHECKBOXES).each(function(index, ele) {
+ var name = $(ele).attr('name');
+ var id = name.replace('user', '');
+ ids.push(id);
+ });
+
+ if (action == '#messageselect') {
+ this.showSendMessage(ids).fail(Notification.exception);
+ } else if (action == '#addgroupnote') {
+ this.showAddNote(ids).fail(Notification.exception);
+ }
+ $(SELECTORS.BULKACTIONSELECT + ' option[value=""]').prop('selected', 'selected');
+ } else if (action !== '') {
+ if ($(SELECTORS.BULKUSERSELECTEDCHECKBOXES).length > 0) {
+ $(SELECTORS.BULKACTIONFORM).submit();
+ } else {
+ $(SELECTORS.BULKACTIONSELECT + ' option[value=""]').prop('selected', 'selected');
+ }
+ }
+ }.bind(this));
+
+ $(SELECTORS.CHECKALLBUTTON).on('click', function() {
+ var showallink = $(this).data('showallink');
+ if (showallink) {
+ window.location = showallink;
+ }
+ });
+
+ $(SELECTORS.CHECKALLNOSBUTTON).on('click', function() {
+ $(SELECTORS.BULKUSERNOSCHECKBOXES).prop('checked', true);
+ });
+ $(SELECTORS.CHECKALLONPAGEBUTTON).on('click', function() {
+ $(SELECTORS.BULKUSERCHECKBOXES).prop('checked', true);
+ });
+
+ $(SELECTORS.CHECKNONEBUTTON).on('click', function() {
+ $(SELECTORS.BULKUSERCHECKBOXES).prop('checked', false);
+ });
+ };
+
+ /**
+ * Show the add note popup
+ *
+ * @method showAddNote
+ * @private
+ * @param {int[]} users
+ * @return {Promise}
+ */
+ Participants.prototype.showAddNote = function(users) {
+
+ if (users.length == 0) {
+ // Nothing to do.
+ return $.Deferred().resolve().promise();
+ }
+
+ var states = [];
+ for (var key in this.noteStateNames) {
+ states.push({value: key, label: this.noteStateNames[key]});
+ }
+
+ var context = {stateNames: states, stateHelpIcon: this.stateHelpIcon};
+ var titlePromise = null;
+ if (users.length == 1) {
+ titlePromise = Str.get_string('addbulknotesingle', 'core_notes');
+ } else {
+ titlePromise = Str.get_string('addbulknote', 'core_notes', users.length);
+ }
+
+ return $.when(
+ ModalFactory.create({
+ type: ModalFactory.types.SAVE_CANCEL,
+ body: Templates.render('core_user/add_bulk_note', context)
+ }),
+ titlePromise
+ ).then(function(modal, title) {
+ // Keep a reference to the modal.
+ this.modal = modal;
+ this.modal.setTitle(title);
+ this.modal.setSaveButtonText(title);
+
+ // We want to focus on the action select when the dialog is closed.
+ this.modal.getRoot().on(ModalEvents.hidden, function() {
+ var notification = $('#user-notifications [role=alert]');
+ if (notification.length) {
+ notification.focus();
+ } else {
+ $(SELECTORS.BULKACTIONSELECT).focus();
+ }
+ this.modal.getRoot().remove();
+ }.bind(this));
+
+ this.modal.getRoot().on(ModalEvents.save, this.submitAddNote.bind(this, users));
+
+ this.modal.show();
+
+ return this.modal;
+ }.bind(this));
+ };
+
+ /**
+ * Add a note to this list of users.
+ *
+ * @method submitAddNote
+ * @private
+ * @param {int[]} users
+ * @return {Promise}
+ */
+ Participants.prototype.submitAddNote = function(users) {
+ var noteText = this.modal.getRoot().find('form textarea').val();
+ var publishState = this.modal.getRoot().find('form select').val();
+ var notes = [],
+ i = 0;
+
+ for (i = 0; i < users.length; i++) {
+ notes.push({userid: users[i], text: noteText, courseid: this.courseId, publishstate: publishState});
+ }
+
+ return Ajax.call([{
+ methodname: 'core_notes_create_notes',
+ args: {notes: notes}
+ }])[0].then(function(noteIds) {
+ if (noteIds.length == 1) {
+ return Str.get_string('addbulknotedonesingle', 'core_notes');
+ } else {
+ return Str.get_string('addbulknotedone', 'core_notes', noteIds.length);
+ }
+ }).then(function(msg) {
+ Notification.addNotification({
+ message: msg,
+ type: "success"
+ });
+ return true;
+ }).catch(Notification.exception);
+ };
+
+ /**
+ * Show the send message popup.
+ *
+ * @method showSendMessage
+ * @private
+ * @param {int[]} users
+ * @return {Promise}
+ */
+ Participants.prototype.showSendMessage = function(users) {
+
+ if (users.length == 0) {
+ // Nothing to do.
+ return $.Deferred().resolve().promise();
+ }
+ var titlePromise = null;
+ if (users.length == 1) {
+ titlePromise = Str.get_string('sendbulkmessagesingle', 'core_message');
+ } else {
+ titlePromise = Str.get_string('sendbulkmessage', 'core_message', users.length);
+ }
+
+ return $.when(
+ ModalFactory.create({
+ type: ModalFactory.types.SAVE_CANCEL,
+ body: Templates.render('core_user/send_bulk_message', {})
+ }),
+ titlePromise
+ ).then(function(modal, title) {
+ // Keep a reference to the modal.
+ this.modal = modal;
+
+ this.modal.setTitle(title);
+ this.modal.setSaveButtonText(title);
+
+ // We want to focus on the action select when the dialog is closed.
+ this.modal.getRoot().on(ModalEvents.hidden, function() {
+ $(SELECTORS.BULKACTIONSELECT).focus();
+ this.modal.getRoot().remove();
+ }.bind(this));
+
+ this.modal.getRoot().on(ModalEvents.save, this.submitSendMessage.bind(this, users));
+
+ this.modal.show();
+
+ return this.modal;
+ }.bind(this));
+ };
+
+ /**
+ * Send a message to these users.
+ *
+ * @method submitSendMessage
+ * @private
+ * @param {int[]} users
+ * @param {Event} e Form submission event.
+ * @return {Promise}
+ */
+ Participants.prototype.submitSendMessage = function(users) {
+
+ var messageText = this.modal.getRoot().find('form textarea').val();
+
+ var messages = [],
+ i = 0;
+
+ for (i = 0; i < users.length; i++) {
+ messages.push({touserid: users[i], text: messageText});
+ }
+
+ return Ajax.call([{
+ methodname: 'core_message_send_instant_messages',
+ args: {messages: messages}
+ }])[0].then(function(messageIds) {
+ if (messageIds.length == 1) {
+ return Str.get_string('sendbulkmessagesentsingle', 'core_message');
+ } else {
+ return Str.get_string('sendbulkmessagesent', 'core_message', messageIds.length);
+ }
+ }).then(function(msg) {
+ Notification.addNotification({
+ message: msg,
+ type: "success"
+ });
+ return true;
+ }).catch(Notification.exception);
+ };
+
+ return /** @alias module:core_user/participants */ {
+ // Public variables and functions.
+
+ /**
+ * Initialise the unified user filter.
+ *
+ * @method init
+ * @param {Object} options - List of options.
+ * @return {Participants}
+ */
+ 'init': function(options) {
+ return new Participants(options);
+ }
+ };
+});
diff --git a/user/groupaddnote.php b/user/groupaddnote.php
deleted file mode 100644
index a8592349a69..00000000000
--- a/user/groupaddnote.php
+++ /dev/null
@@ -1,123 +0,0 @@
-.
-
-/**
- * This file is part of the User section Moodle
- *
- * @copyright 1999 Martin Dougiamas http://dougiamas.com
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- * @package core_user
- */
-
-require_once("../config.php");
-require_once($CFG->dirroot .'/notes/lib.php');
-
-$id = required_param('id', PARAM_INT); // Course id.
-$users = optional_param_array('userid', array(), PARAM_INT); // Array of user id.
-$content = optional_param('content', '', PARAM_RAW); // Note content.
-$state = optional_param('state', '', PARAM_ALPHA); // Note publish state.
-
-$url = new moodle_url('/user/groupaddnote.php', array('id' => $id));
-if ($content !== '') {
- $url->param('content', $content);
-}
-if ($state !== '') {
- $url->param('state', $state);
-}
-$PAGE->set_url($url);
-
-if (! $course = $DB->get_record('course', array('id' => $id))) {
- print_error('invalidcourseid');
-}
-
-$context = context_course::instance($id);
-require_login($course);
-
-// To create notes the current user needs a capability.
-require_capability('moodle/notes:manage', $context);
-
-if (empty($CFG->enablenotes)) {
- print_error('notesdisabled', 'notes');
-}
-
-if (!empty($users) && !empty($content) && confirm_sesskey()) {
- $note = new stdClass();
- $note->courseid = $id;
- $note->format = FORMAT_PLAIN;
- $note->content = $content;
- $note->publishstate = $state;
- foreach ($users as $k => $v) {
- if (!$user = $DB->get_record('user', array('id' => $v))) {
- continue;
- }
- $note->id = 0;
- $note->userid = $v;
- note_save($note);
- }
-
- redirect("$CFG->wwwroot/user/index.php?id=$id");
-}
-
-$straddnote = get_string('groupaddnewnote', 'notes');
-
-$PAGE->navbar->add($straddnote);
-$PAGE->set_title("$course->shortname: ".get_string('extendenrol'));
-$PAGE->set_heading($course->fullname);
-
-// Print headers.
-echo $OUTPUT->header();
-
-// This will contain all available the based On select options, but we'll disable some on them on a per user basis.
-
-echo $OUTPUT->heading($straddnote);
-echo '';
-echo $OUTPUT->footer();
diff --git a/user/index.php b/user/index.php
index e89aab71007..69f8f946c5a 100644
--- a/user/index.php
+++ b/user/index.php
@@ -25,6 +25,7 @@
require_once('../config.php');
require_once($CFG->dirroot.'/user/lib.php');
require_once($CFG->dirroot.'/course/lib.php');
+require_once($CFG->dirroot.'/notes/lib.php');
require_once($CFG->libdir.'/tablelib.php');
require_once($CFG->libdir.'/filelib.php');
require_once($CFG->dirroot.'/enrol/locallib.php');
@@ -292,10 +293,9 @@ if ($bulkoperations) {
'value' => get_string('deselectall')));
echo html_writer::end_tag('div');
$displaylist = array();
- $displaylist['messageselect.php'] = get_string('messageselectadd');
+ $displaylist['#messageselect'] = get_string('messageselectadd');
if (!empty($CFG->enablenotes) && has_capability('moodle/notes:manage', $context) && $context->id != $frontpagectx->id) {
- $displaylist['addnote.php'] = get_string('addnewnote', 'notes');
- $displaylist['groupaddnote.php'] = get_string('groupaddnewnote', 'notes');
+ $displaylist['#addgroupnote'] = get_string('addnewnote', 'notes');
}
if ($context->id != $frontpagectx->id) {
@@ -333,8 +333,11 @@ if ($bulkoperations) {
echo '';
echo '';
- $module = array('name' => 'core_user', 'fullpath' => '/user/module.js');
- $PAGE->requires->js_init_call('M.core_user.init_participation', null, false, $module);
+ $options = new stdClass();
+ $options->courseid = $course->id;
+ $options->noteStateNames = note_get_state_names();
+ $options->stateHelpIcon = $OUTPUT->help_icon('publishstate', 'notes');
+ $PAGE->requires->js_call_amd('core_user/participants', 'init', [$options]);
}
echo ''; // Userlist.
diff --git a/user/module.js b/user/module.js
deleted file mode 100644
index 98c9040280f..00000000000
--- a/user/module.js
+++ /dev/null
@@ -1,58 +0,0 @@
-
-M.core_user = {};
-
-M.core_user.init_participation = function(Y) {
- Y.on('change', function() {
- var action = Y.one('#formactionid');
- if (action.get('value') == '') {
- return;
- }
- var ok = false;
- Y.all('input.usercheckbox').each(function() {
- if (this.get('checked')) {
- ok = true;
- }
- });
- if (!ok) {
- // no checkbox selected
- return;
- }
- Y.one('#participantsform').submit();
- }, '#formactionid');
-
- Y.on('click', function(e) {
- // Presence of a show all link indicates we should redirect to
- // a page with all users listed and checked, otherwise just check
- // those already shown.
- var showallink = this.getAttribute('data-showallink');
- if (showallink) {
- window.location = showallink;
- }
- Y.all('input.usercheckbox').each(function() {
- this.set('checked', 'checked');
- });
- }, '#checkall, #checkallonpage');
-
- Y.on('click', function(e) {
- Y.all('input.usercheckbox').each(function() {
- this.set('checked', '');
- });
- }, '#checknone');
-};
-
-M.core_user.init_tree = function(Y, expand_all, htmlid) {
- Y.use('yui2-treeview', function(Y) {
- var tree = new Y.YUI2.widget.TreeView(htmlid);
-
- tree.subscribe("clickEvent", function(node, event) {
- // we want normal clicking which redirects to url
- return false;
- });
-
- if (expand_all) {
- tree.expandAll();
- }
-
- tree.render();
- });
-};
diff --git a/user/renderer.php b/user/renderer.php
index 37d596a625e..eb2026ec1ab 100644
--- a/user/renderer.php
+++ b/user/renderer.php
@@ -31,67 +31,6 @@ defined('MOODLE_INTERNAL') || die();
*/
class core_user_renderer extends plugin_renderer_base {
- /**
- * Prints user files tree view
- * @return string
- */
- public function user_files_tree() {
- return $this->render(new user_files_tree);
- }
-
- /**
- * Render user files tree
- *
- * @param user_files_tree $tree
- * @return string HTML
- */
- public function render_user_files_tree(user_files_tree $tree) {
- if (empty($tree->dir['subdirs']) && empty($tree->dir['files'])) {
- $html = $this->output->box(get_string('nofilesavailable', 'repository'));
- } else {
- $htmlid = 'user_files_tree_'.uniqid();
- $module = array('name' => 'core_user', 'fullpath' => '/user/module.js');
- $this->page->requires->js_init_call('M.core_user.init_tree', array(false, $htmlid), false, $module);
- $html = '
';
-
- return $result;
- }
-
/**
* Prints user search utility that can search user by first initial of firstname and/or first initial of lastname
* Prints a header with a title and the number of users found within that subset
@@ -321,31 +260,3 @@ class core_user_renderer extends plugin_renderer_base {
return [$optionvalue => $optionlabel];
}
}
-
-/**
- * User files tree
- * @copyright 2010 Dongsheng Cai
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class user_files_tree implements renderable {
-
- /**
- * @var context_user $context
- */
- public $context;
-
- /**
- * @var array $dir
- */
- public $dir;
-
- /**
- * Create user files tree object
- */
- public function __construct() {
- global $USER;
- $this->context = context_user::instance($USER->id);
- $fs = get_file_storage();
- $this->dir = $fs->get_area_tree($this->context->id, 'user', 'private', 0);
- }
-}
diff --git a/user/templates/add_bulk_note.mustache b/user/templates/add_bulk_note.mustache
new file mode 100644
index 00000000000..30ba3703fbd
--- /dev/null
+++ b/user/templates/add_bulk_note.mustache
@@ -0,0 +1,55 @@
+{{!
+ 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 .
+}}
+{{!
+ @template core_user/add_bulk_note
+
+ Template for the add bulk note modal.
+
+ Context variables required for this template:
+ * stateNames array - List of value / label pairs of valid publish states for notes.
+ * stateHelpIcon string - Rendered help icon for the publish state.
+
+ Example context (json):
+ {
+ "stateNames": [ { "value": 0, "label": "State 1"}, { "value": 1, "label": "State 2"} ],
+ "stateHelpIcon": "(help me)"
+ }
+}}
+
+{{#js}}
+require(['core/auto_rows'], function(AutoRows) {
+ AutoRows.init(document.getElementById('bulk-note'));
+});
+{{/js}}
diff --git a/user/templates/send_bulk_message.mustache b/user/templates/send_bulk_message.mustache
new file mode 100644
index 00000000000..34f9c91427f
--- /dev/null
+++ b/user/templates/send_bulk_message.mustache
@@ -0,0 +1,41 @@
+{{!
+ 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 .
+}}
+{{!
+ @template core_user/send_bulk_message
+
+ Template for the send bulk message modal.
+
+ Context variables required for this template:
+ None
+
+ Example context (json):
+ {
+ }
+}}
+
+{{#js}}
+require(['core/auto_rows'], function(AutoRows) {
+ AutoRows.init(document.getElementById('bulk-message'));
+});
+{{/js}}
diff --git a/user/tests/behat/behat_user.php b/user/tests/behat/behat_user.php
new file mode 100644
index 00000000000..36339e64276
--- /dev/null
+++ b/user/tests/behat/behat_user.php
@@ -0,0 +1,56 @@
+.
+
+/**
+ * User steps definition.
+ *
+ * @package core_user
+ * @category test
+ * @copyright 2017 Damyon Wiese
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
+
+require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
+
+/**
+ * Steps definitions for users.
+ *
+ * @package core_user
+ * @category test
+ * @copyright 2017 Damyon Wiese
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class behat_user extends behat_base {
+
+ /**
+ * Choose from the bulk action menu.
+ *
+ * @Given /^I choose "(?P(?:[^"]|\\")*)" from the participants page bulk action menu$/
+ * @param string $nodetext The menu item to select.
+ */
+ public function i_choose_from_the_participants_page_bulk_action_menu($nodetext) {
+ $nodetext = behat_context_helper::escape($nodetext);
+
+ // Open the select.
+ $this->execute("behat_general::i_click_on", array("//select[@id='formactionid']", "xpath_element"));
+
+ // Click on the option.
+ $this->execute("behat_general::i_click_on", array("//select[@id='formactionid']" .
+ "/option[contains(., " . $nodetext . ")]", "xpath_element"));
+ }
+}