mirror of
https://github.com/moodle/moodle.git
synced 2025-03-14 04:30:15 +01:00
Merge branch 'MDL-66075-master' of git://github.com/lameze/moodle
This commit is contained in:
commit
74e9aeca79
@ -584,6 +584,94 @@ class core_enrol_external extends external_api {
|
||||
return new external_multiple_structure(core_user_external::user_description());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns description of method parameters
|
||||
*
|
||||
* @return external_function_parameters
|
||||
*/
|
||||
public static function search_users_parameters(): external_function_parameters {
|
||||
return new external_function_parameters(
|
||||
[
|
||||
'courseid' => new external_value(PARAM_INT, 'course id'),
|
||||
'search' => new external_value(PARAM_RAW, 'query'),
|
||||
'searchanywhere' => new external_value(PARAM_BOOL, 'find a match anywhere, or only at the beginning'),
|
||||
'page' => new external_value(PARAM_INT, 'Page number'),
|
||||
'perpage' => new external_value(PARAM_INT, 'Number per page'),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search course participants.
|
||||
*
|
||||
* @param int $courseid Course id
|
||||
* @param string $search The query
|
||||
* @param bool $searchanywhere Match anywhere in the string
|
||||
* @param int $page Page number
|
||||
* @param int $perpage Max per page
|
||||
* @return array An array of users
|
||||
* @throws moodle_exception
|
||||
*/
|
||||
public static function search_users(int $courseid, string $search, bool $searchanywhere, int $page, int $perpage): array {
|
||||
global $PAGE, $DB, $CFG;
|
||||
|
||||
require_once($CFG->dirroot.'/enrol/locallib.php');
|
||||
require_once($CFG->dirroot.'/user/lib.php');
|
||||
|
||||
$params = self::validate_parameters(
|
||||
self::search_users_parameters(),
|
||||
[
|
||||
'courseid' => $courseid,
|
||||
'search' => $search,
|
||||
'searchanywhere' => $searchanywhere,
|
||||
'page' => $page,
|
||||
'perpage' => $perpage
|
||||
]
|
||||
);
|
||||
$context = context_course::instance($params['courseid']);
|
||||
try {
|
||||
self::validate_context($context);
|
||||
} catch (Exception $e) {
|
||||
$exceptionparam = new stdClass();
|
||||
$exceptionparam->message = $e->getMessage();
|
||||
$exceptionparam->courseid = $params['courseid'];
|
||||
throw new moodle_exception('errorcoursecontextnotvalid' , 'webservice', '', $exceptionparam);
|
||||
}
|
||||
course_require_view_participants($context);
|
||||
|
||||
$course = get_course($params['courseid']);
|
||||
$manager = new course_enrolment_manager($PAGE, $course);
|
||||
|
||||
$users = $manager->search_users($params['search'],
|
||||
$params['searchanywhere'],
|
||||
$params['page'],
|
||||
$params['perpage']);
|
||||
|
||||
$results = [];
|
||||
// Add also extra user fields.
|
||||
$requiredfields = array_merge(
|
||||
['id', 'fullname', 'profileimageurl', 'profileimageurlsmall'],
|
||||
get_extra_user_fields($context)
|
||||
);
|
||||
foreach ($users['users'] as $user) {
|
||||
if ($userdetails = user_get_user_details($user, $course, $requiredfields)) {
|
||||
$results[] = $userdetails;
|
||||
}
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns description of method result value
|
||||
*
|
||||
* @return external_multiple_structure
|
||||
*/
|
||||
public static function search_users_returns(): external_multiple_structure {
|
||||
global $CFG;
|
||||
require_once($CFG->dirroot . '/user/externallib.php');
|
||||
return new external_multiple_structure(core_user_external::user_description());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns description of method parameters
|
||||
*
|
||||
|
@ -531,6 +531,35 @@ class course_enrolment_manager {
|
||||
return $this->execute_search_queries($search, $fields, $countfields, $sql, $params, $page, $perpage, 0, $returnexactcount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches through the enrolled users in this course.
|
||||
*
|
||||
* @param string $search The search term.
|
||||
* @param bool $searchanywhere Can the search term be anywhere, or must it be at the start.
|
||||
* @param int $page Starting at 0.
|
||||
* @param int $perpage Number of users returned per page.
|
||||
* @param bool $returnexactcount Return the exact total users using count_record or not.
|
||||
* @return array with two or three elements:
|
||||
* int totalusers Number users matching the search. (This element only exist if $returnexactcount was set to true)
|
||||
* array users List of user objects returned by the query.
|
||||
* boolean moreusers True if there are still more users, otherwise is False.
|
||||
*/
|
||||
public function search_users(string $search = '', bool $searchanywhere = false, int $page = 0, int $perpage = 25,
|
||||
bool $returnexactcount = false) {
|
||||
list($ufields, $params, $wherecondition) = $this->get_basic_search_conditions($search, $searchanywhere);
|
||||
|
||||
$fields = 'SELECT ' . $ufields;
|
||||
$countfields = 'SELECT COUNT(u.id)';
|
||||
$sql = " FROM {user} u
|
||||
JOIN {user_enrolments} ue ON ue.userid = u.id
|
||||
JOIN {enrol} e ON ue.enrolid = e.id
|
||||
WHERE $wherecondition
|
||||
AND e.courseid = :courseid";
|
||||
$params['courseid'] = $this->course->id;
|
||||
|
||||
return $this->execute_search_queries($search, $fields, $countfields, $sql, $params, $page, $perpage, 0, $returnexactcount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an array containing some SQL to user for when selecting, params for
|
||||
* that SQL, and the filter that was used in constructing the sql.
|
||||
|
@ -325,7 +325,7 @@ class core_course_enrolment_manager_testcase extends advanced_testcase {
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case for test_get_potential_users and test_search_other_users tests.
|
||||
* Test case for test_get_potential_users, test_search_other_users and test_search_users tests.
|
||||
*
|
||||
* @return array Dataset
|
||||
*/
|
||||
@ -337,4 +337,42 @@ class core_course_enrolment_manager_testcase extends advanced_testcase {
|
||||
[5, true, 3, 3, false]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test search_users function.
|
||||
*
|
||||
* @dataProvider search_users_provider
|
||||
*
|
||||
* @param int $perpage Number of users per page.
|
||||
* @param bool $returnexactcount Return the exact count or not.
|
||||
* @param int $expectedusers Expected number of users return.
|
||||
* @param int $expectedtotalusers Expected total of users in database.
|
||||
* @param bool $expectedmoreusers Expected for more users return or not.
|
||||
*/
|
||||
public function test_search_users($perpage, $returnexactcount, $expectedusers, $expectedtotalusers, $expectedmoreusers) {
|
||||
global $PAGE;
|
||||
$this->resetAfterTest();
|
||||
|
||||
$this->getDataGenerator()->create_and_enrol($this->course, 'student', ['firstname' => 'sutest 1']);
|
||||
$this->getDataGenerator()->create_and_enrol($this->course, 'student', ['firstname' => 'sutest 2']);
|
||||
$this->getDataGenerator()->create_and_enrol($this->course, 'student', ['firstname' => 'sutest 3']);
|
||||
|
||||
$manager = new course_enrolment_manager($PAGE, $this->course);
|
||||
$users = $manager->search_users(
|
||||
'sutest',
|
||||
true,
|
||||
0,
|
||||
$perpage,
|
||||
$returnexactcount
|
||||
);
|
||||
|
||||
$this->assertCount($expectedusers, $users['users']);
|
||||
$this->assertEquals($expectedmoreusers, $users['moreusers']);
|
||||
if ($returnexactcount) {
|
||||
$this->assertArrayHasKey('totalusers', $users);
|
||||
$this->assertEquals($expectedtotalusers, $users['totalusers']);
|
||||
} else {
|
||||
$this->assertArrayNotHasKey('totalusers', $users);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1171,4 +1171,92 @@ class core_enrol_externallib_testcase extends externallib_advanced_testcase {
|
||||
$ue = $DB->count_records('user_enrolments', ['id' => $ueid]);
|
||||
$this->assertEquals(0, $ue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for core_enrol_external::test_search_users().
|
||||
*/
|
||||
public function test_search_users() {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest(true);
|
||||
$datagen = $this->getDataGenerator();
|
||||
|
||||
/** @var enrol_manual_plugin $manualplugin */
|
||||
$manualplugin = enrol_get_plugin('manual');
|
||||
$this->assertNotNull($manualplugin);
|
||||
|
||||
$studentroleid = $DB->get_field('role', 'id', ['shortname' => 'student'], MUST_EXIST);
|
||||
$teacherroleid = $DB->get_field('role', 'id', ['shortname' => 'editingteacher'], MUST_EXIST);
|
||||
|
||||
$course1 = $datagen->create_course();
|
||||
$course2 = $datagen->create_course();
|
||||
|
||||
$user1 = $datagen->create_user(['firstname' => 'user 1']);
|
||||
$user2 = $datagen->create_user(['firstname' => 'user 2']);
|
||||
$user3 = $datagen->create_user(['firstname' => 'user 3']);
|
||||
$teacher = $datagen->create_user(['firstname' => 'user 4']);
|
||||
|
||||
$instanceid = null;
|
||||
$instances = enrol_get_instances($course1->id, true);
|
||||
foreach ($instances as $inst) {
|
||||
if ($inst->enrol == 'manual') {
|
||||
$instanceid = (int)$inst->id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (empty($instanceid)) {
|
||||
$instanceid = $manualplugin->add_default_instance($course1);
|
||||
if (empty($instanceid)) {
|
||||
$instanceid = $manualplugin->add_instance($course1);
|
||||
}
|
||||
}
|
||||
$this->assertNotNull($instanceid);
|
||||
|
||||
$instance = $DB->get_record('enrol', ['id' => $instanceid], '*', MUST_EXIST);
|
||||
$manualplugin->enrol_user($instance, $user1->id, $studentroleid, 0, 0, ENROL_USER_ACTIVE);
|
||||
$manualplugin->enrol_user($instance, $user2->id, $studentroleid, 0, 0, ENROL_USER_ACTIVE);
|
||||
$manualplugin->enrol_user($instance, $user3->id, $studentroleid, 0, 0, ENROL_USER_ACTIVE);
|
||||
$manualplugin->enrol_user($instance, $teacher->id, $teacherroleid, 0, 0, ENROL_USER_ACTIVE);
|
||||
|
||||
$this->setUser($teacher);
|
||||
|
||||
// Search for users in a course with enrolled users.
|
||||
$result = core_enrol_external::search_users($course1->id, 'user', true, 0, 30);
|
||||
$this->assertCount(4, $result);
|
||||
|
||||
$this->expectException('moodle_exception');
|
||||
// Search for users in a course without any enrolled users, shouldn't return anything.
|
||||
$result = core_enrol_external::search_users($course2->id, 'user', true, 0, 30);
|
||||
$this->assertCount(0, $result);
|
||||
|
||||
// Search for invalid first name.
|
||||
$result = core_enrol_external::search_users($course1->id, 'yada yada', true, 0, 30);
|
||||
$this->assertCount(0, $result);
|
||||
|
||||
// Test pagination, it should return only 3 users.
|
||||
$result = core_enrol_external::search_users($course1->id, 'user', true, 0, 3);
|
||||
$this->assertCount(3, $result);
|
||||
|
||||
// Test pagination, it should return only 3 users.
|
||||
$result = core_enrol_external::search_users($course1->id, 'user 1', true, 0, 1);
|
||||
$result = $result[0];
|
||||
$this->assertEquals($user1->id, $result['id']);
|
||||
$this->assertEquals($user1->email, $result['email']);
|
||||
$this->assertEquals(fullname($user1), $result['fullname']);
|
||||
|
||||
$this->setUser($user1);
|
||||
|
||||
// Search for users in a course with enrolled users.
|
||||
$result = core_enrol_external::search_users($course1->id, 'user', true, 0, 30);
|
||||
$this->assertCount(4, $result);
|
||||
|
||||
$this->expectException('moodle_exception');
|
||||
// Search for users in a course without any enrolled users, shouldn't return anything.
|
||||
$result = core_enrol_external::search_users($course2->id, 'user', true, 0, 30);
|
||||
$this->assertCount(0, $result);
|
||||
|
||||
// Search for invalid first name.
|
||||
$result = core_enrol_external::search_users($course1->id, 'yada yada', true, 0, 30);
|
||||
$this->assertCount(0, $result);
|
||||
}
|
||||
}
|
||||
|
@ -634,6 +634,15 @@ $functions = array(
|
||||
'type' => 'read',
|
||||
'capabilities' => 'moodle/course:enrolreview'
|
||||
),
|
||||
'core_enrol_search_users' => [
|
||||
'classname' => 'core_enrol_external',
|
||||
'methodname' => 'search_users',
|
||||
'classpath' => 'enrol/externallib.php',
|
||||
'description' => 'Search within the list of course participants',
|
||||
'ajax' => true,
|
||||
'type' => 'read',
|
||||
'capabilities' => 'moodle/course:viewparticipants',
|
||||
],
|
||||
'core_enrol_get_users_courses' => array(
|
||||
'classname' => 'core_enrol_external',
|
||||
'methodname' => 'get_users_courses',
|
||||
|
2
mod/forum/amd/build/form-user-selector.min.js
vendored
Normal file
2
mod/forum/amd/build/form-user-selector.min.js
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
define ("mod_forum/form-user-selector",["jquery","core/ajax","core/templates"],function(a,b,c){return{processResults:function processResults(b,c){var d=[];a.each(c,function(a,b){d.push({value:b.id,label:b._label})});return d},transport:function transport(d,e,f,g){var h,i=a(d).attr("courseid");h=b.call([{methodname:"core_enrol_search_users",args:{courseid:i,search:e,searchanywhere:!0,page:0,perpage:30}}]);h[0].then(function(b){var d=[],e=0;a.each(b,function(a,b){d.push(c.render("mod_forum/form-user-selector-suggestion",b))});return a.when.apply(a.when,d).then(function(){var c=arguments;a.each(b,function(a,b){b._label=c[e];e++});f(b)})}).fail(g)}}});
|
||||
//# sourceMappingURL=form-user-selector.min.js.map
|
1
mod/forum/amd/build/form-user-selector.min.js.map
Normal file
1
mod/forum/amd/build/form-user-selector.min.js.map
Normal file
@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../src/form-user-selector.js"],"names":["define","$","Ajax","Templates","processResults","selector","results","users","each","index","user","push","value","id","label","_label","transport","query","success","failure","promise","courseid","attr","call","methodname","args","search","searchanywhere","page","perpage","then","promises","i","render","when","apply","arguments","fail"],"mappings":"AAyBAA,OAAM,gCAAC,CAAC,QAAD,CAAW,WAAX,CAAwB,gBAAxB,CAAD,CAA4C,SAASC,CAAT,CAAYC,CAAZ,CAAkBC,CAAlB,CAA6B,CAC3E,MAAyD,CACrDC,cAAc,CAAE,wBAASC,CAAT,CAAmBC,CAAnB,CAA4B,CACxC,GAAIC,CAAAA,CAAK,CAAG,EAAZ,CACAN,CAAC,CAACO,IAAF,CAAOF,CAAP,CAAgB,SAASG,CAAT,CAAgBC,CAAhB,CAAsB,CAClCH,CAAK,CAACI,IAAN,CAAW,CACPC,KAAK,CAAEF,CAAI,CAACG,EADL,CAEPC,KAAK,CAAEJ,CAAI,CAACK,MAFL,CAAX,CAIH,CALD,EAMA,MAAOR,CAAAA,CACV,CAVoD,CAYrDS,SAAS,CAAE,mBAASX,CAAT,CAAmBY,CAAnB,CAA0BC,CAA1B,CAAmCC,CAAnC,CAA4C,IAC/CC,CAAAA,CAD+C,CAE/CC,CAAQ,CAAGpB,CAAC,CAACI,CAAD,CAAD,CAAYiB,IAAZ,CAAiB,UAAjB,CAFoC,CAInDF,CAAO,CAAGlB,CAAI,CAACqB,IAAL,CAAU,CAAC,CACjBC,UAAU,CAAE,yBADK,CAEjBC,IAAI,CAAE,CACFJ,QAAQ,CAAEA,CADR,CAEFK,MAAM,CAAET,CAFN,CAGFU,cAAc,GAHZ,CAIFC,IAAI,CAAE,CAJJ,CAKFC,OAAO,CAAE,EALP,CAFW,CAAD,CAAV,CAAV,CAWAT,CAAO,CAAC,CAAD,CAAP,CAAWU,IAAX,CAAgB,SAASxB,CAAT,CAAkB,CAC9B,GAAIyB,CAAAA,CAAQ,CAAG,EAAf,CACIC,CAAC,CAAG,CADR,CAIA/B,CAAC,CAACO,IAAF,CAAOF,CAAP,CAAgB,SAASG,CAAT,CAAgBC,CAAhB,CAAsB,CAClCqB,CAAQ,CAACpB,IAAT,CAAcR,CAAS,CAAC8B,MAAV,CAAiB,yCAAjB,CAA4DvB,CAA5D,CAAd,CACH,CAFD,EAKA,MAAOT,CAAAA,CAAC,CAACiC,IAAF,CAAOC,KAAP,CAAalC,CAAC,CAACiC,IAAf,CAAqBH,CAArB,EAA+BD,IAA/B,CAAoC,UAAW,CAClD,GAAIL,CAAAA,CAAI,CAAGW,SAAX,CACAnC,CAAC,CAACO,IAAF,CAAOF,CAAP,CAAgB,SAASG,CAAT,CAAgBC,CAAhB,CAAsB,CAClCA,CAAI,CAACK,MAAL,CAAcU,CAAI,CAACO,CAAD,CAAlB,CACAA,CAAC,EACJ,CAHD,EAIAd,CAAO,CAACZ,CAAD,CAEV,CARM,CAUV,CApBD,EAoBG+B,IApBH,CAoBQlB,CApBR,CAqBH,CAhDoD,CAoD5D,CArDK,CAAN","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Enrolled user selector module.\n *\n * @module mod_forum/form-user-selector\n * @class form-user-selector\n * @package mod_forum\n * @copyright 2019 Shamim Rezaie\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(['jquery', 'core/ajax', 'core/templates'], function($, Ajax, Templates) {\n return /** @alias module:mod_forum/form-user-selector */ {\n processResults: function(selector, results) {\n var users = [];\n $.each(results, function(index, user) {\n users.push({\n value: user.id,\n label: user._label\n });\n });\n return users;\n },\n\n transport: function(selector, query, success, failure) {\n var promise;\n var courseid = $(selector).attr('courseid');\n\n promise = Ajax.call([{\n methodname: 'core_enrol_search_users',\n args: {\n courseid: courseid,\n search: query,\n searchanywhere: true,\n page: 0,\n perpage: 30\n }\n }]);\n\n promise[0].then(function(results) {\n var promises = [],\n i = 0;\n\n // Render the label.\n $.each(results, function(index, user) {\n promises.push(Templates.render('mod_forum/form-user-selector-suggestion', user));\n });\n\n // Apply the label to the results.\n return $.when.apply($.when, promises).then(function() {\n var args = arguments;\n $.each(results, function(index, user) {\n user._label = args[i];\n i++;\n });\n success(results);\n return;\n });\n\n }).fail(failure);\n }\n\n };\n\n});\n"],"file":"form-user-selector.min.js"}
|
79
mod/forum/amd/src/form-user-selector.js
Normal file
79
mod/forum/amd/src/form-user-selector.js
Normal file
@ -0,0 +1,79 @@
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Enrolled user selector module.
|
||||
*
|
||||
* @module mod_forum/form-user-selector
|
||||
* @class form-user-selector
|
||||
* @package mod_forum
|
||||
* @copyright 2019 Shamim Rezaie
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
define(['jquery', 'core/ajax', 'core/templates'], function($, Ajax, Templates) {
|
||||
return /** @alias module:mod_forum/form-user-selector */ {
|
||||
processResults: function(selector, results) {
|
||||
var users = [];
|
||||
$.each(results, function(index, user) {
|
||||
users.push({
|
||||
value: user.id,
|
||||
label: user._label
|
||||
});
|
||||
});
|
||||
return users;
|
||||
},
|
||||
|
||||
transport: function(selector, query, success, failure) {
|
||||
var promise;
|
||||
var courseid = $(selector).attr('courseid');
|
||||
|
||||
promise = Ajax.call([{
|
||||
methodname: 'core_enrol_search_users',
|
||||
args: {
|
||||
courseid: courseid,
|
||||
search: query,
|
||||
searchanywhere: true,
|
||||
page: 0,
|
||||
perpage: 30
|
||||
}
|
||||
}]);
|
||||
|
||||
promise[0].then(function(results) {
|
||||
var promises = [],
|
||||
i = 0;
|
||||
|
||||
// Render the label.
|
||||
$.each(results, function(index, user) {
|
||||
promises.push(Templates.render('mod_forum/form-user-selector-suggestion', user));
|
||||
});
|
||||
|
||||
// Apply the label to the results.
|
||||
return $.when.apply($.when, promises).then(function() {
|
||||
var args = arguments;
|
||||
$.each(results, function(index, user) {
|
||||
user._label = args[i];
|
||||
i++;
|
||||
});
|
||||
success(results);
|
||||
return;
|
||||
});
|
||||
|
||||
}).fail(failure);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
});
|
81
mod/forum/classes/form/export_form.php
Normal file
81
mod/forum/classes/form/export_form.php
Normal file
@ -0,0 +1,81 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* This file contains the form definition for discussion export.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2019 Simey Lameze <simey@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace mod_forum\form;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die('Direct access to this script is forbidden.');
|
||||
|
||||
require_once($CFG->dirroot.'/mod/forum/lib.php');
|
||||
require_once($CFG->libdir.'/formslib.php');
|
||||
|
||||
/**
|
||||
* Export discussion form.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2019 Simey Lameze <simey@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL Juv3 or later
|
||||
*/
|
||||
class export_form extends \moodleform {
|
||||
|
||||
/**
|
||||
* Define the form - called by parent constructor
|
||||
*/
|
||||
public function definition() {
|
||||
$mform = $this->_form;
|
||||
$forum = $this->_customdata['forum'];
|
||||
|
||||
$mform->addElement('hidden', 'id');
|
||||
$mform->setType('id', PARAM_INT);
|
||||
$mform->setDefault('id', $forum->get_id());
|
||||
|
||||
$options = [
|
||||
'ajax' => 'mod_forum/form-user-selector',
|
||||
'multiple' => true,
|
||||
'noselectionstring' => get_string('allusers', 'mod_forum'),
|
||||
'courseid' => $forum->get_course_id(),
|
||||
];
|
||||
$mform->addElement('autocomplete', 'userids', get_string('users'), [], $options);
|
||||
|
||||
// Get the discussions on this forum.
|
||||
$vaultfactory = \mod_forum\local\container::get_vault_factory();
|
||||
$discussionvault = $vaultfactory->get_discussion_vault();
|
||||
$discussions = array_map(function($discussion) {
|
||||
return $discussion->get_name();
|
||||
}, $discussionvault->get_all_discussions_in_forum($forum));
|
||||
$options = [
|
||||
'multiple' => true,
|
||||
'noselectionstring' => get_string('alldiscussions', 'mod_forum'),
|
||||
];
|
||||
$mform->addElement('autocomplete', 'discussionids', get_string('discussions', 'mod_forum'), $discussions, $options);
|
||||
|
||||
// Export formats.
|
||||
$formats = \core_plugin_manager::instance()->get_plugins_of_type('dataformat');
|
||||
$options = [];
|
||||
foreach ($formats as $format) {
|
||||
$options[$format->name] = $format->displayname;
|
||||
}
|
||||
$mform->addElement('select', 'format', 'Format', $options);
|
||||
$this->add_action_buttons(true, get_string('export', 'mod_forum'));
|
||||
}
|
||||
}
|
@ -633,4 +633,14 @@ class capability {
|
||||
|
||||
return $canstart;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the user can export the whole forum (discussions and posts).
|
||||
*
|
||||
* @param stdClass $user The user object.
|
||||
* @return bool True if the user can export the forum or false otherwise.
|
||||
*/
|
||||
public function can_export_forum(stdClass $user) : bool {
|
||||
return has_capability('mod/forum:exportforum', $this->get_context(), $user);
|
||||
}
|
||||
}
|
||||
|
@ -86,6 +86,20 @@ class discussion extends db_table_vault {
|
||||
}, $results);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all discussions in the specified forum.
|
||||
*
|
||||
* @param forum_entity $forum
|
||||
* @return array
|
||||
*/
|
||||
public function get_all_discussions_in_forum(forum_entity $forum): ?array {
|
||||
$records = $this->get_db()->get_records(self::TABLE, [
|
||||
'forum' => $forum->get_id(),
|
||||
]);
|
||||
|
||||
return $this->transform_db_records_to_entities($records);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first discussion in the specified forum.
|
||||
*
|
||||
|
@ -112,33 +112,24 @@ class post extends db_table_vault {
|
||||
bool $canseeprivatereplies,
|
||||
string $orderby = 'created ASC'
|
||||
) : array {
|
||||
$alias = $this->get_table_alias();
|
||||
|
||||
[
|
||||
'where' => $privatewhere,
|
||||
'params' => $privateparams,
|
||||
] = $this->get_private_reply_sql($user, $canseeprivatereplies);
|
||||
|
||||
$wheresql = "{$alias}.discussion = :discussionid {$privatewhere}";
|
||||
$orderbysql = $alias . '.' . $orderby;
|
||||
|
||||
$sql = $this->generate_get_records_sql($wheresql, $orderbysql);
|
||||
$records = $this->get_db()->get_records_sql($sql, array_merge([
|
||||
'discussionid' => $discussionid,
|
||||
], $privateparams));
|
||||
|
||||
return $this->transform_db_records_to_entities($records);
|
||||
return $this->get_from_discussion_ids($user, [$discussionid], $canseeprivatereplies, $orderby);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of posts for the given discussions.
|
||||
*
|
||||
* @param stdClass $user The user to check the unread count for
|
||||
* @param stdClass $user The user to load posts for.
|
||||
* @param int[] $discussionids The list of discussion ids to load posts for
|
||||
* @param bool $canseeprivatereplies Whether this user can see all private replies or not
|
||||
* @param string $orderby Order the results
|
||||
* @return post_entity[]
|
||||
*/
|
||||
public function get_from_discussion_ids(stdClass $user, array $discussionids, bool $canseeprivatereplies) : array {
|
||||
public function get_from_discussion_ids(
|
||||
stdClass $user,
|
||||
array $discussionids,
|
||||
bool $canseeprivatereplies,
|
||||
string $orderby = ''
|
||||
) : array {
|
||||
if (empty($discussionids)) {
|
||||
return [];
|
||||
}
|
||||
@ -153,12 +144,65 @@ class post extends db_table_vault {
|
||||
|
||||
$wheresql = "{$alias}.discussion {$insql} {$privatewhere}";
|
||||
|
||||
$sql = $this->generate_get_records_sql($wheresql, '');
|
||||
if ($orderby) {
|
||||
$orderbysql = $alias . '.' . $orderby;
|
||||
} else {
|
||||
$orderbysql = '';
|
||||
}
|
||||
|
||||
$sql = $this->generate_get_records_sql($wheresql, $orderbysql);
|
||||
$records = $this->get_db()->get_records_sql($sql, array_merge($params, $privateparams));
|
||||
|
||||
return $this->transform_db_records_to_entities($records);
|
||||
}
|
||||
|
||||
/**
|
||||
* The method returns posts made by the supplied users in the supplied discussions.
|
||||
*
|
||||
* @param stdClass $user Only used when restricting private replies
|
||||
* @param int[] $discussionids The list of discussion ids to load posts for
|
||||
* @param int[] $userids Only return posts made by these users
|
||||
* @param bool $canseeprivatereplies Whether this user can see all private replies or not
|
||||
* @param string $orderby Order the results
|
||||
* @return post_entity[]
|
||||
*/
|
||||
public function get_from_discussion_ids_and_user_ids(
|
||||
stdClass $user,
|
||||
array $discussionids,
|
||||
array $userids,
|
||||
bool $canseeprivatereplies,
|
||||
string $orderby = ''
|
||||
): array {
|
||||
if (empty($discussionids) || empty($userids)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$alias = $this->get_table_alias();
|
||||
|
||||
list($indiscussionssql, $indiscussionsparams) = $this->get_db()->get_in_or_equal($discussionids, SQL_PARAMS_NAMED);
|
||||
list($inuserssql, $inusersparams) = $this->get_db()->get_in_or_equal($userids, SQL_PARAMS_NAMED);
|
||||
|
||||
[
|
||||
'where' => $privatewhere,
|
||||
'params' => $privateparams,
|
||||
] = $this->get_private_reply_sql($user, $canseeprivatereplies);
|
||||
|
||||
$wheresql = "{$alias}.discussion {$indiscussionssql}
|
||||
AND {$alias}.userid {$inuserssql}
|
||||
{$privatewhere}";
|
||||
|
||||
if ($orderby) {
|
||||
$orderbysql = $alias . '.' . $orderby;
|
||||
} else {
|
||||
$orderbysql = '';
|
||||
}
|
||||
|
||||
$sql = $this->generate_get_records_sql($wheresql, $orderbysql);
|
||||
$records = $this->get_db()->get_records_sql($sql, array_merge($indiscussionsparams, $inusersparams, $privateparams));
|
||||
|
||||
return $this->transform_db_records_to_entities($records);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a list of replies to the given post. This will load all descendants of the post.
|
||||
* That is, all direct replies and replies to those replies etc.
|
||||
|
@ -328,6 +328,17 @@ $capabilities = array(
|
||||
'manager' => CAP_ALLOW
|
||||
)
|
||||
),
|
||||
'mod/forum:exportforum' => array(
|
||||
'riskbitmask' => RISK_PERSONAL,
|
||||
|
||||
'captype' => 'read',
|
||||
'contextlevel' => CONTEXT_MODULE,
|
||||
'archetypes' => array(
|
||||
'teacher' => CAP_ALLOW,
|
||||
'editingteacher' => CAP_ALLOW,
|
||||
'manager' => CAP_ALLOW,
|
||||
)
|
||||
),
|
||||
'mod/forum:exportpost' => array(
|
||||
|
||||
'riskbitmask' => RISK_PERSONAL,
|
||||
|
125
mod/forum/export.php
Normal file
125
mod/forum/export.php
Normal file
@ -0,0 +1,125 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* Page to export forum discussions.
|
||||
*
|
||||
* @package mod_forum
|
||||
* @copyright 2019 Simey Lameze <simey@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
define('NO_OUTPUT_BUFFERING', true);
|
||||
|
||||
require_once(__DIR__ . '/../../config.php');
|
||||
require_once($CFG->libdir . '/adminlib.php');
|
||||
require_once($CFG->libdir . '/dataformatlib.php');
|
||||
|
||||
$forumid = required_param('id', PARAM_INT);
|
||||
|
||||
$vaultfactory = mod_forum\local\container::get_vault_factory();
|
||||
$managerfactory = mod_forum\local\container::get_manager_factory();
|
||||
$legacydatamapperfactory = mod_forum\local\container::get_legacy_data_mapper_factory();
|
||||
|
||||
$forumvault = $vaultfactory->get_forum_vault();
|
||||
|
||||
$forum = $forumvault->get_from_id($forumid);
|
||||
if (empty($forum)) {
|
||||
throw new moodle_exception('Unable to find forum with id ' . $forumid);
|
||||
}
|
||||
|
||||
$capabilitymanager = $managerfactory->get_capability_manager($forum);
|
||||
if (!$capabilitymanager->can_export_forum($USER)) {
|
||||
throw new moodle_exception('cannotexportforum', 'forum');
|
||||
}
|
||||
|
||||
$course = $forum->get_course_record();
|
||||
$coursemodule = $forum->get_course_module_record();
|
||||
$cm = cm_info::create($coursemodule);
|
||||
|
||||
require_course_login($course, true, $cm);
|
||||
|
||||
$url = new moodle_url('/mod/forum/export.php');
|
||||
$pagetitle = get_string('export', 'mod_forum');
|
||||
$context = $forum->get_context();
|
||||
|
||||
$form = new mod_forum\form\export_form($url->out(false), [
|
||||
'forum' => $forum
|
||||
]);
|
||||
|
||||
if ($form->is_cancelled()) {
|
||||
redirect(new moodle_url('/mod/forum/view.php', ['id' => $cm->id]));
|
||||
} else if ($data = $form->get_data()) {
|
||||
require_sesskey();
|
||||
|
||||
$dataformat = $data->format;
|
||||
|
||||
$discussionvault = $vaultfactory->get_discussion_vault();
|
||||
$postvault = $vaultfactory->get_post_vault();
|
||||
$discussionids = [];
|
||||
if ($data->discussionids) {
|
||||
$discussionids = $data->discussionids;
|
||||
} else {
|
||||
$discussions = $discussionvault->get_all_discussions_in_forum($forum);
|
||||
$discussionids = array_map(function ($discussion) {
|
||||
return $discussion->get_id();
|
||||
}, $discussions);
|
||||
}
|
||||
|
||||
if ($data->userids) {
|
||||
$posts = $postvault->get_from_discussion_ids_and_user_ids($USER,
|
||||
$discussionids,
|
||||
$data->userids,
|
||||
$capabilitymanager->can_view_any_private_reply($USER));
|
||||
} else {
|
||||
$posts = $postvault->get_from_discussion_ids($USER,
|
||||
$discussionids,
|
||||
$capabilitymanager->can_view_any_private_reply($USER));
|
||||
}
|
||||
|
||||
$fields = ['id', 'discussion', 'parent', 'userid', 'created', 'modified', 'mailed', 'subject', 'message',
|
||||
'messageformat', 'messagetrust', 'attachment', 'totalscore', 'mailnow', 'deleted', 'privatereplyto'];
|
||||
|
||||
$datamapper = $legacydatamapperfactory->get_post_data_mapper();
|
||||
$exportdata = new ArrayObject($datamapper->to_legacy_objects($posts));
|
||||
$iterator = $exportdata->getIterator();
|
||||
|
||||
require_once($CFG->libdir . '/dataformatlib.php');
|
||||
$filename = clean_filename('discussion');
|
||||
download_as_dataformat($filename, $dataformat, $fields, $iterator, function($exportdata) use ($fields) {
|
||||
$data = $exportdata;
|
||||
foreach ($fields as $field) {
|
||||
// Convert any boolean fields to their integer equivalent for output.
|
||||
if (is_bool($data->$field)) {
|
||||
$data->$field = (int) $data->$field;
|
||||
}
|
||||
}
|
||||
return $data;
|
||||
});
|
||||
die;
|
||||
}
|
||||
|
||||
$PAGE->set_context($context);
|
||||
$PAGE->set_url($url);
|
||||
$PAGE->set_title($pagetitle);
|
||||
$PAGE->set_pagelayout('admin');
|
||||
$PAGE->set_heading($pagetitle);
|
||||
|
||||
echo $OUTPUT->header();
|
||||
echo $OUTPUT->heading($pagetitle);
|
||||
|
||||
$form->display();
|
||||
|
||||
echo $OUTPUT->footer();
|
@ -29,12 +29,14 @@ $string['addanewquestion'] = 'Add a new question';
|
||||
$string['addanewtopic'] = 'Add a new topic';
|
||||
$string['addtofavourites'] = 'Star this discussion';
|
||||
$string['advancedsearch'] = 'Advanced search';
|
||||
$string['alldiscussions'] = 'All discussions';
|
||||
$string['allforums'] = 'All forums';
|
||||
$string['allowdiscussions'] = 'Can a {$a} post to this forum?';
|
||||
$string['allowsallsubscribe'] = 'This forum allows everyone to choose whether to subscribe or not';
|
||||
$string['allowsdiscussions'] = 'This forum allows each person to start one discussion topic.';
|
||||
$string['allsubscribe'] = 'Subscribe to all forums';
|
||||
$string['allunsubscribe'] = 'Unsubscribe from all forums';
|
||||
$string['allusers'] = 'All users';
|
||||
$string['alreadyfirstpost'] = 'This is already the first post in the discussion';
|
||||
$string['anyfile'] = 'Any file';
|
||||
$string['areaattachment'] = 'Attachments';
|
||||
@ -66,6 +68,7 @@ $string['cannotcreateinstanceforteacher'] = 'Could not create new course module
|
||||
$string['cannotdeletepost'] = 'You can\'t delete this post!';
|
||||
$string['cannotdeletediscussioninsinglediscussion'] = 'You cannot delete the first post in a single discussion';
|
||||
$string['cannoteditposts'] = 'You can\'t edit other people\'s posts!';
|
||||
$string['cannotexportforum'] = 'You cannot export this forum';
|
||||
$string['cannotfinddiscussion'] = 'Could not find the discussion in this forum';
|
||||
$string['cannotfindfirstpost'] = 'Could not find the first post in this forum';
|
||||
$string['cannotfindorcreateforum'] = 'Could not find or create a main announcements forum for the site';
|
||||
@ -242,6 +245,7 @@ $string['everyonecannowchoose'] = 'Everyone can now choose to be subscribed';
|
||||
$string['everyoneisnowsubscribed'] = 'Everyone is now subscribed to this forum';
|
||||
$string['everyoneissubscribed'] = 'Everyone is subscribed to this forum';
|
||||
$string['existingsubscribers'] = 'Existing subscribers';
|
||||
$string['export'] = 'Export';
|
||||
$string['exportdiscussion'] = 'Export whole discussion to portfolio';
|
||||
$string['exportattachmentname'] = 'Export attachment {$a} to portfolio';
|
||||
$string['firstpost'] = 'First post';
|
||||
@ -268,6 +272,7 @@ $string['forum:deleteanypost'] = 'Delete any posts (anytime)';
|
||||
$string['forum:deleteownpost'] = 'Delete own posts (within deadline)';
|
||||
$string['forum:editanypost'] = 'Edit any post';
|
||||
$string['forum:exportdiscussion'] = 'Export whole discussion';
|
||||
$string['forum:exportforum'] = 'Export forum';
|
||||
$string['forum:exportownpost'] = 'Export own post';
|
||||
$string['forum:exportpost'] = 'Export post';
|
||||
$string['forumintro'] = 'Description';
|
||||
|
@ -5245,6 +5245,11 @@ function forum_extend_settings_navigation(settings_navigation $settingsnav, navi
|
||||
$PAGE->cm->context = context_module::instance($PAGE->cm->instance);
|
||||
}
|
||||
|
||||
$vaultfactory = mod_forum\local\container::get_vault_factory();
|
||||
$managerfactory = mod_forum\local\container::get_manager_factory();
|
||||
$forumvault = $vaultfactory->get_forum_vault();
|
||||
$forumentity = $forumvault->get_from_id($forumobject->id);
|
||||
|
||||
$params = $PAGE->url->params();
|
||||
if (!empty($params['d'])) {
|
||||
$discussionid = $params['d'];
|
||||
@ -5379,6 +5384,12 @@ function forum_extend_settings_navigation(settings_navigation $settingsnav, navi
|
||||
$url = new moodle_url(rss_get_url($PAGE->cm->context->id, $userid, "mod_forum", $forumobject->id));
|
||||
$forumnode->add($string, $url, settings_navigation::TYPE_SETTING, null, null, new pix_icon('i/rss', ''));
|
||||
}
|
||||
|
||||
$capabilitymanager = $managerfactory->get_capability_manager($forumentity);
|
||||
if ($capabilitymanager->can_export_forum($USER)) {
|
||||
$url = new moodle_url('/mod/forum/export.php', ['id' => $forumobject->id]);
|
||||
$forumnode->add(get_string('export', 'mod_forum'), $url, navigation_node::TYPE_SETTING);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
52
mod/forum/templates/form-user-selector-suggestion.mustache
Normal file
52
mod/forum/templates/form-user-selector-suggestion.mustache
Normal file
@ -0,0 +1,52 @@
|
||||
{{!
|
||||
This file is part of Moodle - http://moodle.org/
|
||||
|
||||
Moodle is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Moodle is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
}}
|
||||
{{!
|
||||
@template mod_forum/form-user-selector-suggestion
|
||||
|
||||
Moodle template for the list of valid options in an autocomplate form element.
|
||||
|
||||
Classes required for JS:
|
||||
* none
|
||||
|
||||
Data attributes required for JS:
|
||||
* none
|
||||
|
||||
Context variables required for this template:
|
||||
* fullname string Users full name
|
||||
* email string user email field
|
||||
|
||||
Example context (json):
|
||||
{
|
||||
"fullname": "Admin User",
|
||||
"extrafields": [
|
||||
{
|
||||
"name": "email",
|
||||
"value": "admin@example.com"
|
||||
},
|
||||
{
|
||||
"name": "phone1",
|
||||
"value": "0123456789"
|
||||
}
|
||||
]
|
||||
}
|
||||
}}
|
||||
<span>
|
||||
<span>{{fullname}}</span>
|
||||
{{#extrafields}}
|
||||
<span><small>{{value}}</small></span>
|
||||
{{/extrafields}}
|
||||
</span>
|
@ -194,6 +194,20 @@ class mod_forum_vaults_post_testcase extends advanced_testcase {
|
||||
$this->assertArrayHasKey($post2->id, $entities);
|
||||
$this->assertArrayHasKey($post3->id, $entities);
|
||||
$this->assertArrayHasKey($post4->id, $entities);
|
||||
|
||||
// Test ordering by id descending.
|
||||
$entities = $this->vault->get_from_discussion_ids($user, [$discussion1->id, $discussion2->id], false, 'id DESC');
|
||||
$this->assertEquals($post4->id, array_values($entities)[0]->get_id());
|
||||
$this->assertEquals($post3->id, array_values($entities)[1]->get_id());
|
||||
$this->assertEquals($post2->id, array_values($entities)[2]->get_id());
|
||||
$this->assertEquals($post1->id, array_values($entities)[3]->get_id());
|
||||
|
||||
// Test ordering by id ascending.
|
||||
$entities = $this->vault->get_from_discussion_ids($user, [$discussion1->id, $discussion2->id], false, 'id ASC');
|
||||
$this->assertEquals($post1->id, array_values($entities)[0]->get_id());
|
||||
$this->assertEquals($post2->id, array_values($entities)[1]->get_id());
|
||||
$this->assertEquals($post3->id, array_values($entities)[2]->get_id());
|
||||
$this->assertEquals($post4->id, array_values($entities)[3]->get_id());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -213,38 +227,85 @@ class mod_forum_vaults_post_testcase extends advanced_testcase {
|
||||
|
||||
[$student, $otherstudent] = $this->helper_create_users($course, 2, 'student');
|
||||
[$teacher, $otherteacher] = $this->helper_create_users($course, 2, 'teacher');
|
||||
[$discussion, $post] = $this->helper_post_to_forum($forum, $teacher);
|
||||
$reply = $this->helper_post_to_discussion($forum, $discussion, $teacher, [
|
||||
'privatereplyto' => $student->id,
|
||||
]);
|
||||
|
||||
// Create the posts structure below.
|
||||
// Forum:
|
||||
// -> Post (student 1)
|
||||
// ---> Post private reply (teacher 1)
|
||||
// -> Otherpost (teacher 1)
|
||||
// ---> Otherpost private reply (teacher 2)
|
||||
// ---> Otherpost reply (student 1)
|
||||
// ----> Otherpost reply private reply (teacher 1).
|
||||
[$discussion, $post] = $this->helper_post_to_forum($forum, $student);
|
||||
$postprivatereply = $this->helper_reply_to_post($post, $teacher, [
|
||||
'privatereplyto' => $student->id
|
||||
]);
|
||||
[$otherdiscussion, $otherpost] = $this->helper_post_to_forum($forum, $teacher);
|
||||
$otherpostprivatereply = $this->helper_reply_to_post($otherpost, $otherteacher, [
|
||||
'privatereplyto' => $teacher->id,
|
||||
]);
|
||||
$otherpostreply = $this->helper_reply_to_post($otherpost, $student);
|
||||
$otherpostreplyprivatereply = $this->helper_reply_to_post($otherpostreply, $teacher, [
|
||||
'privatereplyto' => $student->id
|
||||
]);
|
||||
|
||||
// The user is the author.
|
||||
// Teacher 1. Request all posts from the vault, telling the vault that the teacher CAN see private replies made by anyone.
|
||||
$entities = $this->vault->get_from_discussion_ids($teacher, [$discussion->id, $otherdiscussion->id], true);
|
||||
$this->assertCount(3, $entities);
|
||||
$this->assertCount(6, $entities);
|
||||
$this->assertArrayHasKey($post->id, $entities); // Order is not guaranteed, so just verify element existence.
|
||||
$this->assertArrayHasKey($reply->id, $entities);
|
||||
$this->assertArrayHasKey($postprivatereply->id, $entities);
|
||||
$this->assertArrayHasKey($otherpost->id, $entities);
|
||||
$this->assertArrayHasKey($otherpostprivatereply->id, $entities);
|
||||
$this->assertArrayHasKey($otherpostreply->id, $entities);
|
||||
$this->assertArrayHasKey($otherpostreplyprivatereply->id, $entities);
|
||||
|
||||
// The user is the intended recipient.
|
||||
// Student 1. Request all posts from the vault, telling the vault that the student CAN'T see private replies made by anyone.
|
||||
// Teacher2's private reply to otherpost is omitted.
|
||||
$entities = $this->vault->get_from_discussion_ids($student, [$discussion->id, $otherdiscussion->id], false);
|
||||
$this->assertCount(3, $entities);
|
||||
$this->assertCount(5, $entities);
|
||||
$this->assertArrayHasKey($post->id, $entities); // Order is not guaranteed, so just verify element existence.
|
||||
$this->assertArrayHasKey($reply->id, $entities);
|
||||
$this->assertArrayHasKey($postprivatereply->id, $entities);
|
||||
$this->assertArrayHasKey($otherpost->id, $entities);
|
||||
$this->assertArrayHasKey($otherpostreply->id, $entities);
|
||||
$this->assertArrayHasKey($otherpostreplyprivatereply->id, $entities);
|
||||
|
||||
// The user is another teacher..
|
||||
// Student 1. Request all posts from the vault, telling the vault that student CAN see all private replies made.
|
||||
// The private reply made by teacher 2 to otherpost is now included.
|
||||
$entities = $this->vault->get_from_discussion_ids($student, [$discussion->id, $otherdiscussion->id], true);
|
||||
$this->assertCount(6, $entities);
|
||||
$this->assertArrayHasKey($post->id, $entities); // Order is not guaranteed, so just verify element existence.
|
||||
$this->assertArrayHasKey($postprivatereply->id, $entities);
|
||||
$this->assertArrayHasKey($otherpost->id, $entities);
|
||||
$this->assertArrayHasKey($otherpostprivatereply->id, $entities);
|
||||
$this->assertArrayHasKey($otherpostreply->id, $entities);
|
||||
$this->assertArrayHasKey($otherpostreplyprivatereply->id, $entities);
|
||||
|
||||
// Teacher 2. Request all posts from the vault, telling the vault that teacher2 CAN see all private replies made.
|
||||
$entities = $this->vault->get_from_discussion_ids($otherteacher, [$discussion->id, $otherdiscussion->id], true);
|
||||
$this->assertCount(6, $entities);
|
||||
$this->assertArrayHasKey($post->id, $entities); // Order is not guaranteed, so just verify element existence.
|
||||
$this->assertArrayHasKey($postprivatereply->id, $entities);
|
||||
$this->assertArrayHasKey($otherpost->id, $entities);
|
||||
$this->assertArrayHasKey($otherpostprivatereply->id, $entities);
|
||||
$this->assertArrayHasKey($otherpostreply->id, $entities);
|
||||
$this->assertArrayHasKey($otherpostreplyprivatereply->id, $entities);
|
||||
|
||||
// Teacher 2. Request all posts from the vault, telling the vault that teacher2 CANNOT see all private replies made.
|
||||
// The private replies not relating to teacher 2 directly are omitted.
|
||||
$entities = $this->vault->get_from_discussion_ids($otherteacher, [$discussion->id, $otherdiscussion->id], false);
|
||||
$this->assertCount(4, $entities);
|
||||
$this->assertArrayHasKey($post->id, $entities); // Order is not guaranteed, so just verify element existence.
|
||||
$this->assertArrayHasKey($otherpost->id, $entities);
|
||||
$this->assertArrayHasKey($otherpostprivatereply->id, $entities);
|
||||
$this->assertArrayHasKey($otherpostreply->id, $entities);
|
||||
|
||||
// Student 2. Request all posts from the vault, telling the vault that student2 CAN'T see all private replies made.
|
||||
// All private replies are omitted, as none relate to student2.
|
||||
$entities = $this->vault->get_from_discussion_ids($otherstudent, [$discussion->id, $otherdiscussion->id], false);
|
||||
$this->assertCount(3, $entities);
|
||||
$this->assertArrayHasKey($post->id, $entities); // Order is not guaranteed, so just verify element existence.
|
||||
$this->assertArrayHasKey($reply->id, $entities);
|
||||
$this->assertArrayHasKey($otherpost->id, $entities);
|
||||
|
||||
// The user is a different student.
|
||||
$entities = $this->vault->get_from_discussion_ids($otherstudent, [$discussion->id, $otherdiscussion->id], false);
|
||||
$this->assertCount(2, $entities);
|
||||
$this->assertArrayHasKey($post->id, $entities); // Order is not guaranteed, so just verify element existence.
|
||||
$this->assertArrayHasKey($otherpost->id, $entities);
|
||||
$this->assertArrayHasKey($otherpostreply->id, $entities);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -877,4 +938,175 @@ class mod_forum_vaults_post_testcase extends advanced_testcase {
|
||||
|
||||
$this->assertEquals([], $this->vault->get_first_post_for_discussion_ids([]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_from_discussion_ids_and_user_ids.
|
||||
*
|
||||
* @covers ::get_from_discussion_ids_and_user_ids
|
||||
* @covers ::<!public>
|
||||
*/
|
||||
public function test_get_from_discussion_ids_and_user_ids() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$datagenerator = $this->getDataGenerator();
|
||||
$course = $datagenerator->create_course();
|
||||
[$user, $user2] = $this->helper_create_users($course, 2, 'student');
|
||||
$forum = $datagenerator->create_module('forum', ['course' => $course->id]);
|
||||
|
||||
[$discussion1, $post1] = $this->helper_post_to_forum($forum, $user);
|
||||
$post2 = $this->helper_reply_to_post($post1, $user);
|
||||
$post3 = $this->helper_reply_to_post($post1, $user);
|
||||
|
||||
[$discussion2, $post4] = $this->helper_post_to_forum($forum, $user);
|
||||
$discussionids = [$discussion1->id, $discussion2->id];
|
||||
|
||||
$userids = [$user->id];
|
||||
$entities = array_values($this->vault->get_from_discussion_ids_and_user_ids($user,
|
||||
$discussionids,
|
||||
$userids,
|
||||
true,
|
||||
'id ASC'));
|
||||
|
||||
$this->assertCount(4, $entities);
|
||||
$this->assertEquals($post1->id, $entities[0]->get_id());
|
||||
$this->assertEquals($post2->id, $entities[1]->get_id());
|
||||
$this->assertEquals($post3->id, $entities[2]->get_id());
|
||||
$this->assertEquals($post4->id, $entities[3]->get_id());
|
||||
|
||||
$entities = $this->vault->get_from_discussion_ids_and_user_ids($user, [$discussion1->id], $userids, false);
|
||||
$this->assertCount(3, $entities);
|
||||
$this->assertArrayHasKey($post1->id, $entities);
|
||||
$this->assertArrayHasKey($post2->id, $entities);
|
||||
$this->assertArrayHasKey($post3->id, $entities);
|
||||
|
||||
$entities = $this->vault->get_from_discussion_ids_and_user_ids($user, [$discussion1->id, $discussion2->id],
|
||||
[$user->id, $user2->id], false);
|
||||
$this->assertCount(4, $entities);
|
||||
$this->assertArrayHasKey($post1->id, $entities);
|
||||
$this->assertArrayHasKey($post2->id, $entities);
|
||||
$this->assertArrayHasKey($post3->id, $entities);
|
||||
$this->assertArrayHasKey($post4->id, $entities);
|
||||
|
||||
// Test ordering by id descending.
|
||||
$entities = $this->vault->get_from_discussion_ids_and_user_ids($user, [$discussion1->id, $discussion2->id],
|
||||
[$user->id], false, 'id DESC');
|
||||
$this->assertEquals($post4->id, array_values($entities)[0]->get_id());
|
||||
$this->assertEquals($post3->id, array_values($entities)[1]->get_id());
|
||||
$this->assertEquals($post2->id, array_values($entities)[2]->get_id());
|
||||
$this->assertEquals($post1->id, array_values($entities)[3]->get_id());
|
||||
|
||||
// Test ordering by id ascending.
|
||||
$entities = $this->vault->get_from_discussion_ids_and_user_ids($user, [$discussion1->id, $discussion2->id],
|
||||
[$user->id], false, 'id ASC');
|
||||
$this->assertEquals($post1->id, array_values($entities)[0]->get_id());
|
||||
$this->assertEquals($post2->id, array_values($entities)[1]->get_id());
|
||||
$this->assertEquals($post3->id, array_values($entities)[2]->get_id());
|
||||
$this->assertEquals($post4->id, array_values($entities)[3]->get_id());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test get_from_discussion_ids_and_user_ids when no discussion ids were provided.
|
||||
*
|
||||
* @covers ::get_from_discussion_ids_and_user_ids
|
||||
*/
|
||||
public function test_get_from_discussion_ids_and_user_ids_empty() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$datagenerator = $this->getDataGenerator();
|
||||
$course = $datagenerator->create_course();
|
||||
[$student1, $student2] = $this->helper_create_users($course, 2, 'student');
|
||||
$forum = $datagenerator->create_module('forum', ['course' => $course->id]);
|
||||
[$discussion, $post] = $this->helper_post_to_forum($forum, $student1);
|
||||
$this->assertEquals([], $this->vault->get_from_discussion_ids_and_user_ids($student1, [], [], false));
|
||||
$this->assertEquals([], $this->vault->get_from_discussion_ids_and_user_ids($student1, [$discussion->id], [], false));
|
||||
$this->assertEquals([], $this->vault->get_from_discussion_ids_and_user_ids($student1, [], [$student2->id], false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that selecting posts in a discussion only returns posts that the user can see, when considering private
|
||||
* replies.
|
||||
*
|
||||
* @covers ::get_from_discussion_ids_and_user_ids
|
||||
* @covers ::<!public>
|
||||
*/
|
||||
public function test_get_from_discussion_ids_and_user_ids_private_replies() {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$forum = $this->getDataGenerator()->create_module('forum', [
|
||||
'course' => $course->id,
|
||||
]);
|
||||
|
||||
[$student, $otherstudent] = $this->helper_create_users($course, 2, 'student');
|
||||
[$teacher, $otherteacher] = $this->helper_create_users($course, 2, 'teacher');
|
||||
|
||||
// Create the posts structure below.
|
||||
// Forum:
|
||||
// -> Post (student 1)
|
||||
// ---> Post private reply (teacher 1)
|
||||
// -> Otherpost (teacher 1)
|
||||
// ---> Otherpost private reply (teacher 2)
|
||||
// ---> Otherpost reply (student 1)
|
||||
// ----> Otherpost reply private reply (teacher 1).
|
||||
[$discussion, $post] = $this->helper_post_to_forum($forum, $student);
|
||||
$postprivatereply = $this->helper_reply_to_post($post, $teacher, [
|
||||
'privatereplyto' => $student->id
|
||||
]);
|
||||
[$otherdiscussion, $otherpost] = $this->helper_post_to_forum($forum, $teacher);
|
||||
$otherpostprivatereply = $this->helper_reply_to_post($otherpost, $otherteacher, [
|
||||
'privatereplyto' => $teacher->id,
|
||||
]);
|
||||
$otherpostreply = $this->helper_reply_to_post($otherpost, $student);
|
||||
$otherpostreplyprivatereply = $this->helper_reply_to_post($otherpostreply, $teacher, [
|
||||
'privatereplyto' => $student->id
|
||||
]);
|
||||
|
||||
$userids = [$otherstudent->id, $teacher->id, $otherteacher->id];
|
||||
$discussionids = [$discussion->id, $otherdiscussion->id];
|
||||
|
||||
// Teacher 1. Request all posts from the vault, telling the vault that the teacher CAN see private replies made by anyone.
|
||||
$entities = $this->vault->get_from_discussion_ids_and_user_ids($teacher, $discussionids, $userids, true);
|
||||
$this->assertCount(4, $entities);
|
||||
$this->assertArrayHasKey($postprivatereply->id, $entities);
|
||||
$this->assertArrayHasKey($otherpost->id, $entities);
|
||||
$this->assertArrayHasKey($otherpostprivatereply->id, $entities);
|
||||
$this->assertArrayHasKey($otherpostreplyprivatereply->id, $entities);
|
||||
|
||||
// Student 1. Request all posts from the vault, telling the vault that the student CAN'T see private replies made by anyone.
|
||||
// Teacher2's private reply to otherpost is omitted.
|
||||
$entities = $this->vault->get_from_discussion_ids_and_user_ids($student, $discussionids, $userids, false);
|
||||
$this->assertCount(3, $entities);
|
||||
$this->assertArrayHasKey($postprivatereply->id, $entities);
|
||||
$this->assertArrayHasKey($otherpost->id, $entities);
|
||||
$this->assertArrayHasKey($otherpostreplyprivatereply->id, $entities);
|
||||
|
||||
// Student 1. Request all posts from the vault, telling the vault that student CAN see all private replies made.
|
||||
// The private reply made by teacher 2 to otherpost is now included.
|
||||
$entities = $this->vault->get_from_discussion_ids_and_user_ids($student, $discussionids, $userids, true);
|
||||
$this->assertCount(4, $entities);
|
||||
$this->assertArrayHasKey($postprivatereply->id, $entities);
|
||||
$this->assertArrayHasKey($otherpost->id, $entities);
|
||||
$this->assertArrayHasKey($otherpostprivatereply->id, $entities);
|
||||
$this->assertArrayHasKey($otherpostreplyprivatereply->id, $entities);
|
||||
|
||||
// Teacher 2. Request all posts from the vault, telling the vault that teacher2 CAN see all private replies made.
|
||||
$entities = $this->vault->get_from_discussion_ids_and_user_ids($otherteacher, $discussionids, $userids, true);
|
||||
$this->assertCount(4, $entities);
|
||||
$this->assertArrayHasKey($otherpost->id, $entities);
|
||||
$this->assertArrayHasKey($otherpostprivatereply->id, $entities);
|
||||
$this->assertArrayHasKey($otherpostreplyprivatereply->id, $entities);
|
||||
|
||||
// Teacher 2. Request all posts from the vault, telling the vault that teacher2 CANNOT see all private replies made.
|
||||
// The private replies not relating to teacher 2 directly are omitted.
|
||||
$entities = $this->vault->get_from_discussion_ids_and_user_ids($otherteacher, $discussionids, $userids, false);
|
||||
$this->assertCount(2, $entities);
|
||||
$this->assertArrayHasKey($otherpost->id, $entities);
|
||||
$this->assertArrayHasKey($otherpostprivatereply->id, $entities);
|
||||
|
||||
// Student 2. Request all posts from the vault, telling the vault that student2 CAN'T see all private replies made.
|
||||
// All private replies are omitted, as none relate to student2.
|
||||
$entities = $this->vault->get_from_discussion_ids_and_user_ids($otherstudent, $discussionids, $userids, false);
|
||||
$this->assertCount(1, $entities);
|
||||
$this->assertArrayHasKey($otherpost->id, $entities);
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,6 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$plugin->version = 2019052000; // The current module version (Date: YYYYMMDDXX)
|
||||
$plugin->version = 2019052001; // The current module version (Date: YYYYMMDDXX)
|
||||
$plugin->requires = 2019051100; // Requires this Moodle version
|
||||
$plugin->component = 'mod_forum'; // Full name of the plugin (used for diagnostics)
|
||||
|
@ -29,7 +29,7 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$version = 2019092000.00; // YYYYMMDD = weekly release date of this DEV branch.
|
||||
$version = 2019092000.01; // YYYYMMDD = weekly release date of this DEV branch.
|
||||
// RR = release increments - 00 in DEV branches.
|
||||
// .XX = incremental changes.
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user