first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
+141
View File
@@ -0,0 +1,141 @@
<?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/>.
/**
* Cohort related management functions, this file needs to be included manually.
*
* @package core_cohort
* @copyright 2010 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require('../config.php');
require_once($CFG->dirroot.'/cohort/locallib.php');
$id = required_param('id', PARAM_INT);
$returnurl = optional_param('returnurl', '', PARAM_LOCALURL);
require_login();
$cohort = $DB->get_record('cohort', array('id'=>$id), '*', MUST_EXIST);
$context = context::instance_by_id($cohort->contextid, MUST_EXIST);
require_capability('moodle/cohort:assign', $context);
$PAGE->set_context($context);
$PAGE->set_url('/cohort/assign.php', array('id'=>$id));
$PAGE->set_pagelayout('admin');
if ($returnurl) {
$returnurl = new moodle_url($returnurl);
} else {
$returnurl = new moodle_url('/cohort/index.php', array('contextid' => $cohort->contextid));
}
if (!empty($cohort->component)) {
// We can not manually edit cohorts that were created by external systems, sorry.
redirect($returnurl);
}
if (optional_param('cancel', false, PARAM_BOOL)) {
redirect($returnurl);
}
if ($context->contextlevel == CONTEXT_COURSECAT) {
$category = $DB->get_record('course_categories', array('id'=>$context->instanceid), '*', MUST_EXIST);
navigation_node::override_active_url(new moodle_url('/cohort/index.php', array('contextid'=>$cohort->contextid)));
} else {
navigation_node::override_active_url(new moodle_url('/cohort/index.php', array()));
}
$PAGE->navbar->add(get_string('assign', 'cohort'));
$PAGE->set_title(get_string('assigncohorts', 'cohort'));
$PAGE->set_heading($COURSE->fullname);
echo $OUTPUT->header();
echo $OUTPUT->heading(get_string('assignto', 'cohort', format_string($cohort->name)));
echo $OUTPUT->notification(get_string('removeuserwarning', 'core_cohort'));
// Get the user_selector we will need.
$potentialuserselector = new cohort_candidate_selector('addselect', array('cohortid'=>$cohort->id, 'accesscontext'=>$context));
$existinguserselector = new cohort_existing_selector('removeselect', array('cohortid'=>$cohort->id, 'accesscontext'=>$context));
// Process incoming user assignments to the cohort
if (optional_param('add', false, PARAM_BOOL) && confirm_sesskey()) {
$userstoassign = $potentialuserselector->get_selected_users();
if (!empty($userstoassign)) {
foreach ($userstoassign as $adduser) {
cohort_add_member($cohort->id, $adduser->id);
}
$potentialuserselector->invalidate_selected_users();
$existinguserselector->invalidate_selected_users();
}
}
// Process removing user assignments to the cohort
if (optional_param('remove', false, PARAM_BOOL) && confirm_sesskey()) {
$userstoremove = $existinguserselector->get_selected_users();
if (!empty($userstoremove)) {
foreach ($userstoremove as $removeuser) {
cohort_remove_member($cohort->id, $removeuser->id);
}
$potentialuserselector->invalidate_selected_users();
$existinguserselector->invalidate_selected_users();
}
}
// Print the form.
?>
<form id="assignform" method="post" action="<?php echo $PAGE->url ?>"><div>
<input type="hidden" name="sesskey" value="<?php echo sesskey() ?>" />
<input type="hidden" name="returnurl" value="<?php echo $returnurl->out_as_local_url() ?>" />
<table summary="" class="generaltable generalbox boxaligncenter" cellspacing="0">
<tr>
<td id="existingcell">
<p><label for="removeselect"><?php print_string('currentusers', 'cohort'); ?></label></p>
<?php $existinguserselector->display() ?>
</td>
<td id="buttonscell">
<div id="addcontrols">
<input class="btn btn-secondary" name="add" id="add" type="submit" value="<?php echo $OUTPUT->larrow() . '&nbsp;' .
s(get_string('add')); ?>" title="<?php p(get_string('add')); ?>" /><br />
</div>
<div id="removecontrols">
<input class="btn btn-secondary" name="remove" id="remove" type="submit"
value="<?php echo s(get_string('remove')) . '&nbsp;' . $OUTPUT->rarrow(); ?>"
title="<?php p(get_string('remove')); ?>" />
</div>
</td>
<td id="potentialcell">
<p><label for="addselect"><?php print_string('potusers', 'cohort'); ?></label></p>
<?php $potentialuserselector->display() ?>
</td>
</tr>
<tr><td colspan="3" id='backcell'>
<input class="btn btn-secondary" type="submit" name="cancel" value="<?php p(get_string('backtocohorts', 'cohort')); ?>" />
</td></tr>
</table>
</div></form>
<?php
echo $OUTPUT->footer();
@@ -0,0 +1,124 @@
<?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/>.
namespace core_cohort\customfield;
use core_customfield\handler;
use core_customfield\field_controller;
/**
* Cohort handler for custom fields.
*
* @package core_cohort
* @copyright 2023 Dmitrii Metelkin <dmitriim@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cohort_handler extends handler {
/**
* @var cohort_handler
*/
static protected $singleton;
/**
* Returns a singleton.
*
* @param int $itemid
* @return \core_customfield\handler
*/
public static function create(int $itemid = 0): handler {
if (static::$singleton === null) {
self::$singleton = new static(0);
}
return self::$singleton;
}
/**
* Run reset code after unit tests to reset the singleton usage.
*/
public static function reset_caches(): void {
if (!PHPUNIT_TEST) {
throw new \coding_exception('This feature is only intended for use in unit tests');
}
static::$singleton = null;
}
/**
* The current user can configure custom fields on this component.
*
* @return bool true if the current can configure custom fields, false otherwise
*/
public function can_configure(): bool {
return has_capability('moodle/cohort:configurecustomfields', $this->get_configuration_context());
}
/**
* The current user can edit custom fields on the given cohort.
*
* @param field_controller $field
* @param int $instanceid id of the cohort to test edit permission
* @return bool true if the current can edit custom field, false otherwise
*/
public function can_edit(field_controller $field, int $instanceid = 0): bool {
return has_capability('moodle/cohort:manage', $this->get_instance_context($instanceid));
}
/**
* The current user can view custom fields on the given cohort.
*
* @param field_controller $field
* @param int $instanceid id of the cohort to test edit permission
* @return bool true if the current can view custom field, false otherwise
*/
public function can_view(field_controller $field, int $instanceid): bool {
return has_any_capability(['moodle/cohort:manage', 'moodle/cohort:view'], $this->get_instance_context($instanceid));
}
/**
* Context that should be used for new categories created by this handler.
*
* @return \context the context for configuration
*/
public function get_configuration_context(): \context {
return \context_system::instance();
}
/**
* URL for configuration of the fields on this handler.
*
* @return \moodle_url The URL to configure custom fields for this component
*/
public function get_configuration_url(): \moodle_url {
return new \moodle_url('/cohort/customfield.php');
}
/**
* Returns the context for the data associated with the given instanceid.
*
* @param int $instanceid id of the record to get the context for
* @return \context the context for the given record
*/
public function get_instance_context(int $instanceid = 0): \context {
global $DB;
if ($instanceid > 0) {
$cohort = $DB->get_record('cohort', ['id' => $instanceid], '*', MUST_EXIST);
return \context::instance_by_id($cohort->contextid, MUST_EXIST);
} else {
return \context_system::instance();
}
}
}
+89
View File
@@ -0,0 +1,89 @@
<?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/>.
/**
* Class for exporting a cohort summary from an stdClass.
*
* @package core_cohort
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_cohort\external;
defined('MOODLE_INTERNAL') || die();
use renderer_base;
/**
* Class for exporting a cohort summary from an stdClass.
*
* @copyright 2015 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cohort_summary_exporter extends \core\external\exporter {
protected static function define_related() {
// Cohorts can exist on a category context.
return array('context' => '\\context');
}
public static function define_properties() {
return array(
'id' => array(
'type' => PARAM_INT,
),
'name' => array(
'type' => PARAM_TEXT,
),
'idnumber' => array(
'type' => PARAM_RAW, // ID numbers are plain text.
'default' => '',
'null' => NULL_ALLOWED
),
'description' => array(
'type' => PARAM_TEXT,
'default' => '',
'null' => NULL_ALLOWED
),
'descriptionformat' => array(
'type' => PARAM_INT,
'default' => FORMAT_HTML,
'null' => NULL_ALLOWED
),
'visible' => array(
'type' => PARAM_BOOL,
),
'theme' => array(
'type' => PARAM_THEME,
'null' => NULL_ALLOWED
)
);
}
public static function define_other_properties() {
return array(
'contextname' => array(
// The method context::get_context_name() already formats the string, and may return HTML.
'type' => PARAM_RAW
),
);
}
protected function get_other_values(renderer_base $output) {
return array(
'contextname' => $this->related['context']->get_context_name()
);
}
}
+74
View File
@@ -0,0 +1,74 @@
<?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/>.
/**
* Contains class core_cohort\output\cohortidnumber
*
* @package core_cohort
* @copyright 2016 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_cohort\output;
use core_external\external_api;
use lang_string;
/**
* Class to prepare a cohort idnumber for display.
*
* @package core_cohort
* @copyright 2016 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cohortidnumber extends \core\output\inplace_editable {
/**
* Constructor.
*
* @param stdClass $cohort
*/
public function __construct($cohort) {
$cohortcontext = \context::instance_by_id($cohort->contextid);
$editable = has_capability('moodle/cohort:manage', $cohortcontext);
$displayvalue = s($cohort->idnumber); // All idnumbers are plain text.
parent::__construct('core_cohort', 'cohortidnumber', $cohort->id, $editable,
$displayvalue,
$cohort->idnumber,
new lang_string('editcohortidnumber', 'cohort'),
new lang_string('newidnumberfor', 'cohort', $displayvalue));
}
/**
* Updates cohort name and returns instance of this object
*
* @param int $cohortid
* @param string $newvalue
* @return static
*/
public static function update($cohortid, $newvalue) {
global $DB;
$cohort = $DB->get_record('cohort', array('id' => $cohortid), '*', MUST_EXIST);
$cohortcontext = \context::instance_by_id($cohort->contextid);
external_api::validate_context($cohortcontext);
require_capability('moodle/cohort:manage', $cohortcontext);
if ($newvalue == '' || !$DB->record_exists_select('cohort', 'idnumber = ? AND id != ?', [$newvalue, $cohort->id])) {
$record = (object) ['id' => $cohort->id, 'idnumber' => $newvalue, 'contextid' => $cohort->contextid];
cohort_update_cohort($record);
$cohort->idnumber = $newvalue;
}
return new static($cohort);
}
}
+75
View File
@@ -0,0 +1,75 @@
<?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/>.
/**
* Contains class core_cohort\output\cohortname
*
* @package core_cohort
* @copyright 2016 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_cohort\output;
use core_external\external_api;
use lang_string;
/**
* Class to prepare a cohort name for display.
*
* @package core_cohort
* @copyright 2016 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cohortname extends \core\output\inplace_editable {
/**
* Constructor.
*
* @param stdClass $cohort
*/
public function __construct($cohort) {
$cohortcontext = \context::instance_by_id($cohort->contextid);
$editable = has_capability('moodle/cohort:manage', $cohortcontext);
$displayvalue = format_string($cohort->name, true, array('context' => $cohortcontext));
parent::__construct('core_cohort', 'cohortname', $cohort->id, $editable,
$displayvalue,
$cohort->name,
new lang_string('editcohortname', 'cohort'),
new lang_string('newnamefor', 'cohort', $displayvalue));
}
/**
* Updates cohort name and returns instance of this object
*
* @param int $cohortid
* @param string $newvalue
* @return static
*/
public static function update($cohortid, $newvalue) {
global $DB;
$cohort = $DB->get_record('cohort', array('id' => $cohortid), '*', MUST_EXIST);
$cohortcontext = \context::instance_by_id($cohort->contextid);
external_api::validate_context($cohortcontext);
require_capability('moodle/cohort:manage', $cohortcontext);
$newvalue = clean_param($newvalue, PARAM_TEXT);
if (strval($newvalue) !== '') {
$record = (object)array('id' => $cohort->id, 'name' => $newvalue, 'contextid' => $cohort->contextid);
cohort_update_cohort($record);
$cohort->name = $newvalue;
}
return new static($cohort);
}
}
+230
View File
@@ -0,0 +1,230 @@
<?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/>.
/**
* Privacy class for requesting user data.
*
* @package core_cohort
* @copyright 2018 Sara Arjona <sara@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_cohort\privacy;
defined('MOODLE_INTERNAL') || die();
use core_privacy\local\metadata\collection;
use core_privacy\local\request\contextlist;
use core_privacy\local\request\approved_contextlist;
use core_privacy\local\request\transform;
use core_privacy\local\request\writer;
use core_privacy\local\request\userlist;
use core_privacy\local\request\approved_userlist;
/**
* Privacy class for requesting user data.
*
* @copyright 2018 Sara Arjona <sara@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements
\core_privacy\local\metadata\provider,
\core_privacy\local\request\core_userlist_provider,
\core_privacy\local\request\plugin\provider {
/**
* Return the fields which contain personal data.
*
* @param collection $collection The initialised collection to add items to.
* @return collection A listing of user data stored through this system.
*/
public static function get_metadata(collection $collection): collection {
$collection->add_database_table('cohort_members', [
'cohortid' => 'privacy:metadata:cohort_members:cohortid',
'userid' => 'privacy:metadata:cohort_members:userid',
'timeadded' => 'privacy:metadata:cohort_members:timeadded'
], 'privacy:metadata:cohort_members');
return $collection;
}
/**
* Get the list of contexts that contain user information for the specified user.
*
* @param int $userid The user to search.
* @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin.
*/
public static function get_contexts_for_userid(int $userid): contextlist {
$sql = "SELECT ctx.id
FROM {context} ctx
INNER JOIN {cohort} c ON c.contextid = ctx.id
INNER JOIN {cohort_members} cm ON cm.cohortid = c.id
WHERE cm.userid = :userid AND (ctx.contextlevel = :contextlevel1 OR ctx.contextlevel = :contextlevel2)";
$params = [
'userid' => $userid,
'contextlevel1' => CONTEXT_SYSTEM,
'contextlevel2' => CONTEXT_COURSECAT,
];
$contextlist = new contextlist();
$contextlist->add_from_sql($sql, $params);
return $contextlist;
}
/**
* Get the list of users within a specific context.
*
* @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination.
*/
public static function get_users_in_context(userlist $userlist) {
$context = $userlist->get_context();
if (!$context instanceof \context_system && !$context instanceof \context_coursecat) {
return;
}
$params = ['contextid' => $context->id];
$sql = "SELECT cm.userid
FROM {cohort_members} cm
JOIN {cohort} c ON cm.cohortid = c.id
WHERE c.contextid = :contextid";
$userlist->add_from_sql('userid', $sql, $params);
}
/**
* Export all user data for the specified user, in the specified contexts.
*
* @param approved_contextlist $contextlist The approved contexts to export information for.
*/
public static function export_user_data(approved_contextlist $contextlist) {
global $DB;
// Remove contexts different from SYSTEM or COURSECAT.
$contexts = array_reduce($contextlist->get_contexts(), function($carry, $context) {
if ($context->contextlevel == CONTEXT_SYSTEM || $context->contextlevel == CONTEXT_COURSECAT) {
$carry[] = $context->id;
}
return $carry;
}, []);
if (empty($contexts)) {
return;
}
// Get cohort data.
$userid = $contextlist->get_user()->id;
list($contextsql, $contextparams) = $DB->get_in_or_equal($contexts, SQL_PARAMS_NAMED);
$sql = "SELECT c.name,
c.idnumber,
c.description,
c.visible,
cm.timeadded,
ctx.id as contextid
FROM {context} ctx
INNER JOIN {cohort} c ON c.contextid = ctx.id
INNER JOIN {cohort_members} cm ON cm.cohortid = c.id
WHERE ctx.id {$contextsql}
AND cm.userid = :userid";
$params = [
'userid' => $userid
] + $contextparams;
$cohorts = $DB->get_recordset_sql($sql, $params);
foreach ($cohorts as $cohort) {
$alldata[$cohort->contextid][] = (object)[
'name' => $cohort->name,
'idnumber' => $cohort->idnumber,
'visible' => transform::yesno($cohort->visible),
'timeadded' => transform::datetime($cohort->timeadded),
];
}
$cohorts->close();
// Export cohort data.
array_walk($alldata, function($data, $contextid) {
$context = \context::instance_by_id($contextid);
writer::with_context($context)->export_related_data([], 'cohort', $data);
});
}
/**
* Delete all use data which matches the specified context.
*
* @param context $context A user context.
*/
public static function delete_data_for_all_users_in_context(\context $context) {
if (!$context instanceof \context_system && !$context instanceof \context_coursecat) {
return;
}
static::delete_data($context);
}
/**
* Delete multiple users within a single context.
*
* @param approved_userlist $userlist The approved context and user information to delete information for.
*/
public static function delete_data_for_users(approved_userlist $userlist) {
$context = $userlist->get_context();
if ($context instanceof \context_system || $context instanceof \context_coursecat) {
foreach ($userlist->get_userids() as $userid) {
static::delete_data($context, $userid);
}
}
}
/**
* Delete all user data for the specified user, in the specified contexts.
*
* @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
*/
public static function delete_data_for_user(approved_contextlist $contextlist) {
if (empty($contextlist->count())) {
return;
}
$userid = $contextlist->get_user()->id;
foreach ($contextlist->get_contexts() as $context) {
if (!$context instanceof \context_system && !$context instanceof \context_coursecat) {
continue;
}
static::delete_data($context, $userid);
}
}
/**
* Delete data related to a context and user (if defined).
*
* @param context $context A context.
* @param int $userid The user ID.
*/
protected static function delete_data(\context $context, int $userid = null) {
global $DB;
$cohortids = $DB->get_fieldset_select('cohort', 'id', 'contextid = :contextid', ['contextid' => $context->id]);
foreach ($cohortids as $cohortid) {
$params = ['cohortid' => $cohortid];
if (!empty($userid)) {
$params['userid'] = $userid;
}
$DB->delete_records('cohort_members', $params);
}
}
}
@@ -0,0 +1,174 @@
<?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/>.
declare(strict_types=1);
namespace core_cohort\reportbuilder\audience;
use context;
use context_system;
use core_course_category;
use stdClass;
use core_reportbuilder\local\audiences\base;
use core_reportbuilder\local\helpers\database;
use MoodleQuickForm;
/**
* The backend class for Cohort member audience type
*
* @package core_reportbuilder
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cohortmember extends base {
/**
* Adds audience's elements to the given mform
*
* @param MoodleQuickForm $mform The form to add elements to
*/
public function get_config_form(MoodleQuickForm $mform): void {
$cohorts = self::get_cohorts();
$mform->addElement('autocomplete', 'cohorts', get_string('selectfromcohort', 'cohort'),
$cohorts, ['multiple' => true]);
$mform->addRule('cohorts', null, 'required', null, 'client');
}
/**
* Helps to build SQL to retrieve users that matches the current report audience
*
* @param string $usertablealias
* @return array array of three elements [$join, $where, $params]
*/
public function get_sql(string $usertablealias): array {
global $DB;
$cohorts = $this->get_configdata()['cohorts'];
[$insql, $inparams] = $DB->get_in_or_equal($cohorts, SQL_PARAMS_NAMED, database::generate_param_name('_'));
$cm = database::generate_alias();
$join = "JOIN {cohort_members} {$cm}
ON ({$cm}.userid = {$usertablealias}.id)";
return [$join, "{$cm}.cohortid " . $insql, $inparams];
}
/**
* Return user friendly name of this audience type
*
* @return string
*/
public function get_name(): string {
return get_string('memberofcohort', 'cohort');
}
/**
* Return the description for the audience.
*
* @return string
*/
public function get_description(): string {
global $DB;
$cohortlist = [];
$cohortids = $this->get_configdata()['cohorts'];
$cohorts = $DB->get_records_list('cohort', 'id', $cohortids, 'name');
foreach ($cohorts as $cohort) {
$cohortlist[] = format_string($cohort->name, true, ['context' => $cohort->contextid, 'escape' => false]);
}
return $this->format_description_for_multiselect($cohortlist);
}
/**
* If the current user is able to add this audience.
*
* @return bool
*/
public function user_can_add(): bool {
// Check system context first.
if (has_capability('moodle/cohort:view', context_system::instance())) {
return true;
}
// If there is at least one category with given permissions, user can add.
return !empty(core_course_category::make_categories_list('moodle/cohort:view'));
}
/**
* Returns if this audience type is available for the user
*
* Check if there are available cohorts in the system for this user to use.
*
* @return bool
*/
public function is_available(): bool {
return !empty(self::get_cohorts());
}
/**
* If the current user is able to edit this audience.
*
* @return bool
*/
public function user_can_edit(): bool {
global $DB;
$canedit = true;
$cohortids = $this->get_configdata()['cohorts'];
$cohorts = $DB->get_records_list('cohort', 'id', $cohortids);
foreach ($cohorts as $cohort) {
$context = context::instance_by_id($cohort->contextid, MUST_EXIST);
$canedit = $canedit && has_capability('moodle/cohort:view', $context);
if ($canedit === false) {
break;
}
}
return $canedit;
}
/**
* Cohorts selector.
*
* @return array
*/
private static function get_cohorts(): array {
global $CFG;
require_once($CFG->dirroot.'/cohort/lib.php');
$cohortslist = [];
// Search cohorts user can view.
$usercohorts = cohort_get_all_cohorts(0, 0);
// The previous method doesn't check cohorts on system context.
$syscontext = context_system::instance();
$cohorts = array_filter($usercohorts['cohorts'], static function(stdClass $cohort) use ($syscontext): bool {
return ($cohort->contextid != $syscontext->id) || has_capability('moodle/cohort:view', $syscontext);
});
foreach ($cohorts as $cohort) {
$cohortslist[$cohort->id] = format_string($cohort->name, true, [
'context' => $cohort->contextid,
'escape' => false,
]);
}
return $cohortslist;
}
}
@@ -0,0 +1,133 @@
<?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/>.
declare(strict_types=1);
namespace core_cohort\reportbuilder\datasource;
use core_cohort\reportbuilder\local\entities\{cohort, cohort_member};
use core_reportbuilder\local\filters\boolean_select;
use core_reportbuilder\datasource;
use core_reportbuilder\local\entities\user;
/**
* Cohorts datasource
*
* @package core_cohort
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cohorts extends datasource {
/**
* Return user friendly name of the datasource
*
* @return string
*/
public static function get_name(): string {
return get_string('cohorts', 'core_cohort');
}
/**
* Initialise report
*/
protected function initialise(): void {
$cohortentity = new cohort();
$cohorttablealias = $cohortentity->get_table_alias('cohort');
$this->set_main_table('cohort', $cohorttablealias);
$this->add_entity($cohortentity);
// Join the cohort member entity to the cohort entity.
$cohortmemberentity = new cohort_member();
$cohortmembertablealias = $cohortmemberentity->get_table_alias('cohort_members');
$this->add_entity($cohortmemberentity
->add_join("LEFT JOIN {cohort_members} {$cohortmembertablealias}
ON {$cohortmembertablealias}.cohortid = {$cohorttablealias}.id")
);
// Join the user entity to the cohort member entity.
$userentity = new user();
$usertablealias = $userentity->get_table_alias('user');
$this->add_entity($userentity
->add_joins($cohortmemberentity->get_joins())
->add_join("LEFT JOIN {user} {$usertablealias}
ON {$usertablealias}.id = {$cohortmembertablealias}.userid")
);
// Add all columns/filters/conditions from entities to be available in custom reports.
$this->add_all_from_entities();
}
/**
* Return the columns that will be added to the report as part of default setup
*
* @return string[]
*/
public function get_default_columns(): array {
return [
'cohort:name',
'cohort:context',
'cohort:idnumber',
'cohort:description',
];
}
/**
* Return the filters that will be added to the report once is created
*
* @return string[]
*/
public function get_default_filters(): array {
return ['cohort:context', 'cohort:name'];
}
/**
* Return the conditions that will be added to the report once is created
*
* @return string[]
*/
public function get_default_conditions(): array {
return [
'cohort:visible',
];
}
/**
* Return the condition values that will be set for the report upon creation
*
* @return array
*/
public function get_default_condition_values(): array {
return [
'cohort:visible_operator' => boolean_select::CHECKED,
];
}
/**
* Return the default sorting that will be added to the report once it is created
*
* @return array|int[]
*/
public function get_default_column_sorting(): array {
return [
'cohort:name' => SORT_ASC,
];
}
}
@@ -0,0 +1,396 @@
<?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/>.
declare(strict_types=1);
namespace core_cohort\reportbuilder\local\entities;
use context;
use context_helper;
use lang_string;
use stdClass;
use theme_config;
use core_reportbuilder\local\entities\base;
use core_reportbuilder\local\filters\boolean_select;
use core_reportbuilder\local\filters\cohort as cohort_filter;
use core_reportbuilder\local\filters\date;
use core_reportbuilder\local\filters\select;
use core_reportbuilder\local\filters\text;
use core_reportbuilder\local\helpers\custom_fields;
use core_reportbuilder\local\helpers\format;
use core_reportbuilder\local\report\column;
use core_reportbuilder\local\report\filter;
/**
* Cohort entity
*
* @package core_cohort
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cohort extends base {
/**
* Database tables that this entity uses
*
* @return string[]
*/
protected function get_default_tables(): array {
return [
'cohort',
'context',
];
}
/**
* The default title for this entity
*
* @return lang_string
*/
protected function get_default_entity_title(): lang_string {
return new lang_string('cohort', 'core_cohort');
}
/**
* Initialise the entity
*
* @return base
*/
public function initialise(): base {
$tablealias = $this->get_table_alias('cohort');
$customfields = (new custom_fields(
"{$tablealias}.id",
$this->get_entity_name(),
'core_cohort',
'cohort',
))
->add_joins($this->get_joins());
$columns = array_merge($this->get_all_columns(), $customfields->get_columns());
foreach ($columns as $column) {
$this->add_column($column);
}
// All the filters defined by the entity can also be used as conditions.
$filters = array_merge($this->get_all_filters(), $customfields->get_filters());
foreach ($filters as $filter) {
$this
->add_filter($filter)
->add_condition($filter);
}
return $this;
}
/**
* Returns list of all available columns
*
* @return column[]
*/
protected function get_all_columns(): array {
global $DB;
$tablealias = $this->get_table_alias('cohort');
$contextalias = $this->get_table_alias('context');
// Category/context column.
$columns[] = (new column(
'context',
new lang_string('category'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->add_join($this->get_context_join())
->set_type(column::TYPE_TEXT)
->add_fields("{$tablealias}.contextid, " . context_helper::get_preload_record_columns_sql($contextalias))
->set_is_sortable(true)
->add_callback(static function($contextid, stdClass $cohort): string {
if ($contextid === null) {
return '';
}
context_helper::preload_from_record($cohort);
return context::instance_by_id($cohort->contextid)->get_context_name(false);
});
// Name column.
$columns[] = (new column(
'name',
new lang_string('name', 'core_cohort'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TEXT)
->add_fields("{$tablealias}.name")
->set_is_sortable(true);
// ID number column.
$columns[] = (new column(
'idnumber',
new lang_string('idnumber', 'core_cohort'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TEXT)
->add_fields("{$tablealias}.idnumber")
->set_is_sortable(true);
// Description column.
$descriptionfieldsql = "{$tablealias}.description";
if ($DB->get_dbfamily() === 'oracle') {
$descriptionfieldsql = $DB->sql_order_by_text($descriptionfieldsql, 1024);
}
$columns[] = (new column(
'description',
new lang_string('description'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->add_join($this->get_context_join())
->set_type(column::TYPE_LONGTEXT)
->add_field($descriptionfieldsql, 'description')
->add_fields("{$tablealias}.descriptionformat, {$tablealias}.id, {$tablealias}.contextid")
->add_fields(context_helper::get_preload_record_columns_sql($contextalias))
->add_callback(static function(?string $description, stdClass $cohort): string {
global $CFG;
require_once("{$CFG->libdir}/filelib.php");
if ($description === null) {
return '';
}
context_helper::preload_from_record($cohort);
$context = context::instance_by_id($cohort->contextid);
$description = file_rewrite_pluginfile_urls($description, 'pluginfile.php', $context->id, 'cohort',
'description', $cohort->id);
return format_text($description, $cohort->descriptionformat, ['context' => $context->id]);
});
// Visible column.
$columns[] = (new column(
'visible',
new lang_string('visible', 'core_cohort'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_BOOLEAN)
->add_fields("{$tablealias}.visible")
->set_is_sortable(true)
->set_callback([format::class, 'boolean_as_text']);
// Time created column.
$columns[] = (new column(
'timecreated',
new lang_string('timecreated', 'core_reportbuilder'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TIMESTAMP)
->add_fields("{$tablealias}.timecreated")
->set_is_sortable(true)
->set_callback([format::class, 'userdate']);
// Time modified column.
$columns[] = (new column(
'timemodified',
new lang_string('timemodified', 'core_reportbuilder'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TIMESTAMP)
->add_fields("{$tablealias}.timemodified")
->set_is_sortable(true)
->set_callback([format::class, 'userdate']);
// Component column.
$columns[] = (new column(
'component',
new lang_string('component', 'core_cohort'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TEXT)
->add_fields("{$tablealias}.component")
->set_is_sortable(true)
->add_callback(static function(?string $component): string {
if ($component === null) {
return '';
}
return $component === ''
? get_string('nocomponent', 'cohort')
: get_string('pluginname', $component);
});
// Theme column.
$columns[] = (new column(
'theme',
new lang_string('theme'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TEXT)
->add_fields("{$tablealias}.theme")
->set_is_sortable(true)
->add_callback(static function (?string $theme): string {
if ((string) $theme === '') {
return '';
}
return get_string('pluginname', "theme_{$theme}");
});
return $columns;
}
/**
* Return list of all available filters
*
* @return filter[]
*/
protected function get_all_filters(): array {
global $DB;
$tablealias = $this->get_table_alias('cohort');
// Cohort select filter.
$filters[] = (new filter(
cohort_filter::class,
'cohortselect',
new lang_string('selectcohort', 'core_cohort'),
$this->get_entity_name(),
"{$tablealias}.id"
))
->add_joins($this->get_joins());
// Context filter.
$filters[] = (new filter(
select::class,
'context',
new lang_string('category'),
$this->get_entity_name(),
"{$tablealias}.contextid"
))
->add_joins($this->get_joins())
->set_options_callback(static function(): array {
global $DB;
// Load all contexts in which there are cohorts.
$ctxfields = context_helper::get_preload_record_columns_sql('ctx');
$contexts = $DB->get_records_sql("
SELECT DISTINCT {$ctxfields}, c.contextid
FROM {context} ctx
JOIN {cohort} c ON c.contextid = ctx.id");
// Transform context record into it's name (used as the filter options).
return array_map(static function(stdClass $contextrecord): string {
context_helper::preload_from_record($contextrecord);
return context::instance_by_id($contextrecord->contextid)
->get_context_name(false);
}, $contexts);
});
// Name filter.
$filters[] = (new filter(
text::class,
'name',
new lang_string('name', 'core_cohort'),
$this->get_entity_name(),
"{$tablealias}.name"
))
->add_joins($this->get_joins());
// ID number filter.
$filters[] = (new filter(
text::class,
'idnumber',
new lang_string('idnumber', 'core_cohort'),
$this->get_entity_name(),
"{$tablealias}.idnumber"
))
->add_joins($this->get_joins());
// Time created filter.
$filters[] = (new filter(
date::class,
'timecreated',
new lang_string('timecreated', 'core_reportbuilder'),
$this->get_entity_name(),
"{$tablealias}.timecreated"
))
->add_joins($this->get_joins());
// Description filter.
$filters[] = (new filter(
text::class,
'description',
new lang_string('description'),
$this->get_entity_name(),
$DB->sql_cast_to_char("{$tablealias}.description")
))
->add_joins($this->get_joins());
// Theme filter.
$filters[] = (new filter(
select::class,
'theme',
new lang_string('theme'),
$this->get_entity_name(),
"{$tablealias}.theme",
))
->set_options_callback(static function(): array {
return array_map(
fn(theme_config $theme) => $theme->get_theme_name(),
get_list_of_themes(),
);
})
->add_joins($this->get_joins());
// Visible filter.
$filters[] = (new filter(
boolean_select::class,
'visible',
new lang_string('visible', 'core_cohort'),
$this->get_entity_name(),
"{$tablealias}.visible"
))
->add_joins($this->get_joins());
return $filters;
}
/**
* Return context join used by columns
*
* @return string
*/
private function get_context_join(): string {
// If the context table is already joined, we don't need to do that again.
if ($this->has_table_join_alias('context')) {
return '';
}
$tablealias = $this->get_table_alias('cohort');
$contextalias = $this->get_table_alias('context');
return "LEFT JOIN {context} {$contextalias} ON {$contextalias}.id = {$tablealias}.contextid";
}
}
@@ -0,0 +1,122 @@
<?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/>.
declare(strict_types=1);
namespace core_cohort\reportbuilder\local\entities;
use lang_string;
use core_reportbuilder\local\entities\base;
use core_reportbuilder\local\filters\date;
use core_reportbuilder\local\helpers\format;
use core_reportbuilder\local\report\column;
use core_reportbuilder\local\report\filter;
/**
* Cohort member entity
*
* @package core_cohort
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cohort_member extends base {
/**
* Database tables that this entity uses
*
* @return string[]
*/
protected function get_default_tables(): array {
return [
'cohort_members',
];
}
/**
* The default title for this entity
*
* @return lang_string
*/
protected function get_default_entity_title(): lang_string {
return new lang_string('cohortmember', 'core_cohort');
}
/**
* Initialise the entity
*
* @return base
*/
public function initialise(): base {
$columns = $this->get_all_columns();
foreach ($columns as $column) {
$this->add_column($column);
}
// All the filters defined by the entity can also be used as conditions.
$filters = $this->get_all_filters();
foreach ($filters as $filter) {
$this
->add_filter($filter)
->add_condition($filter);
}
return $this;
}
/**
* Returns list of all available columns
*
* @return column[]
*/
protected function get_all_columns(): array {
$tablealias = $this->get_table_alias('cohort_members');
// Time added column.
$columns[] = (new column(
'timeadded',
new lang_string('timeadded', 'core_reportbuilder'),
$this->get_entity_name()
))
->add_joins($this->get_joins())
->set_type(column::TYPE_TIMESTAMP)
->add_fields("{$tablealias}.timeadded")
->set_is_sortable(true)
->set_callback([format::class, 'userdate']);
return $columns;
}
/**
* Return list of all available filters
*
* @return filter[]
*/
protected function get_all_filters(): array {
$tablealias = $this->get_table_alias('cohort_members');
// Time added filter.
$filters[] = (new filter(
date::class,
'timeadded',
new lang_string('timeadded', 'core_reportbuilder'),
$this->get_entity_name(),
"{$tablealias}.timeadded"
))
->add_joins($this->get_joins());
return $filters;
}
}
@@ -0,0 +1,270 @@
<?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/>.
namespace core_cohort\reportbuilder\local\systemreports;
use context;
use context_coursecat;
use context_system;
use core_cohort\reportbuilder\local\entities\cohort;
use core_reportbuilder\local\helpers\database;
use core_reportbuilder\local\report\action;
use core_reportbuilder\local\report\column;
use html_writer;
use lang_string;
use moodle_url;
use pix_icon;
use core_reportbuilder\system_report;
use stdClass;
/**
* Cohorts system report class implementation
*
* @package core_cohort
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cohorts extends system_report {
/**
* Initialise report, we need to set the main table, load our entities and set columns/filters
*/
protected function initialise(): void {
// Our main entity, it contains all of the column definitions that we need.
$cohortentity = new cohort();
$entitymainalias = $cohortentity->get_table_alias('cohort');
$this->set_main_table('cohort', $entitymainalias);
$this->add_entity($cohortentity);
// Any columns required by actions should be defined here to ensure they're always available.
$this->add_base_fields("{$entitymainalias}.id, {$entitymainalias}.contextid, {$entitymainalias}.visible, " .
"{$entitymainalias}.component");
// Check if report needs to show a specific category.
$contextid = $this->get_parameter('contextid', 0, PARAM_INT);
$showall = $this->get_parameter('showall', true, PARAM_BOOL);
if (!$showall) {
$paramcontextid = database::generate_param_name();
$this->add_base_condition_sql("{$entitymainalias}.contextid = :$paramcontextid", [$paramcontextid => $contextid]);
}
// Now we can call our helper methods to add the content we want to include in the report.
$this->add_columns($cohortentity);
$this->add_filters();
$this->add_actions();
// Set if report can be downloaded.
$this->set_downloadable(false);
}
/**
* Validates access to view this report
*
* @return bool
*/
protected function can_view(): bool {
$contextid = $this->get_parameter('contextid', 0, PARAM_INT);
if ($contextid) {
$context = context::instance_by_id($contextid, MUST_EXIST);
} else {
$context = context_system::instance();
}
return has_any_capability(['moodle/cohort:manage', 'moodle/cohort:view'], $context);
}
/**
* Adds the columns we want to display in the report
*
* They are provided by the entities we previously added in the {@see initialise} method, referencing each by their
* unique identifier. If custom columns are needed just for this report, they can be defined here.
*
* @param cohort $cohortentity
*/
public function add_columns(cohort $cohortentity): void {
$entitymainalias = $cohortentity->get_table_alias('cohort');
$showall = $this->get_parameter('showall', false, PARAM_BOOL);
// Category column. An extra callback is appended in order to extend the current column formatting.
if ($showall) {
$this->add_column_from_entity('cohort:context')
->add_callback(static function(string $value, stdClass $cohort): string {
$context = context::instance_by_id($cohort->contextid);
if ($context instanceof context_coursecat) {
return html_writer::link(new moodle_url('/cohort/index.php',
['contextid' => $cohort->contextid]), $value);
}
return $value;
});
}
// Name column using the inplace editable component.
$this->add_column(new column(
'editablename',
new lang_string('name', 'core_cohort'),
$cohortentity->get_entity_name()
))
->set_type(column::TYPE_TEXT)
->set_is_sortable(true)
->add_fields("{$entitymainalias}.name, {$entitymainalias}.id, {$entitymainalias}.contextid")
->add_callback(static function(string $name, stdClass $cohort): string {
global $OUTPUT, $PAGE;
$renderer = $PAGE->get_renderer('core');
$template = new \core_cohort\output\cohortname($cohort);
return $renderer->render_from_template('core/inplace_editable', $template->export_for_template($OUTPUT));
});
// ID Number column using the inplace editable component.
$this->add_column(new column(
'editableidnumber',
new lang_string('idnumber', 'core_cohort'),
$cohortentity->get_entity_name()
))
->set_type(column::TYPE_TEXT)
->set_is_sortable(true)
->add_fields("{$entitymainalias}.idnumber, {$entitymainalias}.id, {$entitymainalias}.contextid")
->add_callback(static function(?string $idnumber, stdClass $cohort): string {
global $OUTPUT, $PAGE;
$renderer = $PAGE->get_renderer('core');
$template = new \core_cohort\output\cohortidnumber($cohort);
return $renderer->render_from_template('core/inplace_editable', $template->export_for_template($OUTPUT));
});
// Description column.
$this->add_column_from_entity('cohort:description');
// Cohort size column using a custom SQL query to count cohort members.
$cm = database::generate_param_name();
$sql = "(SELECT count($cm.id) as memberscount
FROM {cohort_members} $cm
WHERE $cm.cohortid = {$entitymainalias}.id)";
$this->add_column(new column(
'memberscount',
new lang_string('memberscount', 'cohort'),
$cohortentity->get_entity_name()
))
->set_type(column::TYPE_INTEGER)
->set_is_sortable(true)
->add_field($sql, 'memberscount');
// Component column. Override the display name of a column.
$this->add_column_from_entity('cohort:component')
->set_title(new lang_string('source', 'core_plugin'));
// It's possible to set a default initial sort direction for one column.
$this->set_initial_sort_column('cohort:editablename', SORT_ASC);
}
/**
* Adds the filters we want to display in the report
*
* They are all provided by the entities we previously added in the {@see initialise} method, referencing each by their
* unique identifier
*/
protected function add_filters(): void {
$filters = [
'cohort:name',
'cohort:idnumber',
'cohort:description',
];
$this->add_filters_from_entities($filters);
}
/**
* Add the system report actions. An extra column will be appended to each row, containing all actions added here
*
* Note the use of ":id" placeholder which will be substituted according to actual values in the row
*/
protected function add_actions(): void {
$contextid = $this->get_parameter('contextid', 0, PARAM_INT);
$showall = $this->get_parameter('showall', true, PARAM_BOOL);
$returnurl = (new moodle_url('/cohort/index.php',
['id' => ':id', 'contextid' => $contextid, 'showall' => $showall]))->out(false);
// Hide action. It will be only shown if the property 'visible' is true and user has 'moodle/cohort:manage' capabillity.
$this->add_action((new action(
new moodle_url('/cohort/edit.php', ['id' => ':id', 'sesskey' => sesskey(), 'hide' => 1, 'returnurl' => $returnurl]),
new pix_icon('t/show', '', 'core'),
[],
false,
new lang_string('hide')
))->add_callback(function(stdClass $row): bool {
return empty($row->component) && $row->visible
&& has_capability('moodle/cohort:manage', context::instance_by_id($row->contextid));
}));
// Show action. It will be only shown if the property 'visible' is false and user has 'moodle/cohort:manage' capabillity.
$this->add_action((new action(
new moodle_url('/cohort/edit.php', ['id' => ':id', 'sesskey' => sesskey(), 'show' => 1, 'returnurl' => $returnurl]),
new pix_icon('t/hide', '', 'core'),
[],
false,
new lang_string('show')
))->add_callback(function(stdClass $row): bool {
return empty($row->component) && !$row->visible
&& has_capability('moodle/cohort:manage', context::instance_by_id($row->contextid));
}));
// Edit action. It will be only shown if user has 'moodle/cohort:manage' capabillity.
$this->add_action((new action(
new moodle_url('/cohort/edit.php', ['id' => ':id', 'returnurl' => $returnurl]),
new pix_icon('t/edit', '', 'core'),
[],
false,
new lang_string('edit')
))->add_callback(function(stdClass $row): bool {
return empty($row->component) && has_capability('moodle/cohort:manage', context::instance_by_id($row->contextid));
}));
// Delete action. It will be only shown if user has 'moodle/cohort:manage' capabillity.
$this->add_action((new action(
new moodle_url('/cohort/edit.php', ['id' => ':id', 'delete' => 1, 'returnurl' => $returnurl]),
new pix_icon('t/delete', '', 'core'),
['class' => 'text-danger'],
false,
new lang_string('delete')
))->add_callback(function(stdClass $row): bool {
return empty($row->component) && has_capability('moodle/cohort:manage', context::instance_by_id($row->contextid));
}));
// Assign members to cohort action. It will be only shown if user has 'moodle/cohort:assign' capabillity.
$this->add_action((new action(
new moodle_url('/cohort/assign.php', ['id' => ':id', 'returnurl' => $returnurl]),
new pix_icon('i/users', '', 'core'),
[],
false,
new lang_string('assign', 'core_cohort')
))->add_callback(function(stdClass $row): bool {
return empty($row->component) && has_capability('moodle/cohort:assign', context::instance_by_id($row->contextid));
}));
}
/**
* CSS class for the row
*
* @param stdClass $row
* @return string
*/
public function get_row_class(stdClass $row): string {
return (!$row->visible) ? 'text-muted' : '';
}
}
+40
View File
@@ -0,0 +1,40 @@
<?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/>.
/**
* Manage cohort custom fields
*
* @package core_cohort
* @copyright 2023 Dmitrii Metelkin <dmitriim@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use core_cohort\customfield\cohort_handler;
use core_customfield\output\management;
require_once('../config.php');
require_once($CFG->libdir . '/adminlib.php');
admin_externalpage_setup('cohort_customfield');
$output = $PAGE->get_renderer('core_customfield');
$handler = cohort_handler::create();
$outputpage = new management($handler);
echo $output->header(),
$output->heading(new lang_string('cohort_customfield', 'admin')),
$output->render($outputpage),
$output->footer();
+192
View File
@@ -0,0 +1,192 @@
<?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/>.
/**
* Cohort related management functions, this file needs to be included manually.
*
* @package core_cohort
* @copyright 2010 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require('../config.php');
require_once($CFG->dirroot.'/course/lib.php');
require_once($CFG->dirroot.'/cohort/lib.php');
require_once($CFG->dirroot.'/cohort/edit_form.php');
$id = optional_param('id', 0, PARAM_INT);
$contextid = optional_param('contextid', 0, PARAM_INT);
$delete = optional_param('delete', 0, PARAM_BOOL);
$show = optional_param('show', 0, PARAM_BOOL);
$hide = optional_param('hide', 0, PARAM_BOOL);
$confirm = optional_param('confirm', 0, PARAM_BOOL);
$returnurl = optional_param('returnurl', '', PARAM_LOCALURL);
require_login();
$category = null;
if ($id) {
$cohort = $DB->get_record('cohort', array('id'=>$id), '*', MUST_EXIST);
$context = context::instance_by_id($cohort->contextid, MUST_EXIST);
} else {
$context = context::instance_by_id($contextid, MUST_EXIST);
if ($context->contextlevel != CONTEXT_COURSECAT and $context->contextlevel != CONTEXT_SYSTEM) {
throw new \moodle_exception('invalidcontext');
}
$cohort = new stdClass();
$cohort->id = 0;
$cohort->contextid = $context->id;
$cohort->name = '';
$cohort->description = '';
}
require_capability('moodle/cohort:manage', $context);
if ($returnurl) {
$returnurl = new moodle_url($returnurl);
} else {
$returnurl = new moodle_url('/cohort/index.php', array('contextid'=>$context->id));
}
if (!empty($cohort->component)) {
// We can not manually edit cohorts that were created by external systems, sorry.
redirect($returnurl);
}
$PAGE->set_context($context);
$baseurl = new moodle_url('/cohort/edit.php', array('contextid' => $context->id, 'id' => $cohort->id));
$PAGE->set_url($baseurl);
$PAGE->set_context($context);
$PAGE->set_pagelayout('admin');
if ($context->contextlevel == CONTEXT_COURSECAT) {
core_course_category::page_setup();
// Set the cohorts node active in the settings navigation block.
if ($cohortsnode = $PAGE->settingsnav->find('cohort', navigation_node::TYPE_SETTING)) {
$cohortsnode->make_active();
}
$PAGE->set_secondary_active_tab('cohort');
} else {
navigation_node::override_active_url(new moodle_url('/cohort/index.php', array()));
$PAGE->set_heading($COURSE->fullname);
}
if ($delete and $cohort->id) {
$PAGE->url->param('delete', 1);
if ($confirm and confirm_sesskey()) {
cohort_delete_cohort($cohort);
redirect($returnurl);
}
$strheading = get_string('delcohort', 'cohort');
$PAGE->navbar->add($strheading);
$PAGE->set_title($strheading);
echo $OUTPUT->header();
echo $OUTPUT->heading($strheading);
$yesurl = new moodle_url('/cohort/edit.php', array('id' => $cohort->id, 'delete' => 1,
'confirm' => 1, 'sesskey' => sesskey(), 'returnurl' => $returnurl->out_as_local_url()));
$message = get_string('delconfirm', 'cohort', format_string($cohort->name));
echo $OUTPUT->confirm($message, $yesurl, $returnurl);
echo $OUTPUT->footer();
die;
}
if ($show && $cohort->id && confirm_sesskey()) {
if (!$cohort->visible) {
$record = (object)array('id' => $cohort->id, 'visible' => 1, 'contextid' => $cohort->contextid);
cohort_update_cohort($record);
}
redirect($returnurl);
}
if ($hide && $cohort->id && confirm_sesskey()) {
if ($cohort->visible) {
$record = (object)array('id' => $cohort->id, 'visible' => 0, 'contextid' => $cohort->contextid);
cohort_update_cohort($record);
}
redirect($returnurl);
}
$editoroptions = array('maxfiles' => EDITOR_UNLIMITED_FILES,
'maxbytes' => $SITE->maxbytes, 'context' => $context);
if ($cohort->id) {
// Edit existing.
$cohort = file_prepare_standard_editor($cohort, 'description', $editoroptions,
$context, 'cohort', 'description', $cohort->id);
$strheading = get_string('editcohort', 'cohort');
} else {
// Add new.
$cohort = file_prepare_standard_editor($cohort, 'description', $editoroptions,
$context, 'cohort', 'description', null);
$strheading = get_string('addcohort', 'cohort');
}
$PAGE->set_title($strheading);
$PAGE->navbar->add($strheading);
$editform = new cohort_edit_form(null, array('editoroptions'=>$editoroptions, 'data'=>$cohort, 'returnurl'=>$returnurl));
if ($editform->is_cancelled()) {
redirect($returnurl);
} else if ($data = $editform->get_data()) {
$oldcontextid = $context->id;
$editoroptions['context'] = $context = context::instance_by_id($data->contextid);
if ($data->id) {
if ($data->contextid != $oldcontextid) {
// Cohort was moved to another context.
get_file_storage()->move_area_files_to_new_context($oldcontextid, $context->id,
'cohort', 'description', $data->id);
}
$data = file_postupdate_standard_editor($data, 'description', $editoroptions,
$context, 'cohort', 'description', $data->id);
cohort_update_cohort($data);
} else {
$data->descriptionformat = $data->description_editor['format'];
$data->description = $description = $data->description_editor['text'];
$data->id = cohort_add_cohort($data);
$editoroptions['context'] = $context = context::instance_by_id($data->contextid);
$data = file_postupdate_standard_editor($data, 'description', $editoroptions,
$context, 'cohort', 'description', $data->id);
if ($description != $data->description) {
$updatedata = (object)array('id' => $data->id,
'description' => $data->description, 'contextid' => $context->id);
cohort_update_cohort($updatedata);
}
}
if ($returnurl->get_param('showall') || $returnurl->get_param('contextid') == $data->contextid) {
// Redirect to where we were before.
redirect($returnurl);
} else {
// Use new context id, it has been changed.
redirect(new moodle_url('/cohort/index.php', array('contextid' => $data->contextid)));
}
}
echo $OUTPUT->header();
echo $OUTPUT->heading($strheading);
if (!$id && ($editcontrols = cohort_edit_controls($context, $baseurl))) {
echo $OUTPUT->render($editcontrols);
}
echo $editform->display();
echo $OUTPUT->footer();
+138
View File
@@ -0,0 +1,138 @@
<?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/>.
/**
* Cohort related management functions, this file needs to be included manually.
*
* @package core_cohort
* @copyright 2010 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/lib/formslib.php');
class cohort_edit_form extends moodleform {
/**
* Define the cohort edit form
*/
public function definition() {
global $CFG;
$mform = $this->_form;
$editoroptions = $this->_customdata['editoroptions'];
$cohort = $this->_customdata['data'];
$mform->addElement('text', 'name', get_string('name', 'cohort'), 'maxlength="254" size="50"');
$mform->addRule('name', get_string('required'), 'required', null, 'client');
$mform->setType('name', PARAM_TEXT);
$options = $this->get_category_options($cohort->contextid);
$mform->addElement('autocomplete', 'contextid', get_string('context', 'role'), $options);
$mform->addRule('contextid', null, 'required', null, 'client');
$mform->addElement('text', 'idnumber', get_string('idnumber', 'cohort'), 'maxlength="254" size="50"');
$mform->setType('idnumber', PARAM_RAW); // Idnumbers are plain text, must not be changed.
$mform->addElement('advcheckbox', 'visible', get_string('visible', 'cohort'));
$mform->setDefault('visible', 1);
$mform->addHelpButton('visible', 'visible', 'cohort');
$mform->addElement('editor', 'description_editor', get_string('description', 'cohort'), null, $editoroptions);
$mform->setType('description_editor', PARAM_RAW);
if (!empty($CFG->allowcohortthemes)) {
$themes = array_merge(array('' => get_string('forceno')), cohort_get_list_of_themes());
$mform->addElement('select', 'theme', get_string('forcetheme'), $themes);
}
$mform->addElement('hidden', 'id');
$mform->setType('id', PARAM_INT);
if (isset($this->_customdata['returnurl'])) {
$mform->addElement('hidden', 'returnurl', $this->_customdata['returnurl']->out_as_local_url());
$mform->setType('returnurl', PARAM_LOCALURL);
}
$handler = core_cohort\customfield\cohort_handler::create();
$handler->instance_form_definition($mform, empty($cohort->id) ? 0 : $cohort->id);
$this->add_action_buttons();
$handler->instance_form_before_set_data($cohort);
$this->set_data($cohort);
}
public function validation($data, $files) {
global $DB;
$errors = parent::validation($data, $files);
$idnumber = trim($data['idnumber']);
if ($idnumber === '') {
// Fine, empty is ok.
} else if ($data['id']) {
$current = $DB->get_record('cohort', array('id'=>$data['id']), '*', MUST_EXIST);
if ($current->idnumber !== $idnumber) {
if ($DB->record_exists('cohort', array('idnumber'=>$idnumber))) {
$errors['idnumber'] = get_string('duplicateidnumber', 'cohort');
}
}
} else {
if ($DB->record_exists('cohort', array('idnumber'=>$idnumber))) {
$errors['idnumber'] = get_string('duplicateidnumber', 'cohort');
}
}
$handler = core_cohort\customfield\cohort_handler::create();
$errors = array_merge($errors, $handler->instance_form_validation($data, $files));
return $errors;
}
protected function get_category_options($currentcontextid) {
$displaylist = core_course_category::make_categories_list('moodle/cohort:manage');
$options = array();
$syscontext = context_system::instance();
if (has_capability('moodle/cohort:manage', $syscontext)) {
$options[$syscontext->id] = $syscontext->get_context_name();
}
foreach ($displaylist as $cid=>$name) {
$context = context_coursecat::instance($cid);
$options[$context->id] = $name;
}
// Always add current - this is not likely, but if the logic gets changed it might be a problem.
if (!isset($options[$currentcontextid])) {
$context = context::instance_by_id($currentcontextid, MUST_EXIST);
$options[$context->id] = $syscontext->get_context_name();
}
return $options;
}
/**
* Apply a logic after data is set.
*/
public function definition_after_data() {
$cohortid = $this->_form->getElementValue('id');
$handler = core_cohort\customfield\cohort_handler::create();
$handler->instance_form_definition_after_data($this->_form, empty($cohortid) ? 0 : $cohortid);
}
}
+941
View File
@@ -0,0 +1,941 @@
<?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/>.
use core_external\external_api;
use core_external\external_format_value;
use core_external\external_function_parameters;
use core_external\external_multiple_structure;
use core_external\external_single_structure;
use core_external\external_value;
use core_external\external_warnings;
use core_external\util;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/cohort/lib.php');
/**
* External cohort API
*
* @package core_cohort
* @category external
* @copyright MediaTouch 2000 srl
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class core_cohort_external extends external_api {
/**
* Returns description of method parameters
*
* @return external_function_parameters
* @since Moodle 2.5
*/
public static function create_cohorts_parameters() {
return new external_function_parameters(
array(
'cohorts' => new external_multiple_structure(
new external_single_structure(
array(
'categorytype' => new external_single_structure(
array(
'type' => new external_value(PARAM_TEXT, 'the name of the field: id (numeric value
of course category id) or idnumber (alphanumeric value of idnumber course category)
or system (value ignored)'),
'value' => new external_value(PARAM_RAW, 'the value of the categorytype')
)
),
'name' => new external_value(PARAM_RAW, 'cohort name'),
'idnumber' => new external_value(PARAM_RAW, 'cohort idnumber'),
'description' => new external_value(PARAM_RAW, 'cohort description', VALUE_OPTIONAL),
'descriptionformat' => new external_format_value('description', VALUE_DEFAULT),
'visible' => new external_value(PARAM_BOOL, 'cohort visible', VALUE_OPTIONAL, true),
'theme' => new external_value(PARAM_THEME,
'the cohort theme. The allowcohortthemes setting must be enabled on Moodle',
VALUE_OPTIONAL
),
'customfields' => self::build_custom_fields_parameters_structure(),
)
)
)
)
);
}
/**
* Create one or more cohorts
*
* @param array $cohorts An array of cohorts to create.
* @return array An array of arrays
* @since Moodle 2.5
*/
public static function create_cohorts($cohorts) {
global $CFG, $DB;
require_once("$CFG->dirroot/cohort/lib.php");
$params = self::validate_parameters(self::create_cohorts_parameters(), array('cohorts' => $cohorts));
$availablethemes = cohort_get_list_of_themes();
$transaction = $DB->start_delegated_transaction();
$syscontext = context_system::instance();
$cohortids = array();
foreach ($params['cohorts'] as $cohort) {
$cohort = (object)$cohort;
// Category type (context id).
$categorytype = $cohort->categorytype;
if (!in_array($categorytype['type'], array('idnumber', 'id', 'system'))) {
throw new invalid_parameter_exception('category type must be id, idnumber or system:' . $categorytype['type']);
}
if ($categorytype['type'] === 'system') {
$cohort->contextid = $syscontext->id;
} else if ($catid = $DB->get_field('course_categories', 'id', array($categorytype['type'] => $categorytype['value']))) {
$catcontext = context_coursecat::instance($catid);
$cohort->contextid = $catcontext->id;
} else {
throw new invalid_parameter_exception('category not exists: category '
.$categorytype['type'].' = '.$categorytype['value']);
}
// Make sure that the idnumber doesn't already exist.
if ($DB->record_exists('cohort', array('idnumber' => $cohort->idnumber))) {
throw new invalid_parameter_exception('record already exists: idnumber='.$cohort->idnumber);
}
$context = context::instance_by_id($cohort->contextid, MUST_EXIST);
if ($context->contextlevel != CONTEXT_COURSECAT and $context->contextlevel != CONTEXT_SYSTEM) {
throw new invalid_parameter_exception('Invalid context');
}
self::validate_context($context);
require_capability('moodle/cohort:manage', $context);
// Make sure theme is valid.
if (isset($cohort->theme)) {
if (!empty($CFG->allowcohortthemes)) {
if (empty($availablethemes[$cohort->theme])) {
throw new moodle_exception('errorinvalidparam', 'webservice', '', 'theme');
}
}
}
// Validate format.
$cohort->descriptionformat = util::validate_format($cohort->descriptionformat);
// Custom fields.
if (!empty($cohort->customfields)) {
foreach ($cohort->customfields as $field) {
$fieldname = self::build_custom_field_name($field['shortname']);
$cohort->{$fieldname} = $field['value'];
}
unset($cohort->customfields);
}
$cohort->id = cohort_add_cohort($cohort);
list($cohort->description, $cohort->descriptionformat) =
\core_external\util::format_text($cohort->description, $cohort->descriptionformat,
$context, 'cohort', 'description', $cohort->id);
$cohortids[] = (array)$cohort;
}
$transaction->allow_commit();
return $cohortids;
}
/**
* Returns description of method result value
*
* @return \core_external\external_description
* @since Moodle 2.5
*/
public static function create_cohorts_returns() {
return new external_multiple_structure(
new external_single_structure(
array(
'id' => new external_value(PARAM_INT, 'cohort id'),
'name' => new external_value(PARAM_RAW, 'cohort name'),
'idnumber' => new external_value(PARAM_RAW, 'cohort idnumber'),
'description' => new external_value(PARAM_RAW, 'cohort description'),
'descriptionformat' => new external_format_value('description'),
'visible' => new external_value(PARAM_BOOL, 'cohort visible'),
'theme' => new external_value(PARAM_THEME, 'cohort theme', VALUE_OPTIONAL),
)
)
);
}
/**
* Returns description of method parameters
*
* @return external_function_parameters
* @since Moodle 2.5
*/
public static function delete_cohorts_parameters() {
return new external_function_parameters(
array(
'cohortids' => new external_multiple_structure(new external_value(PARAM_INT, 'cohort ID')),
)
);
}
/**
* Delete cohorts
*
* @param array $cohortids
* @return null
* @since Moodle 2.5
*/
public static function delete_cohorts($cohortids) {
global $CFG, $DB;
require_once("$CFG->dirroot/cohort/lib.php");
$params = self::validate_parameters(self::delete_cohorts_parameters(), array('cohortids' => $cohortids));
$transaction = $DB->start_delegated_transaction();
foreach ($params['cohortids'] as $cohortid) {
// Validate params.
$cohortid = validate_param($cohortid, PARAM_INT);
$cohort = $DB->get_record('cohort', array('id' => $cohortid), '*', MUST_EXIST);
// Now security checks.
$context = context::instance_by_id($cohort->contextid, MUST_EXIST);
if ($context->contextlevel != CONTEXT_COURSECAT and $context->contextlevel != CONTEXT_SYSTEM) {
throw new invalid_parameter_exception('Invalid context');
}
self::validate_context($context);
require_capability('moodle/cohort:manage', $context);
cohort_delete_cohort($cohort);
}
$transaction->allow_commit();
return null;
}
/**
* Returns description of method result value
*
* @return null
* @since Moodle 2.5
*/
public static function delete_cohorts_returns() {
return null;
}
/**
* Returns description of method parameters
*
* @return external_function_parameters
* @since Moodle 2.5
*/
public static function get_cohorts_parameters() {
return new external_function_parameters(
array(
'cohortids' => new external_multiple_structure(new external_value(PARAM_INT, 'Cohort ID')
, 'List of cohort id. A cohort id is an integer.', VALUE_DEFAULT, array()),
)
);
}
/**
* Get cohorts definition specified by ids
*
* @param array $cohortids array of cohort ids
* @return array of cohort objects (id, courseid, name)
* @since Moodle 2.5
*/
public static function get_cohorts($cohortids = array()) {
global $DB, $CFG;
$params = self::validate_parameters(self::get_cohorts_parameters(), array('cohortids' => $cohortids));
if (empty($cohortids)) {
$cohorts = $DB->get_records('cohort');
if (!empty($cohorts)) {
$cohortids = array_keys($cohorts);
}
} else {
$cohorts = $DB->get_records_list('cohort', 'id', $params['cohortids']);
}
$customfieldsdata = self::get_custom_fields_data($cohortids);
$cohortsinfo = array();
foreach ($cohorts as $cohort) {
// Now security checks.
$context = context::instance_by_id($cohort->contextid, MUST_EXIST);
if ($context->contextlevel != CONTEXT_COURSECAT and $context->contextlevel != CONTEXT_SYSTEM) {
throw new invalid_parameter_exception('Invalid context');
}
self::validate_context($context);
if (!has_any_capability(array('moodle/cohort:manage', 'moodle/cohort:view'), $context)) {
throw new required_capability_exception($context, 'moodle/cohort:view', 'nopermissions', '');
}
// Only return theme when $CFG->allowcohortthemes is enabled.
if (!empty($cohort->theme) && empty($CFG->allowcohortthemes)) {
$cohort->theme = null;
}
list($cohort->description, $cohort->descriptionformat) =
\core_external\util::format_text($cohort->description, $cohort->descriptionformat,
$context, 'cohort', 'description', $cohort->id);
$cohort->customfields = !empty($customfieldsdata[$cohort->id]) ? $customfieldsdata[$cohort->id] : [];
$cohortsinfo[] = (array) $cohort;
}
return $cohortsinfo;
}
/**
* Returns description of method result value
*
* @return \core_external\external_description
* @since Moodle 2.5
*/
public static function get_cohorts_returns() {
return new external_multiple_structure(
new external_single_structure(
array(
'id' => new external_value(PARAM_INT, 'ID of the cohort'),
'name' => new external_value(PARAM_RAW, 'cohort name'),
'idnumber' => new external_value(PARAM_RAW, 'cohort idnumber'),
'description' => new external_value(PARAM_RAW, 'cohort description'),
'descriptionformat' => new external_format_value('description'),
'visible' => new external_value(PARAM_BOOL, 'cohort visible'),
'theme' => new external_value(PARAM_THEME, 'cohort theme', VALUE_OPTIONAL),
'customfields' => self::build_custom_fields_returns_structure(),
)
)
);
}
/**
* Returns the description of external function parameters.
*
* @return external_function_parameters
*/
public static function search_cohorts_parameters() {
$query = new external_value(
PARAM_RAW,
'Query string'
);
$includes = new external_value(
PARAM_ALPHA,
'What other contexts to fetch the frameworks from. (all, parents, self)',
VALUE_DEFAULT,
'parents'
);
$limitfrom = new external_value(
PARAM_INT,
'limitfrom we are fetching the records from',
VALUE_DEFAULT,
0
);
$limitnum = new external_value(
PARAM_INT,
'Number of records to fetch',
VALUE_DEFAULT,
25
);
return new external_function_parameters(array(
'query' => $query,
'context' => self::get_context_parameters(),
'includes' => $includes,
'limitfrom' => $limitfrom,
'limitnum' => $limitnum
));
}
/**
* Search cohorts.
*
* @param string $query
* @param array $context
* @param string $includes
* @param int $limitfrom
* @param int $limitnum
* @return array
*/
public static function search_cohorts($query, $context, $includes = 'parents', $limitfrom = 0, $limitnum = 25) {
global $CFG;
require_once($CFG->dirroot . '/cohort/lib.php');
$params = self::validate_parameters(self::search_cohorts_parameters(), array(
'query' => $query,
'context' => $context,
'includes' => $includes,
'limitfrom' => $limitfrom,
'limitnum' => $limitnum,
));
$query = $params['query'];
$includes = $params['includes'];
$context = self::get_context_from_params($params['context']);
$limitfrom = $params['limitfrom'];
$limitnum = $params['limitnum'];
self::validate_context($context);
$manager = has_capability('moodle/cohort:manage', $context);
if (!$manager) {
require_capability('moodle/cohort:view', $context);
}
// TODO Make this more efficient.
if ($includes == 'self') {
$results = cohort_get_cohorts($context->id, $limitfrom, $limitnum, $query);
$results = $results['cohorts'];
} else if ($includes == 'parents') {
$results = cohort_get_cohorts($context->id, $limitfrom, $limitnum, $query);
$results = $results['cohorts'];
if (!$context instanceof context_system) {
$results = $results + cohort_get_available_cohorts($context, COHORT_ALL, $limitfrom, $limitnum, $query);
}
} else if ($includes == 'all') {
$results = cohort_get_all_cohorts($limitfrom, $limitnum, $query);
$results = $results['cohorts'];
} else {
throw new coding_exception('Invalid parameter value for \'includes\'.');
}
$cohorts = array();
if (!empty($results)) {
$cohortids = array_column($results, 'id');
$customfieldsdata = self::get_custom_fields_data($cohortids);
}
foreach ($results as $key => $cohort) {
$cohortcontext = context::instance_by_id($cohort->contextid);
// Only return theme when $CFG->allowcohortthemes is enabled.
if (!empty($cohort->theme) && empty($CFG->allowcohortthemes)) {
$cohort->theme = null;
}
if (!isset($cohort->description)) {
$cohort->description = '';
}
if (!isset($cohort->descriptionformat)) {
$cohort->descriptionformat = FORMAT_PLAIN;
}
list($cohort->description, $cohort->descriptionformat) =
\core_external\util::format_text($cohort->description, $cohort->descriptionformat,
$cohortcontext, 'cohort', 'description', $cohort->id);
$cohort->customfields = !empty($customfieldsdata[$cohort->id]) ? $customfieldsdata[$cohort->id] : [];
$cohorts[$key] = $cohort;
}
return array('cohorts' => $cohorts);
}
/**
* Returns description of external function result value.
*
* @return \core_external\external_description
*/
public static function search_cohorts_returns() {
return new external_single_structure(array(
'cohorts' => new external_multiple_structure(
new external_single_structure(array(
'id' => new external_value(PARAM_INT, 'ID of the cohort'),
'name' => new external_value(PARAM_RAW, 'cohort name'),
'idnumber' => new external_value(PARAM_RAW, 'cohort idnumber'),
'description' => new external_value(PARAM_RAW, 'cohort description'),
'descriptionformat' => new external_format_value('description'),
'visible' => new external_value(PARAM_BOOL, 'cohort visible'),
'theme' => new external_value(PARAM_THEME, 'cohort theme', VALUE_OPTIONAL),
'customfields' => self::build_custom_fields_returns_structure(),
))
)
));
}
/**
* Returns description of method parameters
*
* @return external_function_parameters
* @since Moodle 2.5
*/
public static function update_cohorts_parameters() {
return new external_function_parameters(
array(
'cohorts' => new external_multiple_structure(
new external_single_structure(
array(
'id' => new external_value(PARAM_INT, 'ID of the cohort'),
'categorytype' => new external_single_structure(
array(
'type' => new external_value(PARAM_TEXT, 'the name of the field: id (numeric value
of course category id) or idnumber (alphanumeric value of idnumber course category)
or system (value ignored)'),
'value' => new external_value(PARAM_RAW, 'the value of the categorytype')
)
),
'name' => new external_value(PARAM_RAW, 'cohort name'),
'idnumber' => new external_value(PARAM_RAW, 'cohort idnumber'),
'description' => new external_value(PARAM_RAW, 'cohort description', VALUE_OPTIONAL),
'descriptionformat' => new external_format_value('description', VALUE_DEFAULT),
'visible' => new external_value(PARAM_BOOL, 'cohort visible', VALUE_OPTIONAL),
'theme' => new external_value(PARAM_THEME,
'the cohort theme. The allowcohortthemes setting must be enabled on Moodle',
VALUE_OPTIONAL
),
'customfields' => self::build_custom_fields_parameters_structure(),
)
)
)
)
);
}
/**
* Update cohorts
*
* @param array $cohorts
* @return null
* @since Moodle 2.5
*/
public static function update_cohorts($cohorts) {
global $CFG, $DB;
require_once("$CFG->dirroot/cohort/lib.php");
$params = self::validate_parameters(self::update_cohorts_parameters(), array('cohorts' => $cohorts));
$availablethemes = cohort_get_list_of_themes();
$transaction = $DB->start_delegated_transaction();
$syscontext = context_system::instance();
foreach ($params['cohorts'] as $cohort) {
$cohort = (object) $cohort;
if (trim($cohort->name) == '') {
throw new invalid_parameter_exception('Invalid cohort name');
}
$oldcohort = $DB->get_record('cohort', array('id' => $cohort->id), '*', MUST_EXIST);
$oldcontext = context::instance_by_id($oldcohort->contextid, MUST_EXIST);
require_capability('moodle/cohort:manage', $oldcontext);
// Category type (context id).
$categorytype = $cohort->categorytype;
if (!in_array($categorytype['type'], array('idnumber', 'id', 'system'))) {
throw new invalid_parameter_exception('category type must be id, idnumber or system:' . $categorytype['type']);
}
if ($categorytype['type'] === 'system') {
$cohort->contextid = $syscontext->id;
} else if ($catid = $DB->get_field('course_categories', 'id', array($categorytype['type'] => $categorytype['value']))) {
$cohort->contextid = $DB->get_field('context', 'id', array('instanceid' => $catid,
'contextlevel' => CONTEXT_COURSECAT));
} else {
throw new invalid_parameter_exception('category not exists: category='.$categorytype['value']);
}
if ($cohort->contextid != $oldcohort->contextid) {
$context = context::instance_by_id($cohort->contextid, MUST_EXIST);
if ($context->contextlevel != CONTEXT_COURSECAT and $context->contextlevel != CONTEXT_SYSTEM) {
throw new invalid_parameter_exception('Invalid context');
}
self::validate_context($context);
require_capability('moodle/cohort:manage', $context);
}
// Make sure theme is valid.
if (!empty($cohort->theme) && !empty($CFG->allowcohortthemes)) {
if (empty($availablethemes[$cohort->theme])) {
$debuginfo = 'The following cohort theme is not installed on this site: '.$cohort->theme;
throw new moodle_exception('errorinvalidparam', 'webservice', '', 'theme', $debuginfo);
}
}
if (!empty($cohort->description)) {
$cohort->descriptionformat = util::validate_format($cohort->descriptionformat);
}
// Custom fields.
if (!empty($cohort->customfields)) {
foreach ($cohort->customfields as $field) {
$fieldname = self::build_custom_field_name($field['shortname']);
$cohort->{$fieldname} = $field['value'];
}
unset($cohort->customfields);
}
cohort_update_cohort($cohort);
}
$transaction->allow_commit();
return null;
}
/**
* Returns description of method result value
*
* @return null
* @since Moodle 2.5
*/
public static function update_cohorts_returns() {
return null;
}
/**
* Returns description of method parameters
*
* @return external_function_parameters
* @since Moodle 2.5
*/
public static function add_cohort_members_parameters() {
return new external_function_parameters (
array(
'members' => new external_multiple_structure (
new external_single_structure (
array (
'cohorttype' => new external_single_structure (
array(
'type' => new external_value(PARAM_ALPHANUMEXT, 'The name of the field: id
(numeric value of cohortid) or idnumber (alphanumeric value of idnumber) '),
'value' => new external_value(PARAM_RAW, 'The value of the cohort')
)
),
'usertype' => new external_single_structure (
array(
'type' => new external_value(PARAM_ALPHANUMEXT, 'The name of the field: id
(numeric value of id) or username (alphanumeric value of username) '),
'value' => new external_value(PARAM_RAW, 'The value of the cohort')
)
)
)
)
)
)
);
}
/**
* Add cohort members
*
* @param array $members of arrays with keys userid, cohortid
* @since Moodle 2.5
*/
public static function add_cohort_members($members) {
global $CFG, $DB;
require_once($CFG->dirroot."/cohort/lib.php");
$params = self::validate_parameters(self::add_cohort_members_parameters(), array('members' => $members));
$transaction = $DB->start_delegated_transaction();
$warnings = array();
foreach ($params['members'] as $member) {
// Cohort parameters.
$cohorttype = $member['cohorttype'];
$cohortparam = array($cohorttype['type'] => $cohorttype['value']);
// User parameters.
$usertype = $member['usertype'];
$userparam = array($usertype['type'] => $usertype['value']);
try {
// Check parameters.
if ($cohorttype['type'] != 'id' && $cohorttype['type'] != 'idnumber') {
$warning = array();
$warning['warningcode'] = '1';
$warning['message'] = 'invalid parameter: cohortype='.$cohorttype['type'];
$warnings[] = $warning;
continue;
}
if ($usertype['type'] != 'id' && $usertype['type'] != 'username') {
$warning = array();
$warning['warningcode'] = '1';
$warning['message'] = 'invalid parameter: usertype='.$usertype['type'];
$warnings[] = $warning;
continue;
}
// Extract parameters.
if (!$cohortid = $DB->get_field('cohort', 'id', $cohortparam)) {
$warning = array();
$warning['warningcode'] = '2';
$warning['message'] = 'cohort '.$cohorttype['type'].'='.$cohorttype['value'].' not exists';
$warnings[] = $warning;
continue;
}
if (!$userid = $DB->get_field('user', 'id', array_merge($userparam, array('deleted' => 0,
'mnethostid' => $CFG->mnet_localhost_id)))) {
$warning = array();
$warning['warningcode'] = '2';
$warning['message'] = 'user '.$usertype['type'].'='.$usertype['value'].' not exists';
$warnings[] = $warning;
continue;
}
if ($DB->record_exists('cohort_members', array('cohortid' => $cohortid, 'userid' => $userid))) {
$warning = array();
$warning['warningcode'] = '3';
$warning['message'] = 'record already exists: cohort('.$cohorttype['type'].'='.$cohorttype['value'].' '.
$usertype['type'].'='.$usertype['value'].')';
$warnings[] = $warning;
continue;
}
$cohort = $DB->get_record('cohort', array('id'=>$cohortid), '*', MUST_EXIST);
$context = context::instance_by_id($cohort->contextid, MUST_EXIST);
if ($context->contextlevel != CONTEXT_COURSECAT and $context->contextlevel != CONTEXT_SYSTEM) {
$warning = array();
$warning['warningcode'] = '1';
$warning['message'] = 'Invalid context: '.$context->contextlevel;
$warnings[] = $warning;
continue;
}
self::validate_context($context);
} catch (Exception $e) {
throw new moodle_exception('Error', 'cohort', '', $e->getMessage());
}
if (!has_any_capability(array('moodle/cohort:manage', 'moodle/cohort:assign'), $context)) {
throw new required_capability_exception($context, 'moodle/cohort:assign', 'nopermissions', '');
}
cohort_add_member($cohortid, $userid);
}
$transaction->allow_commit();
// Return.
$result = array();
$result['warnings'] = $warnings;
return $result;
}
/**
* Returns description of method result value
*
* @return null
* @since Moodle 2.5
*/
public static function add_cohort_members_returns() {
return new external_single_structure(
array(
'warnings' => new external_warnings()
)
);
}
/**
* Returns description of method parameters
*
* @return external_function_parameters
* @since Moodle 2.5
*/
public static function delete_cohort_members_parameters() {
return new external_function_parameters(
array(
'members' => new external_multiple_structure(
new external_single_structure(
array(
'cohortid' => new external_value(PARAM_INT, 'cohort record id'),
'userid' => new external_value(PARAM_INT, 'user id'),
)
)
)
)
);
}
/**
* Delete cohort members
*
* @param array $members of arrays with keys userid, cohortid
* @since Moodle 2.5
*/
public static function delete_cohort_members($members) {
global $CFG, $DB;
require_once("$CFG->dirroot/cohort/lib.php");
// Validate parameters.
$params = self::validate_parameters(self::delete_cohort_members_parameters(), array('members' => $members));
$transaction = $DB->start_delegated_transaction();
foreach ($params['members'] as $member) {
$cohortid = $member['cohortid'];
$userid = $member['userid'];
$cohort = $DB->get_record('cohort', array('id' => $cohortid), '*', MUST_EXIST);
$user = $DB->get_record('user', array('id' => $userid, 'deleted' => 0, 'mnethostid' => $CFG->mnet_localhost_id),
'*', MUST_EXIST);
// Now security checks.
$context = context::instance_by_id($cohort->contextid, MUST_EXIST);
if ($context->contextlevel != CONTEXT_COURSECAT and $context->contextlevel != CONTEXT_SYSTEM) {
throw new invalid_parameter_exception('Invalid context');
}
self::validate_context($context);
if (!has_any_capability(array('moodle/cohort:manage', 'moodle/cohort:assign'), $context)) {
throw new required_capability_exception($context, 'moodle/cohort:assign', 'nopermissions', '');
}
cohort_remove_member($cohort->id, $user->id);
}
$transaction->allow_commit();
}
/**
* Returns description of method result value
*
* @return null
* @since Moodle 2.5
*/
public static function delete_cohort_members_returns() {
return null;
}
/**
* Returns description of method parameters
*
* @return external_function_parameters
* @since Moodle 2.5
*/
public static function get_cohort_members_parameters() {
return new external_function_parameters(
array(
'cohortids' => new external_multiple_structure(new external_value(PARAM_INT, 'Cohort ID')),
)
);
}
/**
* Return all members for a cohort
*
* @param array $cohortids array of cohort ids
* @return array with cohort id keys containing arrays of user ids
* @since Moodle 2.5
*/
public static function get_cohort_members($cohortids) {
global $DB;
$params = self::validate_parameters(self::get_cohort_members_parameters(), array('cohortids' => $cohortids));
$members = array();
foreach ($params['cohortids'] as $cohortid) {
// Validate params.
$cohort = $DB->get_record('cohort', array('id' => $cohortid), '*', MUST_EXIST);
// Now security checks.
$context = context::instance_by_id($cohort->contextid, MUST_EXIST);
if ($context->contextlevel != CONTEXT_COURSECAT and $context->contextlevel != CONTEXT_SYSTEM) {
throw new invalid_parameter_exception('Invalid context');
}
self::validate_context($context);
if (!has_any_capability(array('moodle/cohort:manage', 'moodle/cohort:view'), $context)) {
throw new required_capability_exception($context, 'moodle/cohort:view', 'nopermissions', '');
}
$cohortmembers = $DB->get_records_sql("SELECT u.id FROM {user} u, {cohort_members} cm
WHERE u.id = cm.userid AND cm.cohortid = ?
ORDER BY lastname ASC, firstname ASC", array($cohort->id));
$members[] = array('cohortid' => $cohortid, 'userids' => array_keys($cohortmembers));
}
return $members;
}
/**
* Returns description of method result value
*
* @return \core_external\external_description
* @since Moodle 2.5
*/
public static function get_cohort_members_returns() {
return new external_multiple_structure(
new external_single_structure(
array(
'cohortid' => new external_value(PARAM_INT, 'cohort record id'),
'userids' => new external_multiple_structure(new external_value(PARAM_INT, 'user id')),
)
)
);
}
/**
* Builds a structure for custom fields parameters.
*
* @return \core_external\external_multiple_structure
*/
protected static function build_custom_fields_parameters_structure(): external_multiple_structure {
return new external_multiple_structure(
new external_single_structure(
array(
'shortname' => new external_value(PARAM_ALPHANUMEXT, 'The shortname of the custom field'),
'value' => new external_value(PARAM_RAW, 'The value of the custom field'),
)
), 'Custom fields for the cohort', VALUE_OPTIONAL
);
}
/**
* Builds a structure for custom fields returns.
*
* @return \core_external\external_multiple_structure
*/
protected static function build_custom_fields_returns_structure(): external_multiple_structure {
return new external_multiple_structure(
new external_single_structure(
array(
'name' => new external_value(PARAM_RAW, 'The name of the custom field'),
'shortname' => new external_value(PARAM_RAW,
'The shortname of the custom field - to be able to build the field class in the code'),
'type' => new external_value(PARAM_ALPHANUMEXT,
'The type of the custom field - text field, checkbox...'),
'valueraw' => new external_value(PARAM_RAW, 'The raw value of the custom field'),
'value' => new external_value(PARAM_RAW, 'The value of the custom field'),
)
), 'Custom fields', VALUE_OPTIONAL
);
}
/**
* Returns custom fields data for provided cohorts.
*
* @param array $cohortids a list of cohort IDs to provide data for.
* @return array
*/
protected static function get_custom_fields_data(array $cohortids): array {
$result = [];
$customfieldsdata = cohort_get_custom_fields_data($cohortids);
foreach ($customfieldsdata as $cohortid => $fieldcontrollers) {
foreach ($fieldcontrollers as $fieldcontroller) {
$result[$cohortid][] = [
'type' => $fieldcontroller->get_field()->get('type'),
'value' => $fieldcontroller->export_value(),
'valueraw' => $fieldcontroller->get_value(),
'name' => $fieldcontroller->get_field()->get('name'),
'shortname' => $fieldcontroller->get_field()->get('shortname'),
];
}
}
return $result;
}
/**
* Builds a suitable name of a custom field for a custom field handler based on provided shortname.
*
* @param string $shortname shortname to use.
* @return string
*/
protected static function build_custom_field_name(string $shortname): string {
return 'customfield_' . $shortname;
}
}
+119
View File
@@ -0,0 +1,119 @@
<?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/>.
/**
* Cohort related management functions, this file needs to be included manually.
*
* @package core_cohort
* @copyright 2010 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use core_cohort\reportbuilder\local\systemreports\cohorts;
use core_reportbuilder\local\filters\text;
use core_reportbuilder\system_report_factory;
require('../config.php');
require_once($CFG->dirroot.'/cohort/lib.php');
require_once($CFG->libdir.'/adminlib.php');
$contextid = optional_param('contextid', 0, PARAM_INT);
$searchquery = optional_param('search', '', PARAM_RAW);
$showall = optional_param('showall', false, PARAM_BOOL);
require_login();
if ($contextid) {
$context = context::instance_by_id($contextid, MUST_EXIST);
} else {
$context = context_system::instance();
}
if ($context->contextlevel != CONTEXT_COURSECAT and $context->contextlevel != CONTEXT_SYSTEM) {
throw new \moodle_exception('invalidcontext');
}
$category = null;
if ($context->contextlevel == CONTEXT_COURSECAT) {
$category = $DB->get_record('course_categories', array('id'=>$context->instanceid), '*', MUST_EXIST);
}
$manager = has_capability('moodle/cohort:manage', $context);
$canassign = has_capability('moodle/cohort:assign', $context);
if (!$manager) {
require_capability('moodle/cohort:view', $context);
}
$strcohorts = get_string('cohorts', 'cohort');
if ($category) {
$PAGE->set_pagelayout('admin');
$PAGE->set_context($context);
$PAGE->set_url('/cohort/index.php', array('contextid'=>$context->id));
core_course_category::page_setup();
// Set the cohorts node active in the settings navigation block.
if ($cohortsnode = $PAGE->settingsnav->find('cohort', navigation_node::TYPE_SETTING)) {
$cohortsnode->make_active();
}
$PAGE->set_title($strcohorts);
$showall = false;
} else {
admin_externalpage_setup('cohorts', '', null, '', array('pagelayout'=>'report'));
navigation_node::override_active_url(new moodle_url('/cohort/index.php'));
if ($showall) {
$strallcohorts = get_string('allcohorts', 'cohort');
$PAGE->set_title($strallcohorts);
$PAGE->navbar->add($strallcohorts, $PAGE->url);
} else {
$strsystemcohorts = get_string('systemcohorts', 'cohort');
$PAGE->set_title($strsystemcohorts);
}
}
echo $OUTPUT->header();
echo $OUTPUT->heading($strallcohorts ?? $strsystemcohorts ?? $strcohorts);
$params = [];
if ($contextid) {
$params['contextid'] = $contextid;
}
if ($searchquery) {
$params['search'] = $searchquery;
}
if ($showall) {
$params['showall'] = true;
}
$baseurl = new moodle_url('/cohort/index.php', $params);
if ($editcontrols = cohort_edit_controls($context, $baseurl)) {
echo $OUTPUT->render($editcontrols);
}
$reportparams = ['contextid' => $context->id, 'showall' => $showall];
$report = system_report_factory::create(cohorts::class, $context, '', '', 0, $reportparams);
// Check if it needs to search by name.
if (!empty($searchquery)) {
$report->set_filter_values([
'cohort:name_operator' => text::CONTAINS,
'cohort:name_value' => $searchquery,
]);
}
echo $report->output();
echo $OUTPUT->footer();
+726
View File
@@ -0,0 +1,726 @@
<?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/>.
/**
* Cohort related management functions, this file needs to be included manually.
*
* @package core_cohort
* @copyright 2010 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
define('COHORT_ALL', 0);
define('COHORT_COUNT_MEMBERS', 1);
define('COHORT_COUNT_ENROLLED_MEMBERS', 3);
define('COHORT_WITH_MEMBERS_ONLY', 5);
define('COHORT_WITH_ENROLLED_MEMBERS_ONLY', 17);
define('COHORT_WITH_NOTENROLLED_MEMBERS_ONLY', 23);
/**
* Add new cohort.
*
* @param stdClass $cohort
* @return int new cohort id
*/
function cohort_add_cohort($cohort) {
global $DB, $CFG;
if (!isset($cohort->name)) {
throw new coding_exception('Missing cohort name in cohort_add_cohort().');
}
if (!isset($cohort->idnumber)) {
$cohort->idnumber = NULL;
}
if (!isset($cohort->description)) {
$cohort->description = '';
}
if (!isset($cohort->descriptionformat)) {
$cohort->descriptionformat = FORMAT_HTML;
}
if (!isset($cohort->visible)) {
$cohort->visible = 1;
}
if (empty($cohort->component)) {
$cohort->component = '';
}
if (empty($CFG->allowcohortthemes) && isset($cohort->theme)) {
unset($cohort->theme);
}
if (empty($cohort->theme) || empty($CFG->allowcohortthemes)) {
$cohort->theme = '';
}
if (!isset($cohort->timecreated)) {
$cohort->timecreated = time();
}
if (!isset($cohort->timemodified)) {
$cohort->timemodified = $cohort->timecreated;
}
$cohort->id = $DB->insert_record('cohort', $cohort);
$handler = core_cohort\customfield\cohort_handler::create();
$handler->instance_form_save($cohort, true);
$event = \core\event\cohort_created::create(array(
'context' => context::instance_by_id($cohort->contextid),
'objectid' => $cohort->id,
));
$event->add_record_snapshot('cohort', $cohort);
$event->trigger();
return $cohort->id;
}
/**
* Update existing cohort.
* @param stdClass $cohort
* @return void
*/
function cohort_update_cohort($cohort) {
global $DB, $CFG;
if (property_exists($cohort, 'component') and empty($cohort->component)) {
// prevent NULLs
$cohort->component = '';
}
// Only unset the cohort theme if allowcohortthemes is enabled to prevent the value from being overwritten.
if (empty($CFG->allowcohortthemes) && isset($cohort->theme)) {
unset($cohort->theme);
}
// Delete theme usage cache if the theme has been changed.
if (isset($cohort->theme)) {
$oldcohort = $DB->get_record('cohort', ['id' => $cohort->id]);
if ($cohort->theme != $oldcohort->theme) {
theme_delete_used_in_context_cache($cohort->theme, $oldcohort->theme);
}
}
$cohort->timemodified = time();
// Update custom fields if there are any of them in the form.
$handler = core_cohort\customfield\cohort_handler::create();
$handler->instance_form_save($cohort);
$DB->update_record('cohort', $cohort);
$event = \core\event\cohort_updated::create(array(
'context' => context::instance_by_id($cohort->contextid),
'objectid' => $cohort->id,
));
$event->trigger();
}
/**
* Delete cohort.
* @param stdClass $cohort
* @return void
*/
function cohort_delete_cohort($cohort) {
global $DB;
if ($cohort->component) {
// TODO: add component delete callback
}
$handler = core_cohort\customfield\cohort_handler::create();
$handler->delete_instance($cohort->id);
$DB->delete_records('cohort_members', array('cohortid'=>$cohort->id));
$DB->delete_records('cohort', array('id'=>$cohort->id));
// Notify the competency subsystem.
\core_competency\api::hook_cohort_deleted($cohort);
$event = \core\event\cohort_deleted::create(array(
'context' => context::instance_by_id($cohort->contextid),
'objectid' => $cohort->id,
));
$event->add_record_snapshot('cohort', $cohort);
$event->trigger();
}
/**
* Somehow deal with cohorts when deleting course category,
* we can not just delete them because they might be used in enrol
* plugins or referenced in external systems.
* @param stdClass|core_course_category $category
* @return void
*/
function cohort_delete_category($category) {
global $DB;
// TODO: make sure that cohorts are really, really not used anywhere and delete, for now just move to parent or system context
$oldcontext = context_coursecat::instance($category->id);
if ($category->parent and $parent = $DB->get_record('course_categories', array('id'=>$category->parent))) {
$parentcontext = context_coursecat::instance($parent->id);
$sql = "UPDATE {cohort} SET contextid = :newcontext WHERE contextid = :oldcontext";
$params = array('oldcontext'=>$oldcontext->id, 'newcontext'=>$parentcontext->id);
} else {
$syscontext = context_system::instance();
$sql = "UPDATE {cohort} SET contextid = :newcontext WHERE contextid = :oldcontext";
$params = array('oldcontext'=>$oldcontext->id, 'newcontext'=>$syscontext->id);
}
$DB->execute($sql, $params);
}
/**
* Add cohort member
* @param int $cohortid
* @param int $userid
* @return void
*/
function cohort_add_member($cohortid, $userid) {
global $DB;
if ($DB->record_exists('cohort_members', array('cohortid'=>$cohortid, 'userid'=>$userid))) {
// No duplicates!
return;
}
$record = new stdClass();
$record->cohortid = $cohortid;
$record->userid = $userid;
$record->timeadded = time();
$DB->insert_record('cohort_members', $record);
$cohort = $DB->get_record('cohort', array('id' => $cohortid), '*', MUST_EXIST);
$event = \core\event\cohort_member_added::create(array(
'context' => context::instance_by_id($cohort->contextid),
'objectid' => $cohortid,
'relateduserid' => $userid,
));
$event->add_record_snapshot('cohort', $cohort);
$event->trigger();
}
/**
* Remove cohort member
* @param int $cohortid
* @param int $userid
* @return void
*/
function cohort_remove_member($cohortid, $userid) {
global $DB;
$DB->delete_records('cohort_members', array('cohortid'=>$cohortid, 'userid'=>$userid));
$cohort = $DB->get_record('cohort', array('id' => $cohortid), '*', MUST_EXIST);
$event = \core\event\cohort_member_removed::create(array(
'context' => context::instance_by_id($cohort->contextid),
'objectid' => $cohortid,
'relateduserid' => $userid,
));
$event->add_record_snapshot('cohort', $cohort);
$event->trigger();
}
/**
* Is this user a cohort member?
* @param int $cohortid
* @param int $userid
* @return bool
*/
function cohort_is_member($cohortid, $userid) {
global $DB;
return $DB->record_exists('cohort_members', array('cohortid'=>$cohortid, 'userid'=>$userid));
}
/**
* Returns the list of cohorts visible to the current user in the given course.
*
* The following fields are returned in each record: id, name, contextid, idnumber, visible
* Fields memberscnt and enrolledcnt will be also returned if requested
*
* @param context $currentcontext
* @param int $withmembers one of the COHORT_XXX constants that allows to return non empty cohorts only
* or cohorts with enroled/not enroled users, or just return members count
* @param int $offset
* @param int $limit
* @param string $search
* @param bool $withcustomfields if set to yes, then cohort custom fields will be included in the results.
* @return array
*/
function cohort_get_available_cohorts($currentcontext, $withmembers = 0, $offset = 0, $limit = 25,
$search = '', $withcustomfields = false) {
global $DB;
$params = array();
// Build context subquery. Find the list of parent context where user is able to see any or visible-only cohorts.
// Since this method is normally called for the current course all parent contexts are already preloaded.
$contextsany = array_filter($currentcontext->get_parent_context_ids(),
function($a) {
return has_capability("moodle/cohort:view", context::instance_by_id($a));
});
$contextsvisible = array_diff($currentcontext->get_parent_context_ids(), $contextsany);
if (empty($contextsany) && empty($contextsvisible)) {
// User does not have any permissions to view cohorts.
return array();
}
$subqueries = array();
if (!empty($contextsany)) {
list($parentsql, $params1) = $DB->get_in_or_equal($contextsany, SQL_PARAMS_NAMED, 'ctxa');
$subqueries[] = 'c.contextid ' . $parentsql;
$params = array_merge($params, $params1);
}
if (!empty($contextsvisible)) {
list($parentsql, $params1) = $DB->get_in_or_equal($contextsvisible, SQL_PARAMS_NAMED, 'ctxv');
$subqueries[] = '(c.visible = 1 AND c.contextid ' . $parentsql. ')';
$params = array_merge($params, $params1);
}
$wheresql = '(' . implode(' OR ', $subqueries) . ')';
// Build the rest of the query.
$fromsql = "";
$fieldssql = 'c.id, c.name, c.contextid, c.idnumber, c.visible';
$groupbysql = '';
$havingsql = '';
if ($withmembers) {
$fieldssql .= ', s.memberscnt';
$subfields = "c.id, COUNT(DISTINCT cm.userid) AS memberscnt";
$groupbysql = " GROUP BY c.id";
$fromsql = " LEFT JOIN {cohort_members} cm ON cm.cohortid = c.id ";
if (in_array($withmembers,
array(COHORT_COUNT_ENROLLED_MEMBERS, COHORT_WITH_ENROLLED_MEMBERS_ONLY, COHORT_WITH_NOTENROLLED_MEMBERS_ONLY))) {
list($esql, $params2) = get_enrolled_sql($currentcontext);
$fromsql .= " LEFT JOIN ($esql) u ON u.id = cm.userid ";
$params = array_merge($params2, $params);
$fieldssql .= ', s.enrolledcnt';
$subfields .= ', COUNT(DISTINCT u.id) AS enrolledcnt';
}
if ($withmembers == COHORT_WITH_MEMBERS_ONLY) {
$havingsql = " HAVING COUNT(DISTINCT cm.userid) > 0";
} else if ($withmembers == COHORT_WITH_ENROLLED_MEMBERS_ONLY) {
$havingsql = " HAVING COUNT(DISTINCT u.id) > 0";
} else if ($withmembers == COHORT_WITH_NOTENROLLED_MEMBERS_ONLY) {
$havingsql = " HAVING COUNT(DISTINCT cm.userid) > COUNT(DISTINCT u.id)";
}
}
if ($search) {
list($searchsql, $searchparams) = cohort_get_search_query($search);
$wheresql .= ' AND ' . $searchsql;
$params = array_merge($params, $searchparams);
}
if ($withmembers) {
$sql = "SELECT " . str_replace('c.', 'cohort.', $fieldssql) . "
FROM {cohort} cohort
JOIN (SELECT $subfields
FROM {cohort} c $fromsql
WHERE $wheresql $groupbysql $havingsql
) s ON cohort.id = s.id
ORDER BY cohort.name, cohort.idnumber";
} else {
$sql = "SELECT $fieldssql
FROM {cohort} c $fromsql
WHERE $wheresql
ORDER BY c.name, c.idnumber";
}
$cohorts = $DB->get_records_sql($sql, $params, $offset, $limit);
if ($withcustomfields) {
$cohortids = array_keys($cohorts);
$customfieldsdata = cohort_get_custom_fields_data($cohortids);
foreach ($cohorts as $cohort) {
$cohort->customfields = !empty($customfieldsdata[$cohort->id]) ? $customfieldsdata[$cohort->id] : [];
}
}
return $cohorts;
}
/**
* Check if cohort exists and user is allowed to access it from the given context.
*
* @param stdClass|int $cohortorid cohort object or id
* @param context $currentcontext current context (course) where visibility is checked
* @return boolean
*/
function cohort_can_view_cohort($cohortorid, $currentcontext) {
global $DB;
if (is_numeric($cohortorid)) {
$cohort = $DB->get_record('cohort', array('id' => $cohortorid), 'id, contextid, visible');
} else {
$cohort = $cohortorid;
}
if ($cohort && in_array($cohort->contextid, $currentcontext->get_parent_context_ids())) {
if ($cohort->visible) {
return true;
}
$cohortcontext = context::instance_by_id($cohort->contextid);
if (has_capability('moodle/cohort:view', $cohortcontext)) {
return true;
}
}
return false;
}
/**
* Get a cohort by id. Also does a visibility check and returns false if the user cannot see this cohort.
*
* @param stdClass|int $cohortorid cohort object or id
* @param context $currentcontext current context (course) where visibility is checked
* @param bool $withcustomfields if set to yes, then cohort custom fields will be included in the results.
* @return stdClass|boolean
*/
function cohort_get_cohort($cohortorid, $currentcontext, $withcustomfields = false) {
global $DB;
if (is_numeric($cohortorid)) {
$cohort = $DB->get_record('cohort', array('id' => $cohortorid), 'id, contextid, visible');
} else {
$cohort = $cohortorid;
}
if ($cohort && in_array($cohort->contextid, $currentcontext->get_parent_context_ids())) {
if (!$cohort->visible) {
$cohortcontext = context::instance_by_id($cohort->contextid);
if (!has_capability('moodle/cohort:view', $cohortcontext)) {
return false;
}
}
} else {
return false;
}
if ($cohort && $withcustomfields) {
$customfieldsdata = cohort_get_custom_fields_data([$cohort->id]);
$cohort->customfields = !empty($customfieldsdata[$cohort->id]) ? $customfieldsdata[$cohort->id] : [];
}
return $cohort;
}
/**
* Produces a part of SQL query to filter cohorts by the search string
*
* Called from {@link cohort_get_cohorts()}, {@link cohort_get_all_cohorts()} and {@link cohort_get_available_cohorts()}
*
* @access private
*
* @param string $search search string
* @param string $tablealias alias of cohort table in the SQL query (highly recommended if other tables are used in query)
* @return array of two elements - SQL condition and array of named parameters
*/
function cohort_get_search_query($search, $tablealias = '') {
global $DB;
$params = array();
if (empty($search)) {
// This function should not be called if there is no search string, just in case return dummy query.
return array('1=1', $params);
}
if ($tablealias && substr($tablealias, -1) !== '.') {
$tablealias .= '.';
}
$searchparam = '%' . $DB->sql_like_escape($search) . '%';
$conditions = array();
$fields = array('name', 'idnumber', 'description');
$cnt = 0;
foreach ($fields as $field) {
$conditions[] = $DB->sql_like($tablealias . $field, ':csearch' . $cnt, false);
$params['csearch' . $cnt] = $searchparam;
$cnt++;
}
$sql = '(' . implode(' OR ', $conditions) . ')';
return array($sql, $params);
}
/**
* Get all the cohorts defined in given context.
*
* The function does not check user capability to view/manage cohorts in the given context
* assuming that it has been already verified.
*
* @param int $contextid
* @param int $page number of the current page
* @param int $perpage items per page
* @param string $search search string
* @param bool $withcustomfields if set to yes, then cohort custom fields will be included in the results.
* @return array Array(totalcohorts => int, cohorts => array, allcohorts => int)
*/
function cohort_get_cohorts($contextid, $page = 0, $perpage = 25, $search = '', $withcustomfields = false) {
global $DB;
$fields = "SELECT *";
$countfields = "SELECT COUNT(1)";
$sql = " FROM {cohort}
WHERE contextid = :contextid";
$params = array('contextid' => $contextid);
$order = " ORDER BY name ASC, idnumber ASC";
if (!empty($search)) {
list($searchcondition, $searchparams) = cohort_get_search_query($search);
$sql .= ' AND ' . $searchcondition;
$params = array_merge($params, $searchparams);
}
$totalcohorts = $allcohorts = $DB->count_records('cohort', array('contextid' => $contextid));
if (!empty($search)) {
$totalcohorts = $DB->count_records_sql($countfields . $sql, $params);
}
$cohorts = $DB->get_records_sql($fields . $sql . $order, $params, $page*$perpage, $perpage);
if ($withcustomfields) {
$cohortids = array_keys($cohorts);
$customfieldsdata = cohort_get_custom_fields_data($cohortids);
foreach ($cohorts as $cohort) {
$cohort->customfields = !empty($customfieldsdata[$cohort->id]) ? $customfieldsdata[$cohort->id] : [];
}
}
return array('totalcohorts' => $totalcohorts, 'cohorts' => $cohorts, 'allcohorts' => $allcohorts);
}
/**
* Get all the cohorts defined anywhere in system.
*
* The function assumes that user capability to view/manage cohorts on system level
* has already been verified. This function only checks if such capabilities have been
* revoked in child (categories) contexts.
*
* @param int $page number of the current page
* @param int $perpage items per page
* @param string $search search string
* @param bool $withcustomfields if set to yes, then cohort custom fields will be included in the results.
* @return array Array(totalcohorts => int, cohorts => array, allcohorts => int)
*/
function cohort_get_all_cohorts($page = 0, $perpage = 25, $search = '', $withcustomfields = false) {
global $DB;
$fields = "SELECT c.*, ".context_helper::get_preload_record_columns_sql('ctx');
$countfields = "SELECT COUNT(*)";
$sql = " FROM {cohort} c
JOIN {context} ctx ON ctx.id = c.contextid ";
$params = array();
$wheresql = '';
if ($excludedcontexts = cohort_get_invisible_contexts()) {
list($excludedsql, $excludedparams) = $DB->get_in_or_equal($excludedcontexts, SQL_PARAMS_NAMED, 'excl', false);
$wheresql = ' WHERE c.contextid '.$excludedsql;
$params = array_merge($params, $excludedparams);
}
$totalcohorts = $allcohorts = $DB->count_records_sql($countfields . $sql . $wheresql, $params);
if (!empty($search)) {
list($searchcondition, $searchparams) = cohort_get_search_query($search, 'c');
$wheresql .= ($wheresql ? ' AND ' : ' WHERE ') . $searchcondition;
$params = array_merge($params, $searchparams);
$totalcohorts = $DB->count_records_sql($countfields . $sql . $wheresql, $params);
}
$order = " ORDER BY c.name ASC, c.idnumber ASC";
$cohorts = $DB->get_records_sql($fields . $sql . $wheresql . $order, $params, $page*$perpage, $perpage);
if ($withcustomfields) {
$cohortids = array_keys($cohorts);
$customfieldsdata = cohort_get_custom_fields_data($cohortids);
}
foreach ($cohorts as $cohort) {
// Preload used contexts, they will be used to check view/manage/assign capabilities and display categories names.
context_helper::preload_from_record($cohort);
if ($withcustomfields) {
$cohort->customfields = !empty($customfieldsdata[$cohort->id]) ? $customfieldsdata[$cohort->id] : [];
}
}
return array('totalcohorts' => $totalcohorts, 'cohorts' => $cohorts, 'allcohorts' => $allcohorts);
}
/**
* Get all the cohorts where the given user is member of.
*
* @param int $userid
* @param bool $withcustomfields if set to yes, then cohort custom fields will be included in the results.
* @return array Array
*/
function cohort_get_user_cohorts($userid, $withcustomfields = false) {
global $DB;
$sql = 'SELECT c.*
FROM {cohort} c
JOIN {cohort_members} cm ON c.id = cm.cohortid
WHERE cm.userid = ? AND c.visible = 1';
$cohorts = $DB->get_records_sql($sql, array($userid));
if ($withcustomfields) {
$cohortids = array_keys($cohorts);
$customfieldsdata = cohort_get_custom_fields_data($cohortids);
foreach ($cohorts as $cohort) {
$cohort->customfields = !empty($customfieldsdata[$cohort->id]) ? $customfieldsdata[$cohort->id] : [];
}
}
return $cohorts;
}
/**
* Get the user cohort theme.
*
* If the user is member of one cohort, will return this cohort theme (if defined).
* If the user is member of 2 or more cohorts, will return the theme if all them have the same
* theme (null themes are ignored).
*
* @param int $userid
* @return string|null
*/
function cohort_get_user_cohort_theme($userid) {
$cohorts = cohort_get_user_cohorts($userid);
$theme = null;
foreach ($cohorts as $cohort) {
if (!empty($cohort->theme)) {
if (null === $theme) {
$theme = $cohort->theme;
} else if ($theme != $cohort->theme) {
return null;
}
}
}
return $theme;
}
/**
* Returns list of contexts where cohorts are present but current user does not have capability to view/manage them.
*
* This function is called from {@link cohort_get_all_cohorts()} to ensure correct pagination in rare cases when user
* is revoked capability in child contexts. It assumes that user's capability to view/manage cohorts on system
* level has already been verified.
*
* @access private
*
* @return array array of context ids
*/
function cohort_get_invisible_contexts() {
global $DB;
if (is_siteadmin()) {
// Shortcut, admin can do anything and can not be prohibited from any context.
return array();
}
$records = $DB->get_recordset_sql("SELECT DISTINCT ctx.id, ".context_helper::get_preload_record_columns_sql('ctx')." ".
"FROM {context} ctx JOIN {cohort} c ON ctx.id = c.contextid ");
$excludedcontexts = array();
foreach ($records as $ctx) {
context_helper::preload_from_record($ctx);
if (context::instance_by_id($ctx->id) == context_system::instance()) {
continue; // System context cohorts should be available and permissions already checked.
}
if (!has_any_capability(array('moodle/cohort:manage', 'moodle/cohort:view'), context::instance_by_id($ctx->id))) {
$excludedcontexts[] = $ctx->id;
}
}
$records->close();
return $excludedcontexts;
}
/**
* Returns navigation controls (tabtree) to be displayed on cohort management pages
*
* @param context $context system or category context where cohorts controls are about to be displayed
* @param moodle_url $currenturl
* @return null|renderable
*/
function cohort_edit_controls(context $context, moodle_url $currenturl) {
$tabs = array();
$currenttab = 'view';
$viewurl = new moodle_url('/cohort/index.php', array('contextid' => $context->id));
if (($searchquery = $currenturl->get_param('search'))) {
$viewurl->param('search', $searchquery);
}
if ($context->contextlevel == CONTEXT_SYSTEM) {
$tabs[] = new tabobject('view', new moodle_url($viewurl, array('showall' => 0)), get_string('systemcohorts', 'cohort'));
$tabs[] = new tabobject('viewall', new moodle_url($viewurl, array('showall' => 1)), get_string('allcohorts', 'cohort'));
if ($currenturl->get_param('showall')) {
$currenttab = 'viewall';
}
} else {
$tabs[] = new tabobject('view', $viewurl, get_string('cohorts', 'cohort'));
}
if (has_capability('moodle/cohort:manage', $context)) {
$addurl = new moodle_url('/cohort/edit.php', array('contextid' => $context->id));
$tabs[] = new tabobject('addcohort', $addurl, get_string('addcohort', 'cohort'));
if ($currenturl->get_path() === $addurl->get_path() && !$currenturl->param('id')) {
$currenttab = 'addcohort';
}
$uploadurl = new moodle_url('/cohort/upload.php', array('contextid' => $context->id));
$tabs[] = new tabobject('uploadcohorts', $uploadurl, get_string('uploadcohorts', 'cohort'));
if ($currenturl->get_path() === $uploadurl->get_path()) {
$currenttab = 'uploadcohorts';
}
}
if (count($tabs) > 1) {
return new tabtree($tabs, $currenttab);
}
return null;
}
/**
* Implements callback inplace_editable() allowing to edit values in-place
*
* @param string $itemtype
* @param int $itemid
* @param mixed $newvalue
* @return \core\output\inplace_editable
*/
function core_cohort_inplace_editable($itemtype, $itemid, $newvalue) {
if ($itemtype === 'cohortname') {
return \core_cohort\output\cohortname::update($itemid, $newvalue);
} else if ($itemtype === 'cohortidnumber') {
return \core_cohort\output\cohortidnumber::update($itemid, $newvalue);
}
}
/**
* Returns a list of valid themes which can be displayed in a selector.
*
* @return array as (string)themename => (string)get_string_theme
*/
function cohort_get_list_of_themes() {
$themes = array();
$allthemes = get_list_of_themes();
foreach ($allthemes as $key => $theme) {
if (empty($theme->hidefromselector)) {
$themes[$key] = get_string('pluginname', 'theme_'.$theme->name);
}
}
return $themes;
}
/**
* Returns custom fields data for provided cohorts.
*
* @param array $cohortids a list of cohort IDs to provide data for.
* @return \core_customfield\data_controller[]
*/
function cohort_get_custom_fields_data(array $cohortids): array {
$result = [];
if (!empty($cohortids)) {
$handler = core_cohort\customfield\cohort_handler::create();
$result = $handler->get_instances_data($cohortids, true);
}
return $result;
}
+167
View File
@@ -0,0 +1,167 @@
<?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/>.
/**
* Cohort UI related functions and classes.
*
* @package core_cohort
* @copyright 2012 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/cohort/lib.php');
require_once($CFG->dirroot . '/user/selector/lib.php');
/**
* Cohort assignment candidates
*/
class cohort_candidate_selector extends user_selector_base {
protected $cohortid;
public function __construct($name, $options) {
$this->cohortid = $options['cohortid'];
$options['includecustomfields'] = true;
parent::__construct($name, $options);
}
/**
* Candidate users
* @param string $search
* @return array
*/
public function find_users($search) {
global $DB;
// By default wherecondition retrieves all users except the deleted, not confirmed and guest.
list($wherecondition, $params) = $this->search_sql($search, 'u');
$params = array_merge($params, $this->userfieldsparams);
$params['cohortid'] = $this->cohortid;
$fields = 'SELECT u.id, ' . $this->userfieldsselects;
$countfields = 'SELECT COUNT(1)';
$sql = " FROM {user} u
LEFT JOIN {cohort_members} cm ON (cm.userid = u.id AND cm.cohortid = :cohortid)
$this->userfieldsjoin
WHERE cm.id IS NULL AND $wherecondition";
list($sort, $sortparams) = users_order_by_sql('u', $search, $this->accesscontext, $this->userfieldsmappings);
$order = ' ORDER BY ' . $sort;
if (!$this->is_validating()) {
$potentialmemberscount = $DB->count_records_sql($countfields . $sql, $params);
if ($potentialmemberscount > $this->maxusersperpage) {
return $this->too_many_results($search, $potentialmemberscount);
}
}
$availableusers = $DB->get_records_sql($fields . $sql . $order, array_merge($params, $sortparams));
if (empty($availableusers)) {
return array();
}
if ($search) {
$groupname = get_string('potusersmatching', 'cohort', $search);
} else {
$groupname = get_string('potusers', 'cohort');
}
return array($groupname => $availableusers);
}
protected function get_options() {
$options = parent::get_options();
$options['cohortid'] = $this->cohortid;
$options['file'] = 'cohort/locallib.php';
return $options;
}
}
/**
* Cohort assignment candidates
*/
class cohort_existing_selector extends user_selector_base {
protected $cohortid;
public function __construct($name, $options) {
$this->cohortid = $options['cohortid'];
$options['includecustomfields'] = true;
parent::__construct($name, $options);
}
/**
* Candidate users
* @param string $search
* @return array
*/
public function find_users($search) {
global $DB;
// By default wherecondition retrieves all users except the deleted, not confirmed and guest.
list($wherecondition, $params) = $this->search_sql($search, 'u');
$params = array_merge($params, $this->userfieldsparams);
$params['cohortid'] = $this->cohortid;
$fields = 'SELECT u.id, ' . $this->userfieldsselects;
$countfields = 'SELECT COUNT(1)';
$sql = " FROM {user} u
JOIN {cohort_members} cm ON (cm.userid = u.id AND cm.cohortid = :cohortid)
$this->userfieldsjoin
WHERE $wherecondition";
list($sort, $sortparams) = users_order_by_sql('u', $search, $this->accesscontext, $this->userfieldsmappings);
$order = ' ORDER BY ' . $sort;
if (!$this->is_validating()) {
$potentialmemberscount = $DB->count_records_sql($countfields . $sql, $params);
if ($potentialmemberscount > $this->maxusersperpage) {
return $this->too_many_results($search, $potentialmemberscount);
}
}
$availableusers = $DB->get_records_sql($fields . $sql . $order, array_merge($params, $sortparams));
if (empty($availableusers)) {
return array();
}
if ($search) {
$groupname = get_string('currentusersmatching', 'cohort', $search);
} else {
$groupname = get_string('currentusers', 'cohort');
}
return array($groupname => $availableusers);
}
protected function get_options() {
$options = parent::get_options();
$options['cohortid'] = $this->cohortid;
$options['file'] = 'cohort/locallib.php';
return $options;
}
}
@@ -0,0 +1,126 @@
@core @core_cohort @enrol_cohort
Feature: Access visible and hidden cohorts
In order to enrol users from cohorts
As an manager or teacher
I need to be able to view the list of cohorts defined above the course
Background:
Given the following "categories" exist:
| name | category | idnumber |
| Cat 1 | 0 | CAT1 |
| Cat 2 | 0 | CAT2 |
And the following "cohorts" exist:
| name | idnumber | visible |
| System cohort | CV0 | 1 |
| System hidden cohort | CH0 | 0 |
| System empty cohort | CVE0 | 1 |
And the following "cohorts" exist:
| name | idnumber | contextlevel | reference | visible |
| Cohort in category 1 | CV1 | Category | CAT1 | 1 |
| Cohort in category 2 | CV2 | Category | CAT2 | 1 |
| Cohort hidden in category 1 | CH1 | Category | CAT1 | 0 |
| Cohort empty in category 1 | CVE1 | Category | CAT1 | 1 |
Given the following "users" exist:
| username | firstname | lastname | email |
| user1 | First | User | first@example.com |
| user2 | Second | User | second@example.com |
| student | Sam | User | student@example.com |
| teacher | Terry | User | teacher@example.com |
And the following "cohort members" exist:
| user | cohort |
| student | CV0 |
| student | CV1 |
| student | CV2 |
| student | CH0 |
| student | CH1 |
And the following "role assigns" exist:
| user | role | contextlevel | reference |
| user1 | manager | System | |
| user2 | manager | Category | CAT1 |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | CAT1 |
And the following "course enrolments" exist:
| user | course | role |
| teacher | C1 | editingteacher |
@javascript @skip_chrome_zerosize
Scenario: Teacher can see visible cohorts defined in the above contexts
When I log in as "teacher"
And I am on the "Course 1" "enrolment methods" page
And I select "Cohort sync" from the "Add method" singleselect
And I open the autocomplete suggestions list
Then "Cohort in category 1" "autocomplete_suggestions" should exist
And "System cohort" "autocomplete_suggestions" should exist
And "Cohort hidden in category 1" "autocomplete_suggestions" should not exist
And "System hidden cohort" "autocomplete_suggestions" should not exist
And "Cohort in category 2" "autocomplete_suggestions" should not exist
And "Cohort empty in category 1" "autocomplete_suggestions" should exist
And "System empty cohort" "autocomplete_suggestions" should exist
And I set the field "Cohort" to "System cohort"
And I press "Add method"
And I am on the "Course 1" "enrolled users" page
And I should see "student@example.com"
And I am on the "Course 1" "groups" page
And I press "Auto-create groups"
And the "Select members from cohort" select box should contain "Cohort in category 1"
And the "Select members from cohort" select box should contain "System cohort"
And the "Select members from cohort" select box should not contain "Cohort hidden in category 1"
And the "Select members from cohort" select box should not contain "System hidden cohort"
And the "Select members from cohort" select box should not contain "Cohort in category 2"
And the "Select members from cohort" select box should not contain "Cohort empty in category 1"
And the "Select members from cohort" select box should not contain "System empty cohort"
@javascript @skip_chrome_zerosize
Scenario: System manager can see all cohorts defined in the above contexts
When I log in as "user1"
And I am on the "Course 1" "enrolment methods" page
And I select "Cohort sync" from the "Add method" singleselect
And I open the autocomplete suggestions list
Then "Cohort in category 1" "autocomplete_suggestions" should exist
And "System cohort" "autocomplete_suggestions" should exist
And "Cohort hidden in category 1" "autocomplete_suggestions" should exist
And "System hidden cohort" "autocomplete_suggestions" should exist
And "Cohort in category 2" "autocomplete_suggestions" should not exist
And "Cohort empty in category 1" "autocomplete_suggestions" should exist
And "System empty cohort" "autocomplete_suggestions" should exist
And I set the field "Cohort" to "System cohort"
And I press "Add method"
And I am on the "Course 1" "enrolled users" page
And I should see "student@example.com"
And I am on the "Course 1" "groups" page
And I press "Auto-create groups"
And the "Select members from cohort" select box should contain "Cohort in category 1"
And the "Select members from cohort" select box should contain "System cohort"
And the "Select members from cohort" select box should contain "Cohort hidden in category 1"
And the "Select members from cohort" select box should contain "System hidden cohort"
And the "Select members from cohort" select box should not contain "Cohort in category 2"
And the "Select members from cohort" select box should not contain "Cohort empty in category 1"
And the "Select members from cohort" select box should not contain "System empty cohort"
@javascript @skip_chrome_zerosize
Scenario: Category manager can see all cohorts defined in his category and visible cohorts defined above
When I log in as "user2"
And I am on the "Course 1" "enrolment methods" page
And I select "Cohort sync" from the "Add method" singleselect
And I open the autocomplete suggestions list
Then "Cohort in category 1" "autocomplete_suggestions" should exist
And "System cohort" "autocomplete_suggestions" should exist
And "Cohort hidden in category 1" "autocomplete_suggestions" should exist
And "System hidden cohort" "autocomplete_suggestions" should not exist
And "Cohort in category 2" "autocomplete_suggestions" should not exist
And "Cohort empty in category 1" "autocomplete_suggestions" should exist
And "System empty cohort" "autocomplete_suggestions" should exist
And I set the field "Cohort" to "System cohort"
And I press "Add method"
And I am on the "Course 1" "enrolled users" page
And I should see "student@example.com"
And I am on the "Course 1" "groups" page
And I press "Auto-create groups"
And the "Select members from cohort" select box should contain "Cohort in category 1"
And the "Select members from cohort" select box should contain "System cohort"
And the "Select members from cohort" select box should contain "Cohort hidden in category 1"
And the "Select members from cohort" select box should not contain "System hidden cohort"
And the "Select members from cohort" select box should not contain "Cohort in category 2"
And the "Select members from cohort" select box should not contain "Cohort empty in category 1"
And the "Select members from cohort" select box should not contain "System empty cohort"
+128
View File
@@ -0,0 +1,128 @@
@core @core_cohort
Feature: Add cohorts of users
In order to create site-wide groups
As an admin
I need to create cohorts and add users on them
Background:
Given the following "custom profile fields" exist:
| datatype | shortname | name |
| text | fruit | Fruit |
And the following "users" exist:
| username | firstname | lastname | email | profile_field_fruit |
| user1 | First | User | first@example.com | Apple |
| user2 | Second | User | second@example.com | Banana |
| user3 | Third | User | third@example.com | Apple |
| user4 | Forth | User | forth@example.com | Pear |
And the following "cohort" exists:
| name | Test cohort name |
| idnumber | 333 |
| description | Test cohort description |
And I log in as "admin"
And I navigate to "Users > Accounts > Cohorts" in site administration
@javascript
Scenario: Add a cohort
When I follow "Add new cohort"
And I set the following fields to these values:
| Name | My new cohort |
| Context | System |
| Cohort ID | mynewcohort |
| Description | My new cohort is cool |
And I press "Save changes"
Then the following should exist in the "generaltable" table:
| Name | Cohort ID | Description |
| My new cohort | mynewcohort | My new cohort is cool |
And I should see "Test cohort name"
And I should see "333"
And I should see "Test cohort description"
And I should see "Created manually"
@javascript
Scenario: Add users to a cohort selecting them from the system users list
When I add "First User (first@example.com)" user to "333" cohort members
And I add "Second User (second@example.com)" user to "333" cohort members
Then the following should exist in the "reportbuilder-table" table:
| Name | Cohort size |
| Test cohort name | 2 |
And I press "Assign" action in the "Test cohort name" report row
And the "Current users" select box should contain "First User (first@example.com)"
And the "Current users" select box should contain "Second User (second@example.com)"
And the "Current users" select box should not contain "Forth User (forth@example.com)"
@javascript
Scenario: Add user to cohort using custom user field search
Given the following config values are set as admin:
| showuseridentity | email,profile_field_fruit |
When I press "Assign" action in the "Test cohort name" report row
And I set the field "addselect_searchtext" to "Apple"
And I wait "1" seconds
Then the "Potential users" select box should contain "First User (first@example.com\, Apple)"
And the "Potential users" select box should not contain "Second User (second@example.com\, Banana)"
And the "Potential users" select box should not contain "Forth User (forth@example.com\, Pear)"
And I set the field "Potential users" to "Third User (third@example.com\, Apple)"
And I press "Add"
And the "Current users" select box should contain "Third User (third@example.com\, Apple)"
@javascript
Scenario: Remove user from cohort using custom user field search
Given the following config values are set as admin:
| showuseridentity | email,profile_field_fruit |
And the following "cohort members" exist:
| cohort | user |
| 333 | user1 |
| 333 | user2 |
When I press "Assign" action in the "Test cohort name" report row
And I set the field "removeselect_searchtext" to "Apple"
And I wait "1" seconds
Then the "Current users" select box should not contain "Second User (second@example.com\, Banana)"
And I set the field "Current users" to "First User (first@example.com\, Apple)"
And I press "Remove"
And the "Potential users" select box should contain "First User (first@example.com\, Apple)"
@javascript
Scenario: Add users to a cohort using a bulk user action
When I navigate to "Users > Accounts > Bulk user actions" in site administration
And I set the field "Available" to "Third User"
And I press "Add to selection"
And I set the field "Available" to "Forth User"
And I press "Add to selection"
And I set the field "id_action" to "Add to cohort"
And I press "Go"
And I set the field "Cohort" to "Test cohort name [333]"
And I press "Add to cohort"
And I navigate to "Users > Accounts > Cohorts" in site administration
Then the following should exist in the "reportbuilder-table" table:
| Name | Cohort size |
| Test cohort name | 2 |
And I press "Assign" action in the "Test cohort name" report row
And the "Current users" select box should contain "Third User (third@example.com)"
And the "Current users" select box should contain "Forth User (forth@example.com)"
And the "Current users" select box should not contain "First User (first@example.com)"
@javascript
Scenario: Add users to a cohort using a user list bulk action
When I navigate to "Users > Accounts > Browse list of users" in site administration
And I click on "Third User" "checkbox"
And I click on "Forth User" "checkbox"
And I set the field "Bulk user actions" to "Add to cohort"
And I set the field "Cohort" to "Test cohort name [333]"
And I press "Add to cohort"
And I should see "Browse list of users"
And I navigate to "Users > Accounts > Cohorts" in site administration
Then the following should exist in the "reportbuilder-table" table:
| Name | Cohort size |
| Test cohort name | 2 |
And I press "Assign" action in the "Test cohort name" report row
And the "Current users" select box should contain "Third User (third@example.com)"
And the "Current users" select box should contain "Forth User (forth@example.com)"
And the "Current users" select box should not contain "First User (first@example.com)"
@javascript
Scenario: Edit cohort name in-place
When I navigate to "Users > Accounts > Cohorts" in site administration
And I set the field "Edit cohort name" to "Students cohort"
Then I should not see "Test cohort name"
And I should see "Students cohort"
And I navigate to "Users > Accounts > Cohorts" in site administration
And I should see "Students cohort"
+73
View File
@@ -0,0 +1,73 @@
<?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/>.
/**
* Cohorts steps definitions.
*
* @package core_cohort
* @category test
* @copyright 2013 David Monllaó
* @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 cohort actions.
*
* @package core_cohort
* @category test
* @copyright 2013 David Monllaó
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class behat_cohort extends behat_base {
/**
* Adds the user to the specified cohort. The user should be specified like "Firstname Lastname (user@example.com)".
*
* @Given /^I add "(?P<user_fullname_string>(?:[^"]|\\")*)" user to "(?P<cohort_idnumber_string>(?:[^"]|\\")*)" cohort members$/
* @param string $user
* @param string $cohortidnumber
*/
public function i_add_user_to_cohort_members($user, $cohortidnumber) {
// If we are not in the cohorts management we should move there before anything else.
$cohortsurl = new moodle_url('/cohort/index.php');
if (strpos($this->getSession()->getCurrentUrl(), $cohortsurl->out(false)) !== 0) {
// With JS enabled we should expand a few tree nodes.
$parentnodes = get_string('users', 'admin') . ' > ' .
get_string('accounts', 'admin');
$this->execute("behat_general::i_am_on_homepage");
$this->execute("behat_navigation::i_navigate_to_in_site_administration",
$parentnodes . ' > ' . get_string('cohorts', 'cohort')
);
}
$this->execute('behat_reportbuilder::i_press_action_in_the_report_row',
[get_string('assign', 'cohort'), $this->escape($cohortidnumber)]);
$this->execute("behat_forms::i_set_the_field_to",
array(get_string('potusers', 'cohort'), $this->escape($user))
);
$this->execute("behat_forms::press_button", get_string('add'));
$this->execute("behat_forms::press_button", get_string('backtocohorts', 'cohort'));
}
}
+39
View File
@@ -0,0 +1,39 @@
@core @core_cohort @core_customfield @javascript
Feature: Add and use cohort custom fields
In order to store an extra information about cohorts
As an admin
I need to create cohort customs fields and be able to populate them on cohort creation
Background:
Given the following "custom field categories" exist:
| name | component | area | itemid |
| Category for test | core_cohort | cohort | 0 |
Scenario: Create a new cohort custom field and use the field for a new cohort
When I log in as "admin"
And I navigate to "Users > Accounts > Cohort custom fields" in site administration
And I click on "Add a new custom field" "link"
And I click on "Short text" "link"
And I set the following fields to these values:
| Name | Test field |
| Short name | testfield |
And I click on "Save changes" "button" in the "Adding a new Short text" "dialogue"
Then the following should exist in the "generaltable" table:
| Custom field | Short name | Type |
| Test field | testfield | Short text |
And I navigate to "Users > Accounts > Cohorts" in site administration
And I follow "Add new cohort"
Then I should see "Category for test"
And I should see "Test field"
And I set the following fields to these values:
| Name | My new cohort |
| Context | System |
| Cohort ID | mynewcohort |
| Description | My new cohort description |
| Test field | Custom field text |
And I press "Save changes"
Then the following should exist in the "generaltable" table:
| Name | Cohort ID | Description |
| My new cohort | mynewcohort | My new cohort description |
And I press "Edit" action in the "My new cohort" report row
And the field "Test field" matches value "Custom field text"
@@ -0,0 +1,44 @@
@core @core_cohort @_file_upload
Feature: Upload users to a cohort
In order to quickly fill site-wide groups with users
As an admin
I need to upload a file with users data containing cohort assigns
@javascript @skip_chrome_zerosize
Scenario: Upload users and assign them to a course with cohort enrolment method enabled
Given the following "cohorts" exist:
| name | idnumber |
| Cohort 1 | ASD |
| Cohort 2 | DSA |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
| Course 2 | C2 | 0 |
And I log in as "admin"
And I add "Cohort sync" enrolment method in "Course 1" with:
| Cohort | Cohort 1 |
And I should see "Cohort sync (Cohort 1 - Student)"
And I add "Cohort sync" enrolment method in "Course 2" with:
| Cohort | Cohort 2 |
And I should see "Cohort sync (Cohort 2 - Student)"
When I navigate to "Users > Accounts > Upload users" in site administration
And I upload "lib/tests/fixtures/upload_users_cohorts.csv" file to "File" filemanager
And I press "Upload users"
And I press "Upload users"
And I press "Continue"
And I navigate to "Users > Accounts > Cohorts" in site administration
And I press "Assign" action in the "Cohort 1" report row
Then the "Current users" select box should contain "Tom Jones (tomjones@example.com)"
And the "Current users" select box should contain "Bob Jones (bobjones@example.com)"
And I press "Back to cohorts"
And I press "Assign" action in the "Cohort 2" report row
And the "Current users" select box should contain "Mary Smith (marysmith@example.com)"
And the "Current users" select box should contain "Alice Smith (alicesmith@example.com)"
And I am on the "Course 1" "enrolled users" page
And I should see "Tom Jones"
And I should see "Bob Jones"
And I should not see "Mary Smith"
And I am on the "Course 2" "enrolled users" page
And I should see "Mary Smith"
And I should see "Alice Smith"
And I should not see "Tom Jones"
+195
View File
@@ -0,0 +1,195 @@
@core @core_cohort @_file_upload
Feature: A privileged user can create cohorts using a CSV file
In order to create cohorts using a CSV file
As an admin
I need to be able to upload a CSV file and navigate through the upload process
Background:
Given the following "categories" exist:
| name | category | idnumber |
| Cat 1 | 0 | CAT1 |
| Cat 2 | 0 | CAT2 |
| Cat 3 | CAT1 | CAT3 |
@javascript
Scenario: Upload cohorts with default System context as admin
When I log in as "admin"
And I navigate to "Users > Accounts >Cohorts" in site administration
And I follow "Upload cohorts"
And I upload "cohort/tests/fixtures/uploadcohorts1.csv" file to "File" filemanager
And I click on "Preview" "button"
Then the following should exist in the "previewuploadedcohorts" table:
| name | idnumber | description | Context | visible | Status |
| cohort name 1 | cohortid1 | first description | System | 1 | |
| cohort name 2 | cohortid2 | | System | 1 | |
| cohort name 3 | cohortid3 | | Category 1 | 0 | |
| cohort name 4 | cohortid4 | | Cat 1 | 1 | |
| cohort name 5 | cohortid5 | | Cat 2 | 0 | |
| cohort name 6 | cohortid6 | | Cat 3 | 1 | |
And I press "Upload cohorts"
And I should see "Uploaded 6 cohorts"
And I press "Continue"
And the following should exist in the "reportbuilder-table" table:
| Name | Cohort ID | Description | Cohort size | Source |
| cohort name 1 | cohortid1 | first description | 0 | Created manually |
| cohort name 2 | cohortid2 | | 0 | Created manually |
And I follow "All cohorts"
And the following should exist in the "reportbuilder-table" table:
| Category | Name | Cohort ID | Description | Cohort size | Source |
| System | cohort name 1 | cohortid1 | first description | 0 | Created manually |
| System | cohort name 2 | cohortid2 | | 0 | Created manually |
| Category 1 | cohort name 3 | cohortid3 | | 0 | Created manually |
| Cat 1 | cohort name 4 | cohortid4 | | 0 | Created manually |
| Cat 2 | cohort name 5 | cohortid5 | | 0 | Created manually |
| Cat 3 | cohort name 6 | cohortid6 | | 0 | Created manually |
And ".text-muted" "css_element" should not exist in the "cohort name 1" "table_row"
And ".text-muted" "css_element" should not exist in the "cohort name 2" "table_row"
And ".text-muted" "css_element" should exist in the "cohort name 3" "table_row"
And the "class" attribute of "cohort name 3" "table_row" should contain "text-muted"
And ".text-muted" "css_element" should not exist in the "cohort name 4" "table_row"
And the "class" attribute of "cohort name 5" "table_row" should contain "text-muted"
And ".text-muted" "css_element" should not exist in the "cohort name 6" "table_row"
@javascript @_file_upload
Scenario: Upload cohorts with default category context as admin
When I log in as "admin"
And I navigate to "Users > Accounts >Cohorts" in site administration
And I follow "Upload cohorts"
And I upload "cohort/tests/fixtures/uploadcohorts1.csv" file to "File" filemanager
And I set the field "Default context" to "Cat 1 / Cat 3"
And I click on "Preview" "button"
Then the following should exist in the "previewuploadedcohorts" table:
| name | idnumber | description | Context | Status |
| cohort name 1 | cohortid1 | first description | Cat 3 | |
| cohort name 2 | cohortid2 | | Cat 3 | |
| cohort name 3 | cohortid3 | | Category 1 | |
| cohort name 4 | cohortid4 | | Cat 1 | |
| cohort name 5 | cohortid5 | | Cat 2 | |
| cohort name 6 | cohortid6 | | Cat 3 | |
And I press "Upload cohorts"
And I should see "Uploaded 6 cohorts"
And I press "Continue"
And I should see "Cat 3"
And I navigate to "Users > Accounts >Cohorts" in site administration
And I follow "All cohorts"
And the following should exist in the "reportbuilder-table" table:
| Category | Name | Cohort ID | Description | Cohort size | Source |
| Cat 3 | cohort name 1 | cohortid1 | first description | 0 | Created manually |
| Cat 3 | cohort name 2 | cohortid2 | | 0 | Created manually |
| Category 1 | cohort name 3 | cohortid3 | | 0 | Created manually |
| Cat 1 | cohort name 4 | cohortid4 | | 0 | Created manually |
| Cat 2 | cohort name 5 | cohortid5 | | 0 | Created manually |
| Cat 3 | cohort name 6 | cohortid6 | | 0 | Created manually |
@javascript @_file_upload
Scenario: Upload cohorts with default category context as manager
Given the following "users" exist:
| username | firstname | lastname | email |
| user1 | User | 1 | user1@example.com |
And the following "role assigns" exist:
| user | role | contextlevel | reference |
| user1 | manager | Category | CAT1 |
When I log in as "user1"
And I am on course index
And I follow "Cat 1"
And I navigate to "Cohorts" in current page administration
And I follow "Upload cohorts"
And I upload "cohort/tests/fixtures/uploadcohorts1.csv" file to "File" filemanager
And I click on "Preview" "button"
Then the following should exist in the "previewuploadedcohorts" table:
| name | idnumber | description | Context | Status |
| cohort name 1 | cohortid1 | first description | Cat 1 | |
| cohort name 2 | cohortid2 | | Cat 1 | |
| cohort name 3 | cohortid3 | | Cat 1 | Category Category 1 not found or you don't have permission to create a cohort there. The default context will be used. |
| cohort name 4 | cohortid4 | | Cat 1 | |
| cohort name 5 | cohortid5 | | Cat 1 | Category CAT2 not found or you don't have permission to create a cohort there. The default context will be used. |
| cohort name 6 | cohortid6 | | Cat 3 | |
And I press "Upload cohorts"
And I should see "Uploaded 6 cohorts"
@javascript @_file_upload
Scenario: Upload cohorts with conflicting id number
Given the following "cohorts" exist:
| name | idnumber |
| Cohort | cohortid2 |
When I log in as "admin"
And I navigate to "Users > Accounts >Cohorts" in site administration
And I follow "Upload cohorts"
And I upload "cohort/tests/fixtures/uploadcohorts1.csv" file to "File" filemanager
And I click on "Preview" "button"
Then I should see "Errors were found in CSV data. See details below."
Then the following should exist in the "previewuploadedcohorts" table:
| name | idnumber | description | Context | Status |
| cohort name 1 | cohortid1 | first description | System | |
| cohort name 2 | cohortid2 | | System | Cohort with the same ID number already exists |
| cohort name 3 | cohortid3 | | Category 1 | |
| cohort name 4 | cohortid4 | | Cat 1 | |
| cohort name 5 | cohortid5 | | Cat 2 | |
| cohort name 6 | cohortid6 | | Cat 3 | |
And "Upload cohorts" "button" should not exist
@javascript @_file_upload
Scenario: Upload cohorts with different ways of specifying context
When I log in as "admin"
And I navigate to "Users > Accounts >Cohorts" in site administration
And I follow "Upload cohorts"
And I upload "cohort/tests/fixtures/uploadcohorts2.csv" file to "File" filemanager
And I click on "Preview" "button"
Then the following should exist in the "previewuploadedcohorts" table:
| name | idnumber | description | Context | Status |
| Specify category as name | cohortid1 | | Category 1 | |
| Specify category as idnumber | cohortid2 | | Cat 1 | |
| Specify category as id | cohortid3 | | Category 1 | |
| Specify category as path | cohortid4 | | Cat 3 | |
| Specify category_id | cohortid5 | | Category 1 | |
| Specify category_idnumber | cohortid6 | | Cat 1 | |
| Specify category_path | cohortid7 | | Cat 3 | |
And I should not see "not found or you"
And I press "Upload cohorts"
And I should see "Uploaded 7 cohorts"
And I press "Continue"
And I follow "Upload cohorts"
And I upload "cohort/tests/fixtures/uploadcohorts3.csv" file to "File" filemanager
And I click on "Preview" "button"
And the following should exist in the "previewuploadedcohorts" table:
| name | idnumber | description | Context | Status |
| Specify context as id (system) | cohortid8 | | System | |
| Specify context as name (system) | cohortid9 | | System | |
| Specify context as category name only | cohortid10 | | Cat 1 | |
| Specify context as category path | cohortid12 | | Cat 3 | |
| Specify context as category idnumber | cohortid13 | | Cat 2 | |
And I should not see "not found or you"
And I press "Upload cohorts"
And I should see "Uploaded 5 cohorts"
@javascript @_file_upload
Scenario: Upload cohorts with theme
When I log in as "admin"
And I navigate to "Users > Accounts >Cohorts" in site administration
And I follow "Upload cohorts"
And I upload "cohort/tests/fixtures/uploadcohorts4.csv" file to "File" filemanager
And I click on "Preview" "button"
Then the following should exist in the "previewuploadedcohorts" table:
| name | idnumber | description | Context | visible | theme | Status |
| cohort name 1 | cohortid1 | first description | System | 1 | boost | |
| cohort name 2 | cohortid2 | | System | 1 | | |
| cohort name 3 | cohortid3 | | Category 1 | 0 | boost | |
| cohort name 4 | cohortid4 | | Cat 1 | 1 | classic | |
| cohort name 5 | cohortid5 | | Cat 2 | 0 | | |
| cohort name 6 | cohortid6 | | Cat 3 | 1 | classic | |
And I press "Upload cohorts"
And I should see "Uploaded 6 cohorts"
And I press "Continue"
And the following should exist in the "reportbuilder-table" table:
| Name | Cohort ID | Description | Cohort size | Source |
| cohort name 1 | cohortid1 | first description | 0 | Created manually |
| cohort name 2 | cohortid2 | | 0 | Created manually |
And I follow "All cohorts"
And the following should exist in the "reportbuilder-table" table:
| Category | Name | Cohort ID | Description | Cohort size | Source |
| System | cohort name 1 | cohortid1 | first description | 0 | Created manually |
| System | cohort name 2 | cohortid2 | | 0 | Created manually |
| Category 1 | cohort name 3 | cohortid3 | | 0 | Created manually |
| Cat 1 | cohort name 4 | cohortid4 | | 0 | Created manually |
| Cat 2 | cohort name 5 | cohortid5 | | 0 | Created manually |
| Cat 3 | cohort name 6 | cohortid6 | | 0 | Created manually |
+83
View File
@@ -0,0 +1,83 @@
@core @core_cohort
Feature: View cohort list
In order to operate with cohorts
As an admin or manager
I need to be able to view the list of cohorts in the system
Background:
Given the following "categories" exist:
| name | category | idnumber |
| Cat 1 | 0 | CAT1 |
| Cat 2 | 0 | CAT2 |
| Cat 3 | CAT1 | CAT3 |
And the following "cohorts" exist:
| name | idnumber |
| System cohort | CH0 |
And the following "cohorts" exist:
| name | idnumber | contextlevel | reference |
| Cohort in category 1 | CH1 | Category | CAT1 |
| Cohort in category 2 | CH2 | Category | CAT2 |
| Cohort in category 3 | CH3 | Category | CAT3 |
Given the following "users" exist:
| username | firstname | lastname | email |
| user1 | First | User | first@example.com |
| user2 | Second | User | second@example.com |
And the following "role assigns" exist:
| user | role | contextlevel | reference |
| user1 | manager | System | |
| user2 | manager | Category | CAT1 |
Scenario: Admin can see system cohorts and all cohorts
When I log in as "admin"
And I navigate to "Users > Accounts >Cohorts" in site administration
Then I should see "System cohort"
And I should not see "Cohort in category"
And I follow "All cohorts"
And I should see "System cohort"
And I should see "Cohort in category 1"
And I should see "Cohort in category 2"
And I should see "Cohort in category 3"
And I log out
Scenario: Manager can see system cohorts and all cohorts
When I log in as "user1"
And I navigate to "Users > Accounts >Cohorts" in site administration
Then I should see "System cohort"
And I should not see "Cohort in category"
And I follow "All cohorts"
And I should see "System cohort"
And I should see "Cohort in category 1"
And I should see "Cohort in category 2"
And I should see "Cohort in category 3"
And I log out
Scenario: Manager in category can see cohorts in the category
When I log in as "user2"
And I am on course index
And I follow "Cat 1"
And I follow "Cohorts"
And I should not see "All cohorts"
And I should not see "System cohort"
And I should see "Cohort in category 1"
And I should not see "Cohort in category 2"
And I should not see "Cohort in category 3"
And I log out
@javascript
Scenario: Cohorts list can be filtered
When I log in as "admin"
And I navigate to "Users > Accounts > Cohorts" in site administration
And I follow "All cohorts"
And I click on "Filters" "button"
And I set the following fields in the "Name" "core_reportbuilder > Filter" to these values:
| Name operator | Contains |
| Name value | category 1 |
And I click on "Apply" "button" in the "[data-region='report-filters']" "css_element"
Then the following should exist in the "reportbuilder-table" table:
| Category | Name |
| Cat 1 | Cohort in category 1 |
And the following should not exist in the "reportbuilder-table" table:
| Category | Name |
| Cat 2 | Cohort in category 2 |
| Cat 3 | Cohort in category 3 |
| System | System cohort |
@@ -0,0 +1,182 @@
<?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/>.
namespace core_cohort\customfield;
use advanced_testcase;
use context_system;
use context_coursecat;
use moodle_url;
use core_customfield\field_controller;
/**
* Unit tests for cohort custom field handler.
*
* @package core_cohort
* @covers \core_cohort\customfield\cohort_handler
* @copyright 2022 Dmitrii Metelkin <dmitriim@catalys-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cohort_handler_test extends advanced_testcase {
/**
* Test custom field handler.
* @var \core_customfield\handler
*/
protected $handler;
/**
* Setup.
*/
public function setUp(): void {
$this->handler = cohort_handler::create();
}
/**
* Create Cohort custom field for testing.
*
* @return field_controller
*/
protected function create_cohort_custom_field(): field_controller {
$fieldcategory = self::getDataGenerator()->create_custom_field_category([
'component' => 'core_cohort',
'area' => 'cohort',
]);
return self::getDataGenerator()->create_custom_field([
'shortname' => 'testfield1',
'type' => 'text',
'categoryid' => $fieldcategory->get('id'),
]);
}
/**
* Test configuration context.
*/
public function test_get_configuration_context(): void {
$this->assertInstanceOf(context_system::class, $this->handler->get_configuration_context());
}
/**
* Test getting config URL.
*/
public function test_get_configuration_url(): void {
$this->assertInstanceOf(moodle_url::class, $this->handler->get_configuration_url());
$this->assertEquals('/cohort/customfield.php', $this->handler->get_configuration_url()->out_as_local_url());
}
/**
* Test can configure check.
*/
public function test_can_configure(): void {
$this->resetAfterTest();
$user = self::getDataGenerator()->create_user();
self::setUser($user);
$this->assertFalse($this->handler->can_configure());
$roleid = self::getDataGenerator()->create_role();
assign_capability('moodle/cohort:configurecustomfields', CAP_ALLOW, $roleid, context_system::instance()->id, true);
role_assign($roleid, $user->id, context_system::instance()->id);
$this->assertTrue($this->handler->can_configure());
}
/**
* Test getting instance context.
*/
public function test_get_instance_context(): void {
$this->resetAfterTest();
$category = self::getDataGenerator()->create_category();
$catcontext = context_coursecat::instance($category->id);
$systemcontext = context_system::instance();
$cohortsystem = self::getDataGenerator()->create_cohort();
$cohortcategory = self::getDataGenerator()->create_cohort(['contextid' => $catcontext->id]);
$this->assertInstanceOf(context_system::class, $this->handler->get_instance_context($cohortsystem->id));
$this->assertSame($systemcontext, $this->handler->get_instance_context($cohortsystem->id));
$this->assertInstanceOf(context_coursecat::class, $this->handler->get_instance_context($cohortcategory->id));
$this->assertSame($catcontext, $this->handler->get_instance_context($cohortcategory->id));
}
/**
* Test can edit functionality.
*/
public function test_can_edit(): void {
$this->resetAfterTest();
$roleid = self::getDataGenerator()->create_role();
assign_capability('moodle/cohort:manage', CAP_ALLOW, $roleid, context_system::instance()->id, true);
$field = $this->create_cohort_custom_field();
$user = self::getDataGenerator()->create_user();
self::setUser($user);
$this->assertFalse($this->handler->can_edit($field, 0));
role_assign($roleid, $user->id, context_system::instance()->id);
$this->assertTrue($this->handler->can_edit($field, 0));
}
/**
* Test can view functionality.
*/
public function test_can_view(): void {
$this->resetAfterTest();
$manageroleid = self::getDataGenerator()->create_role();
assign_capability('moodle/cohort:manage', CAP_ALLOW, $manageroleid, context_system::instance()->id, true);
$viewroleid = self::getDataGenerator()->create_role();
assign_capability('moodle/cohort:view', CAP_ALLOW, $viewroleid, context_system::instance()->id, true);
$viewandmanageroleid = self::getDataGenerator()->create_role();
assign_capability('moodle/cohort:manage', CAP_ALLOW, $viewandmanageroleid, context_system::instance()->id, true);
assign_capability('moodle/cohort:view', CAP_ALLOW, $viewandmanageroleid, context_system::instance()->id, true);
$field = $this->create_cohort_custom_field();
$cohort = self::getDataGenerator()->create_cohort();
$user1 = self::getDataGenerator()->create_user();
$user2 = self::getDataGenerator()->create_user();
$user3 = self::getDataGenerator()->create_user();
self::setUser($user1);
$this->assertFalse($this->handler->can_view($field, $cohort->id));
self::setUser($user2);
$this->assertFalse($this->handler->can_view($field, $cohort->id));
self::setUser($user3);
$this->assertFalse($this->handler->can_view($field, $cohort->id));
role_assign($manageroleid, $user1->id, context_system::instance()->id);
role_assign($viewroleid, $user2->id, context_system::instance()->id);
role_assign($viewandmanageroleid, $user3->id, context_system::instance()->id);
self::setUser($user1);
$this->assertTrue($this->handler->can_view($field, $cohort->id));
self::setUser($user2);
$this->assertTrue($this->handler->can_view($field, $cohort->id));
self::setUser($user3);
$this->assertTrue($this->handler->can_view($field, $cohort->id));
}
}
+775
View File
@@ -0,0 +1,775 @@
<?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/>.
namespace core_cohort;
use core_cohort_external;
use core_external\external_api;
use externallib_advanced_testcase;
use core_cohort\customfield\cohort_handler;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/webservice/tests/helpers.php');
require_once($CFG->dirroot . '/cohort/externallib.php');
/**
* External cohort API
*
* @package core_cohort
* @category external
* @copyright MediaTouch 2000 srl
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class externallib_test extends externallib_advanced_testcase {
/**
* Create cohort custom fields for testing.
*/
protected function create_custom_fields(): void {
$fieldcategory = self::getDataGenerator()->create_custom_field_category([
'component' => 'core_cohort',
'area' => 'cohort',
'name' => 'Other fields',
]);
self::getDataGenerator()->create_custom_field([
'shortname' => 'testfield1',
'name' => 'Custom field',
'type' => 'text',
'categoryid' => $fieldcategory->get('id'),
]);
self::getDataGenerator()->create_custom_field([
'shortname' => 'testfield2',
'name' => 'Custom field',
'type' => 'text',
'categoryid' => $fieldcategory->get('id'),
]);
}
/**
* Test create_cohorts
*/
public function test_create_cohorts(): void {
global $DB;
$this->resetAfterTest(true);
set_config('allowcohortthemes', 1);
$contextid = \context_system::instance()->id;
$category = $this->getDataGenerator()->create_category();
// Custom fields.
$this->create_custom_fields();
$cohort1 = array(
'categorytype' => array('type' => 'id', 'value' => $category->id),
'name' => 'cohort test 1',
'idnumber' => 'cohorttest1',
'description' => 'This is a description for cohorttest1',
'theme' => 'classic'
);
$cohort2 = array(
'categorytype' => array('type' => 'system', 'value' => ''),
'name' => 'cohort test 2',
'idnumber' => 'cohorttest2',
'description' => 'This is a description for cohorttest2',
'visible' => 0
);
$cohort3 = array(
'categorytype' => array('type' => 'id', 'value' => $category->id),
'name' => 'cohort test 3',
'idnumber' => 'cohorttest3',
'description' => 'This is a description for cohorttest3'
);
$roleid = $this->assignUserCapability('moodle/cohort:manage', $contextid);
$cohort4 = array(
'categorytype' => array('type' => 'id', 'value' => $category->id),
'name' => 'cohort test 4',
'idnumber' => 'cohorttest4',
'description' => 'This is a description for cohorttest4',
'theme' => 'classic'
);
$cohort5 = array(
'categorytype' => array('type' => 'id', 'value' => $category->id),
'name' => 'cohort test 5 (with custom fields)',
'idnumber' => 'cohorttest5',
'description' => 'This is a description for cohorttest5',
'customfields' => array(
array(
'shortname' => 'testfield1',
'value' => 'Test value 1',
),
array(
'shortname' => 'testfield2',
'value' => 'Test value 2',
),
),
);
// Call the external function.
$this->setCurrentTimeStart();
$createdcohorts = core_cohort_external::create_cohorts(array($cohort1, $cohort2));
$createdcohorts = external_api::clean_returnvalue(core_cohort_external::create_cohorts_returns(), $createdcohorts);
// Check we retrieve the good total number of created cohorts + no error on capability.
$this->assertEquals(2, count($createdcohorts));
foreach ($createdcohorts as $createdcohort) {
$dbcohort = $DB->get_record('cohort', array('id' => $createdcohort['id']));
if ($createdcohort['idnumber'] == $cohort1['idnumber']) {
$conid = $DB->get_field('context', 'id', array('instanceid' => $cohort1['categorytype']['value'],
'contextlevel' => CONTEXT_COURSECAT));
$this->assertEquals($dbcohort->contextid, $conid);
$this->assertEquals($dbcohort->name, $cohort1['name']);
$this->assertEquals($dbcohort->description, $cohort1['description']);
$this->assertEquals($dbcohort->visible, 1); // Field was not specified, ensure it is visible by default.
// As $CFG->allowcohortthemes is enabled, theme must be initialised.
$this->assertEquals($dbcohort->theme, $cohort1['theme']);
} else if ($createdcohort['idnumber'] == $cohort2['idnumber']) {
$this->assertEquals($dbcohort->contextid, \context_system::instance()->id);
$this->assertEquals($dbcohort->name, $cohort2['name']);
$this->assertEquals($dbcohort->description, $cohort2['description']);
$this->assertEquals($dbcohort->visible, $cohort2['visible']);
// Although $CFG->allowcohortthemes is enabled, no theme is defined for this cohort.
$this->assertEquals($dbcohort->theme, '');
} else {
$this->fail('Unrecognised cohort found');
}
$this->assertTimeCurrent($dbcohort->timecreated);
$this->assertTimeCurrent($dbcohort->timemodified);
}
$createdcohorts = core_cohort_external::create_cohorts(array($cohort5));
$createdcohorts = external_api::clean_returnvalue(core_cohort_external::create_cohorts_returns(), $createdcohorts);
$this->assertCount(1, $createdcohorts);
$createdcohort = reset($createdcohorts);
$dbcohort = $DB->get_record('cohort', array('id' => $createdcohort['id']));
$this->assertEquals($cohort5['name'], $dbcohort->name);
$this->assertEquals($cohort5['description'], $dbcohort->description);
$this->assertEquals(1, $dbcohort->visible);
$this->assertEquals('', $dbcohort->theme);
$data = cohort_handler::create()->export_instance_data_object($createdcohort['id'], true);
$this->assertEquals('Test value 1', $data->testfield1);
$this->assertEquals('Test value 2', $data->testfield2);
// Call when $CFG->allowcohortthemes is disabled.
set_config('allowcohortthemes', 0);
$createdcohorts = core_cohort_external::create_cohorts(array($cohort4));
$createdcohorts = external_api::clean_returnvalue(core_cohort_external::create_cohorts_returns(), $createdcohorts);
foreach ($createdcohorts as $createdcohort) {
$dbcohort = $DB->get_record('cohort', array('id' => $createdcohort['id']));
if ($createdcohort['idnumber'] == $cohort4['idnumber']) {
$conid = $DB->get_field('context', 'id', array('instanceid' => $cohort4['categorytype']['value'],
'contextlevel' => CONTEXT_COURSECAT));
$this->assertEquals($dbcohort->contextid, $conid);
$this->assertEquals($dbcohort->name, $cohort4['name']);
$this->assertEquals($dbcohort->description, $cohort4['description']);
$this->assertEquals($dbcohort->visible, 1); // Field was not specified, ensure it is visible by default.
$this->assertEquals($dbcohort->theme, ''); // As $CFG->allowcohortthemes is disabled, theme must be empty.
}
}
// Call without required capability.
$this->unassignUserCapability('moodle/cohort:manage', $contextid, $roleid);
$this->expectException(\required_capability_exception::class);
$createdcohorts = core_cohort_external::create_cohorts(array($cohort3));
}
/**
* Test delete_cohorts
*/
public function test_delete_cohorts(): void {
global $USER, $CFG, $DB;
$this->resetAfterTest(true);
$cohort1 = self::getDataGenerator()->create_cohort();
$cohort2 = self::getDataGenerator()->create_cohort();
// Check the cohorts were correctly created.
$this->assertEquals(2, $DB->count_records_select('cohort', ' (id = :cohortid1 OR id = :cohortid2)',
array('cohortid1' => $cohort1->id, 'cohortid2' => $cohort2->id)));
$contextid = $cohort1->contextid;
$roleid = $this->assignUserCapability('moodle/cohort:manage', $contextid);
// Call the external function.
core_cohort_external::delete_cohorts(array($cohort1->id, $cohort2->id));
// Check we retrieve no cohorts + no error on capability.
$this->assertEquals(0, $DB->count_records_select('cohort', ' (id = :cohortid1 OR id = :cohortid2)',
array('cohortid1' => $cohort1->id, 'cohortid2' => $cohort2->id)));
// Call without required capability.
$cohort1 = self::getDataGenerator()->create_cohort();
$cohort2 = self::getDataGenerator()->create_cohort();
$this->unassignUserCapability('moodle/cohort:manage', $contextid, $roleid);
$this->expectException(\required_capability_exception::class);
core_cohort_external::delete_cohorts(array($cohort1->id, $cohort2->id));
}
/**
* Test get_cohorts
*/
public function test_get_cohorts(): void {
$this->resetAfterTest(true);
// Custom fields.
$this->create_custom_fields();
set_config('allowcohortthemes', 1);
$cohort1 = array(
'contextid' => 1,
'name' => 'cohortnametest1',
'idnumber' => 'idnumbertest1',
'description' => 'This is a description for cohort 1',
'theme' => 'classic',
'customfield_testfield1' => 'Test value 1',
'customfield_testfield2' => 'Test value 2',
);
// We need a site admin to be able to populate cohorts custom fields.
$this->setAdminUser();
$cohort1 = self::getDataGenerator()->create_cohort($cohort1);
$cohort2 = self::getDataGenerator()->create_cohort();
$context = \context_system::instance();
$roleid = $this->assignUserCapability('moodle/cohort:view', $context->id);
// Call the external function.
$returnedcohorts = core_cohort_external::get_cohorts(array(
$cohort1->id, $cohort2->id));
$returnedcohorts = external_api::clean_returnvalue(core_cohort_external::get_cohorts_returns(), $returnedcohorts);
// Check we retrieve the good total number of enrolled cohorts + no error on capability.
$this->assertEquals(2, count($returnedcohorts));
foreach ($returnedcohorts as $enrolledcohort) {
if ($enrolledcohort['idnumber'] == $cohort1->idnumber) {
$this->assertEquals($cohort1->name, $enrolledcohort['name']);
$this->assertEquals($cohort1->description, $enrolledcohort['description']);
$this->assertEquals($cohort1->visible, $enrolledcohort['visible']);
$this->assertEquals($cohort1->theme, $enrolledcohort['theme']);
$this->assertIsArray($enrolledcohort['customfields']);
$this->assertCount(2, $enrolledcohort['customfields']);
$actual = [];
foreach ($enrolledcohort['customfields'] as $customfield) {
$this->assertArrayHasKey('name', $customfield);
$this->assertArrayHasKey('shortname', $customfield);
$this->assertArrayHasKey('type', $customfield);
$this->assertArrayHasKey('valueraw', $customfield);
$this->assertArrayHasKey('value', $customfield);
$actual[$customfield['shortname']] = $customfield;
}
$this->assertEquals('Test value 1', $actual['testfield1']['value']);
$this->assertEquals('Test value 2', $actual['testfield2']['value']);
}
}
// Check that a user with cohort:manage can see the cohort.
$this->unassignUserCapability('moodle/cohort:view', $context->id, $roleid);
$roleid = $this->assignUserCapability('moodle/cohort:manage', $context->id, $roleid);
// Call the external function.
$returnedcohorts = core_cohort_external::get_cohorts(array(
$cohort1->id, $cohort2->id));
$returnedcohorts = external_api::clean_returnvalue(core_cohort_external::get_cohorts_returns(), $returnedcohorts);
// Check we retrieve the good total number of enrolled cohorts + no error on capability.
$this->assertEquals(2, count($returnedcohorts));
// Check when allowcohortstheme is disabled, theme is not returned.
set_config('allowcohortthemes', 0);
$returnedcohorts = core_cohort_external::get_cohorts(array(
$cohort1->id));
$returnedcohorts = external_api::clean_returnvalue(core_cohort_external::get_cohorts_returns(), $returnedcohorts);
foreach ($returnedcohorts as $enrolledcohort) {
if ($enrolledcohort['idnumber'] == $cohort1->idnumber) {
$this->assertNull($enrolledcohort['theme']);
}
}
}
/**
* Test update_cohorts
*/
public function test_update_cohorts(): void {
global $DB;
$this->resetAfterTest(true);
// Custom fields.
$this->create_custom_fields();
set_config('allowcohortthemes', 0);
$cohort1 = self::getDataGenerator()->create_cohort(array('visible' => 0));
$data = cohort_handler::create()->export_instance_data_object($cohort1->id, true);
$this->assertNull($data->testfield1);
$this->assertNull($data->testfield2);
$cohort1 = array(
'id' => $cohort1->id,
'categorytype' => array('type' => 'id', 'value' => '1'),
'name' => 'cohortnametest1',
'idnumber' => 'idnumbertest1',
'description' => 'This is a description for cohort 1',
'theme' => 'classic',
'customfields' => array(
array(
'shortname' => 'testfield1',
'value' => 'Test value 1',
),
array(
'shortname' => 'testfield2',
'value' => 'Test value 2',
),
),
);
$context = \context_system::instance();
$roleid = $this->assignUserCapability('moodle/cohort:manage', $context->id);
// Call the external function.
core_cohort_external::update_cohorts(array($cohort1));
$dbcohort = $DB->get_record('cohort', array('id' => $cohort1['id']));
$contextid = $DB->get_field('context', 'id', array('instanceid' => $cohort1['categorytype']['value'],
'contextlevel' => CONTEXT_COURSECAT));
$this->assertEquals($dbcohort->contextid, $contextid);
$this->assertEquals($dbcohort->name, $cohort1['name']);
$this->assertEquals($dbcohort->idnumber, $cohort1['idnumber']);
$this->assertEquals($dbcohort->description, $cohort1['description']);
$this->assertEquals($dbcohort->visible, 0);
$this->assertEmpty($dbcohort->theme);
$data = cohort_handler::create()->export_instance_data_object($cohort1['id'], true);
$this->assertEquals('Test value 1', $data->testfield1);
$this->assertEquals('Test value 2', $data->testfield2);
// Since field 'visible' was added in 2.8, make sure that update works correctly with and without this parameter.
core_cohort_external::update_cohorts(array($cohort1 + array('visible' => 1)));
$dbcohort = $DB->get_record('cohort', array('id' => $cohort1['id']));
$this->assertEquals(1, $dbcohort->visible);
core_cohort_external::update_cohorts(array($cohort1));
$dbcohort = $DB->get_record('cohort', array('id' => $cohort1['id']));
$this->assertEquals(1, $dbcohort->visible);
// Call when $CFG->allowcohortthemes is enabled.
set_config('allowcohortthemes', 1);
core_cohort_external::update_cohorts(array($cohort1 + array('theme' => 'classic')));
$dbcohort = $DB->get_record('cohort', array('id' => $cohort1['id']));
$this->assertEquals('classic', $dbcohort->theme);
// Call when $CFG->allowcohortthemes is disabled.
set_config('allowcohortthemes', 0);
core_cohort_external::update_cohorts(array($cohort1 + array('theme' => 'boost')));
$dbcohort = $DB->get_record('cohort', array('id' => $cohort1['id']));
$this->assertEquals('classic', $dbcohort->theme);
// Updating custom fields.
$cohort1['customfields'] = array(
array(
'shortname' => 'testfield1',
'value' => 'Test value 1 updated',
),
array(
'shortname' => 'testfield2',
'value' => 'Test value 2 updated',
),
);
core_cohort_external::update_cohorts(array($cohort1));
$data = cohort_handler::create()->export_instance_data_object($cohort1['id'], true);
$this->assertEquals('Test value 1 updated', $data->testfield1);
$this->assertEquals('Test value 2 updated', $data->testfield2);
// Call without required capability.
$this->unassignUserCapability('moodle/cohort:manage', $context->id, $roleid);
$this->expectException(\required_capability_exception::class);
core_cohort_external::update_cohorts(array($cohort1));
}
/**
* Verify handling of 'id' param.
*/
public function test_update_cohorts_invalid_id_param(): void {
$this->resetAfterTest(true);
$cohort = self::getDataGenerator()->create_cohort();
$cohort1 = array(
'id' => 'THIS IS NOT AN ID',
'name' => 'Changed cohort name',
'categorytype' => array('type' => 'id', 'value' => '1'),
'idnumber' => $cohort->idnumber,
);
try {
core_cohort_external::update_cohorts(array($cohort1));
$this->fail('Expecting invalid_parameter_exception exception, none occured');
} catch (\invalid_parameter_exception $e1) {
$this->assertStringContainsString('Invalid external api parameter: the value is "THIS IS NOT AN ID"', $e1->debuginfo);
}
$cohort1['id'] = 9.999; // Also not a valid id of a cohort.
try {
core_cohort_external::update_cohorts(array($cohort1));
$this->fail('Expecting invalid_parameter_exception exception, none occured');
} catch (\invalid_parameter_exception $e2) {
$this->assertStringContainsString('Invalid external api parameter: the value is "9.999"', $e2->debuginfo);
}
}
/**
* Test update_cohorts without permission on the dest category.
*/
public function test_update_cohorts_missing_dest(): void {
global $USER, $CFG, $DB;
$this->resetAfterTest(true);
$category1 = self::getDataGenerator()->create_category(array(
'name' => 'Test category 1'
));
$category2 = self::getDataGenerator()->create_category(array(
'name' => 'Test category 2'
));
$context1 = \context_coursecat::instance($category1->id);
$context2 = \context_coursecat::instance($category2->id);
$cohort = array(
'contextid' => $context1->id,
'name' => 'cohortnametest1',
'idnumber' => 'idnumbertest1',
'description' => 'This is a description for cohort 1'
);
$cohort1 = self::getDataGenerator()->create_cohort($cohort);
$roleid = $this->assignUserCapability('moodle/cohort:manage', $context1->id);
$cohortupdate = array(
'id' => $cohort1->id,
'categorytype' => array('type' => 'id', 'value' => $category2->id),
'name' => 'cohort update',
'idnumber' => 'idnumber update',
'description' => 'This is a description update'
);
// Call the external function.
// Should fail because we don't have permission on the dest category
$this->expectException(\required_capability_exception::class);
core_cohort_external::update_cohorts(array($cohortupdate));
}
/**
* Test update_cohorts without permission on the src category.
*/
public function test_update_cohorts_missing_src(): void {
global $USER, $CFG, $DB;
$this->resetAfterTest(true);
$category1 = self::getDataGenerator()->create_category(array(
'name' => 'Test category 1'
));
$category2 = self::getDataGenerator()->create_category(array(
'name' => 'Test category 2'
));
$context1 = \context_coursecat::instance($category1->id);
$context2 = \context_coursecat::instance($category2->id);
$cohort = array(
'contextid' => $context1->id,
'name' => 'cohortnametest1',
'idnumber' => 'idnumbertest1',
'description' => 'This is a description for cohort 1'
);
$cohort1 = self::getDataGenerator()->create_cohort($cohort);
$roleid = $this->assignUserCapability('moodle/cohort:manage', $context2->id);
$cohortupdate = array(
'id' => $cohort1->id,
'categorytype' => array('type' => 'id', 'value' => $category2->id),
'name' => 'cohort update',
'idnumber' => 'idnumber update',
'description' => 'This is a description update'
);
// Call the external function.
// Should fail because we don't have permission on the src category
$this->expectException(\required_capability_exception::class);
core_cohort_external::update_cohorts(array($cohortupdate));
}
/**
* Test add_cohort_members
*/
public function test_add_cohort_members(): void {
global $DB;
$this->resetAfterTest(true); // Reset all changes automatically after this test.
$contextid = \context_system::instance()->id;
$cohort = array(
'contextid' => $contextid,
'name' => 'cohortnametest1',
'idnumber' => 'idnumbertest1',
'description' => 'This is a description for cohort 1'
);
$cohort0 = self::getDataGenerator()->create_cohort($cohort);
// Check the cohorts were correctly created.
$this->assertEquals(1, $DB->count_records_select('cohort', ' (id = :cohortid0)',
array('cohortid0' => $cohort0->id)));
$cohort1 = array(
'cohorttype' => array('type' => 'id', 'value' => $cohort0->id),
'usertype' => array('type' => 'id', 'value' => '1')
);
$roleid = $this->assignUserCapability('moodle/cohort:assign', $contextid);
// Call the external function.
$addcohortmembers = core_cohort_external::add_cohort_members(array($cohort1));
$addcohortmembers = external_api::clean_returnvalue(core_cohort_external::add_cohort_members_returns(), $addcohortmembers);
// Check we retrieve the good total number of created cohorts + no error on capability.
$this->assertEquals(1, count($addcohortmembers));
foreach ($addcohortmembers as $addcohortmember) {
$dbcohort = $DB->get_record('cohort_members', array('cohortid' => $cohort0->id));
$this->assertEquals($dbcohort->cohortid, $cohort1['cohorttype']['value']);
$this->assertEquals($dbcohort->userid, $cohort1['usertype']['value']);
}
// Call without required capability.
$cohort2 = array(
'cohorttype' => array('type' => 'id', 'value' => $cohort0->id),
'usertype' => array('type' => 'id', 'value' => '2')
);
$this->unassignUserCapability('moodle/cohort:assign', $contextid, $roleid);
$this->expectException(\required_capability_exception::class);
$addcohortmembers = core_cohort_external::add_cohort_members(array($cohort2));
}
/**
* Test delete_cohort_members
*/
public function test_delete_cohort_members(): void {
global $DB;
$this->resetAfterTest(true); // Reset all changes automatically after this test.
$cohort1 = self::getDataGenerator()->create_cohort();
$user1 = self::getDataGenerator()->create_user();
$cohort2 = self::getDataGenerator()->create_cohort();
$user2 = self::getDataGenerator()->create_user();
$context = \context_system::instance();
$roleid = $this->assignUserCapability('moodle/cohort:assign', $context->id);
$cohortaddmember1 = array(
'cohorttype' => array('type' => 'id', 'value' => $cohort1->id),
'usertype' => array('type' => 'id', 'value' => $user1->id)
);
$cohortmembers1 = core_cohort_external::add_cohort_members(array($cohortaddmember1));
$cohortmembers1 = external_api::clean_returnvalue(core_cohort_external::add_cohort_members_returns(), $cohortmembers1);
$cohortaddmember2 = array(
'cohorttype' => array('type' => 'id', 'value' => $cohort2->id),
'usertype' => array('type' => 'id', 'value' => $user2->id)
);
$cohortmembers2 = core_cohort_external::add_cohort_members(array($cohortaddmember2));
$cohortmembers2 = external_api::clean_returnvalue(core_cohort_external::add_cohort_members_returns(), $cohortmembers2);
// Check we retrieve no cohorts + no error on capability.
$this->assertEquals(2, $DB->count_records_select('cohort_members', ' ((cohortid = :idcohort1 AND userid = :iduser1)
OR (cohortid = :idcohort2 AND userid = :iduser2))',
array('idcohort1' => $cohort1->id, 'iduser1' => $user1->id, 'idcohort2' => $cohort2->id, 'iduser2' => $user2->id)));
// Call the external function.
$cohortdel1 = array(
'cohortid' => $cohort1->id,
'userid' => $user1->id
);
$cohortdel2 = array(
'cohortid' => $cohort2->id,
'userid' => $user2->id
);
core_cohort_external::delete_cohort_members(array($cohortdel1, $cohortdel2));
// Check we retrieve no cohorts + no error on capability.
$this->assertEquals(0, $DB->count_records_select('cohort_members', ' ((cohortid = :idcohort1 AND userid = :iduser1)
OR (cohortid = :idcohort2 AND userid = :iduser2))',
array('idcohort1' => $cohort1->id, 'iduser1' => $user1->id, 'idcohort2' => $cohort2->id, 'iduser2' => $user2->id)));
// Call without required capability.
$this->unassignUserCapability('moodle/cohort:assign', $context->id, $roleid);
$this->expectException(\required_capability_exception::class);
core_cohort_external::delete_cohort_members(array($cohortdel1, $cohortdel2));
}
/**
* Search cohorts.
*/
public function test_search_cohorts(): void {
global $DB, $CFG;
$this->resetAfterTest(true);
$this->create_custom_fields();
$creator = $this->getDataGenerator()->create_user();
$user = $this->getDataGenerator()->create_user();
$catuser = $this->getDataGenerator()->create_user();
$catcreator = $this->getDataGenerator()->create_user();
$courseuser = $this->getDataGenerator()->create_user();
$category = $this->getDataGenerator()->create_category();
$othercategory = $this->getDataGenerator()->create_category();
$course = $this->getDataGenerator()->create_course();
$syscontext = \context_system::instance();
$catcontext = \context_coursecat::instance($category->id);
$coursecontext = \context_course::instance($course->id);
// Fetching default authenticated user role.
$authrole = $DB->get_record('role', array('id' => $CFG->defaultuserroleid));
// Reset all default authenticated users permissions.
unassign_capability('moodle/cohort:manage', $authrole->id);
// Creating specific roles.
$creatorrole = create_role('Creator role', 'creatorrole', 'creator role description');
$userrole = create_role('User role', 'userrole', 'user role description');
$courserole = create_role('Course user role', 'courserole', 'course user role description');
assign_capability('moodle/cohort:manage', CAP_ALLOW, $creatorrole, $syscontext->id);
assign_capability('moodle/cohort:view', CAP_ALLOW, $courserole, $syscontext->id);
// Check for parameter $includes = 'parents'.
role_assign($creatorrole, $creator->id, $syscontext->id);
role_assign($creatorrole, $catcreator->id, $catcontext->id);
role_assign($userrole, $user->id, $syscontext->id);
role_assign($userrole, $catuser->id, $catcontext->id);
// Enrol user in the course.
$this->getDataGenerator()->enrol_user($courseuser->id, $course->id, 'courserole');
$syscontext = array('contextid' => \context_system::instance()->id);
$catcontext = array('contextid' => \context_coursecat::instance($category->id)->id);
$othercatcontext = array('contextid' => \context_coursecat::instance($othercategory->id)->id);
$coursecontext = array('contextid' => \context_course::instance($course->id)->id);
$customfields = array(
'contextid' => \context_system::instance()->id,
'customfield_testfield1' => 'Test value 1',
'customfield_testfield2' => 'Test value 2',
);
// We need a site admin to be able to populate cohorts custom fields.
$this->setAdminUser();
$cohort1 = $this->getDataGenerator()->create_cohort(array_merge($syscontext, array('name' => 'Cohortsearch 1')));
$cohort2 = $this->getDataGenerator()->create_cohort(array_merge($catcontext, array('name' => 'Cohortsearch 2')));
$cohort3 = $this->getDataGenerator()->create_cohort(array_merge($othercatcontext, array('name' => 'Cohortsearch 3')));
$cohort4 = $this->getDataGenerator()->create_cohort(array_merge($customfields, array('name' => 'Cohortsearch 4')));
// A user without permission in the system.
$this->setUser($user);
try {
$result = core_cohort_external::search_cohorts("Cohortsearch", $syscontext, 'parents');
$this->fail('Invalid permissions in system');
} catch (\required_capability_exception $e) {
// All good.
}
// A user without permission in a category.
$this->setUser($catuser);
try {
$result = core_cohort_external::search_cohorts("Cohortsearch", $catcontext, 'parents');
$this->fail('Invalid permissions in category');
} catch (\required_capability_exception $e) {
// All good.
}
// A user with permissions in the system.
$this->setUser($creator);
$result = core_cohort_external::search_cohorts("Cohortsearch 4", $syscontext, 'parents');
$this->assertCount(1, $result['cohorts']);
$this->assertEquals('Cohortsearch 4', $result['cohorts'][$cohort4->id]->name);
// A user with permissions in the system, searching category context.
$result = core_cohort_external::search_cohorts("Cohortsearch 4", $catcontext, 'parents');
$this->assertCount(1, $result['cohorts']);
$this->assertEquals('Cohortsearch 4', $result['cohorts'][$cohort4->id]->name);
$this->assertEqualsCanonicalizing([
'Test value 1',
'Test value 2',
], array_column($result['cohorts'][$cohort4->id]->customfields, 'value'));
$actual = [];
foreach ($result['cohorts'][$cohort4->id]->customfields as $customfield) {
$this->assertArrayHasKey('name', $customfield);
$this->assertArrayHasKey('shortname', $customfield);
$this->assertArrayHasKey('type', $customfield);
$this->assertArrayHasKey('valueraw', $customfield);
$this->assertArrayHasKey('value', $customfield);
$actual[$customfield['shortname']] = $customfield;
}
// A user with permissions in the category.
$this->setUser($catcreator);
$result = core_cohort_external::search_cohorts("Cohortsearch", $catcontext, 'parents');
$this->assertCount(3, $result['cohorts']);
$cohorts = array();
foreach ($result['cohorts'] as $cohort) {
$cohorts[] = $cohort->name;
}
$this->assertTrue(in_array('Cohortsearch 1', $cohorts));
// Check for parameter $includes = 'self'.
$this->setUser($creator);
$result = core_cohort_external::search_cohorts("Cohortsearch", $othercatcontext, 'self');
$this->assertCount(1, $result['cohorts']);
$this->assertEquals('Cohortsearch 3', $result['cohorts'][$cohort3->id]->name);
// Check for parameter $includes = 'all'.
$this->setUser($creator);
$result = core_cohort_external::search_cohorts("Cohortsearch", $syscontext, 'all');
$this->assertCount(4, $result['cohorts']);
// A user in the course context with the system cohort:view capability. Check that all the system cohorts are returned.
$this->setUser($courseuser);
$result = core_cohort_external::search_cohorts("Cohortsearch", $coursecontext, 'all');
$this->assertCount(2, $result['cohorts']);
$this->assertEquals('Cohortsearch 1', $result['cohorts'][$cohort1->id]->name);
// Detect invalid parameter $includes.
$this->setUser($creator);
try {
$result = core_cohort_external::search_cohorts("Cohortsearch", $syscontext, 'invalid');
$this->fail('Invalid parameter includes');
} catch (\coding_exception $e) {
// All good.
}
}
}
+7
View File
@@ -0,0 +1,7 @@
name,idnumber,description,category,visible
cohort name 1,cohortid1,first description,,
cohort name 2,cohortid2,,,
cohort name 3,cohortid3,,Category 1,no
cohort name 4,cohortid4,,CAT1,yes
cohort name 5,cohortid5,,CAT2,0
cohort name 6,cohortid6,,CAT3,1
1 name idnumber description category visible
2 cohort name 1 cohortid1 first description
3 cohort name 2 cohortid2
4 cohort name 3 cohortid3 Category 1 no
5 cohort name 4 cohortid4 CAT1 yes
6 cohort name 5 cohortid5 CAT2 0
7 cohort name 6 cohortid6 CAT3 1
+8
View File
@@ -0,0 +1,8 @@
name,idnumber,description,category,category_id,category_idnumber,category_path
Specify category as name,cohortid1,,Category 1,,,
Specify category as idnumber,cohortid2,,CAT1,,,
Specify category as id,cohortid3,,1,,,
Specify category as path,cohortid4,,Cat 1 / Cat 3,,,
Specify category_id,cohortid5,,,1,,
Specify category_idnumber,cohortid6,,,,CAT1,
Specify category_path,cohortid7,,,,,Cat 1 / Cat 3
1 name idnumber description category category_id category_idnumber category_path
2 Specify category as name cohortid1 Category 1
3 Specify category as idnumber cohortid2 CAT1
4 Specify category as id cohortid3 1
5 Specify category as path cohortid4 Cat 1 / Cat 3
6 Specify category_id cohortid5 1
7 Specify category_idnumber cohortid6 CAT1
8 Specify category_path cohortid7 Cat 1 / Cat 3
+6
View File
@@ -0,0 +1,6 @@
name,idnumber,description,context
Specify context as id (system),cohortid8,,1
Specify context as name (system),cohortid9,,System
Specify context as category name only,cohortid10,,Cat 1
Specify context as category path,cohortid12,,Cat 1 / Cat 3
Specify context as category idnumber,cohortid13,,CAT2
1 name idnumber description context
2 Specify context as id (system) cohortid8 1
3 Specify context as name (system) cohortid9 System
4 Specify context as category name only cohortid10 Cat 1
5 Specify context as category path cohortid12 Cat 1 / Cat 3
6 Specify context as category idnumber cohortid13 CAT2
+7
View File
@@ -0,0 +1,7 @@
name,idnumber,description,category,visible,theme
cohort name 1,cohortid1,first description,,,boost
cohort name 2,cohortid2,,,,
cohort name 3,cohortid3,,Category 1,no,boost
cohort name 4,cohortid4,,CAT1,yes,classic
cohort name 5,cohortid5,,CAT2,0,
cohort name 6,cohortid6,,CAT3,1,classic
1 name idnumber description category visible theme
2 cohort name 1 cohortid1 first description boost
3 cohort name 2 cohortid2
4 cohort name 3 cohortid3 Category 1 no boost
5 cohort name 4 cohortid4 CAT1 yes classic
6 cohort name 5 cohortid5 CAT2 0
7 cohort name 6 cohortid6 CAT3 1 classic
+13
View File
@@ -0,0 +1,13 @@
name,idnumber,description,category
cohort name 1,cid1,first description,
cohort name 2,cid2,,
cohort name 3,cid3,,Category 1
cohort name 4,cid4,,CAT1
cohort name 5,cid5,,CAT2
cohort name 6,cid6,,CAT3
cohort name 7,cid7,,CAT1
cohort name 8,cid8,,CAT2
cohort name 9,cid9,,CAT3
cohort name 10,cid10,,CAT1
cohort name 11,cid11,,CAT2
cohort name 12,cid1,,CAT3
1 name idnumber description category
2 cohort name 1 cid1 first description
3 cohort name 2 cid2
4 cohort name 3 cid3 Category 1
5 cohort name 4 cid4 CAT1
6 cohort name 5 cid5 CAT2
7 cohort name 6 cid6 CAT3
8 cohort name 7 cid7 CAT1
9 cohort name 8 cid8 CAT2
10 cohort name 9 cid9 CAT3
11 cohort name 10 cid10 CAT1
12 cohort name 11 cid11 CAT2
13 cohort name 12 cid1 CAT3
+973
View File
@@ -0,0 +1,973 @@
<?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/>.
namespace core_cohort;
use core_cohort\customfield\cohort_handler;
use core_customfield\data_controller;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("$CFG->dirroot/cohort/lib.php");
/**
* Cohort library tests.
*
* @package core_cohort
* @category phpunit
* @copyright 2012 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class lib_test extends \advanced_testcase {
/**
* Create Cohort custom field for testing.
*
* @return \core_customfield\field_controller
*/
protected function create_cohort_custom_field(): \core_customfield\field_controller {
$fieldcategory = self::getDataGenerator()->create_custom_field_category([
'component' => 'core_cohort',
'area' => 'cohort',
'name' => 'Other fields',
]);
return self::getDataGenerator()->create_custom_field([
'shortname' => 'testfield1',
'name' => 'Custom field',
'type' => 'text',
'categoryid' => $fieldcategory->get('id'),
]);
}
public function test_cohort_add_cohort(): void {
global $DB;
$this->resetAfterTest();
$this->setAdminUser();
$this->create_cohort_custom_field();
$cohort = new \stdClass();
$cohort->contextid = \context_system::instance()->id;
$cohort->name = 'test cohort';
$cohort->idnumber = 'testid';
$cohort->description = 'test cohort desc';
$cohort->descriptionformat = FORMAT_HTML;
$cohort->customfield_testfield1 = 'Test value 1';
$id = cohort_add_cohort($cohort);
$this->assertNotEmpty($id);
$newcohort = $DB->get_record('cohort', array('id'=>$id));
$this->assertEquals($cohort->contextid, $newcohort->contextid);
$this->assertSame($cohort->name, $newcohort->name);
$this->assertSame($cohort->description, $newcohort->description);
$this->assertEquals($cohort->descriptionformat, $newcohort->descriptionformat);
$this->assertNotEmpty($newcohort->timecreated);
$this->assertSame($newcohort->component, '');
$this->assertSame($newcohort->theme, '');
$this->assertSame($newcohort->timecreated, $newcohort->timemodified);
$handler = cohort_handler::create();
$customfieldsdata = $handler->export_instance_data_object($id);
$this->assertEquals('Test value 1', $customfieldsdata->testfield1);
}
public function test_cohort_add_cohort_missing_name(): void {
$cohort = new \stdClass();
$cohort->contextid = \context_system::instance()->id;
$cohort->name = null;
$cohort->idnumber = 'testid';
$cohort->description = 'test cohort desc';
$cohort->descriptionformat = FORMAT_HTML;
$this->expectException(\coding_exception::class);
$this->expectExceptionMessage('Missing cohort name in cohort_add_cohort()');
cohort_add_cohort($cohort);
}
public function test_cohort_add_cohort_event(): void {
$this->resetAfterTest();
// Setup cohort data structure.
$cohort = new \stdClass();
$cohort->contextid = \context_system::instance()->id;
$cohort->name = 'test cohort';
$cohort->idnumber = 'testid';
$cohort->description = 'test cohort desc';
$cohort->descriptionformat = FORMAT_HTML;
// Catch Events.
$sink = $this->redirectEvents();
// Perform the add operation.
$id = cohort_add_cohort($cohort);
// Capture the event.
$events = $sink->get_events();
$sink->close();
// Validate the event.
$this->assertCount(1, $events);
$event = $events[0];
$this->assertInstanceOf('\core\event\cohort_created', $event);
$this->assertEquals('cohort', $event->objecttable);
$this->assertEquals($id, $event->objectid);
$this->assertEquals($cohort->contextid, $event->contextid);
$url = new \moodle_url('/cohort/index.php', array('contextid' => $event->contextid));
$this->assertEquals($url, $event->get_url());
$this->assertEquals($cohort, $event->get_record_snapshot('cohort', $id));
$this->assertEventContextNotUsed($event);
}
public function test_cohort_update_cohort(): void {
global $DB;
$this->resetAfterTest();
$this->setAdminUser();
$this->create_cohort_custom_field();
$cohort = new \stdClass();
$cohort->contextid = \context_system::instance()->id;
$cohort->name = 'test cohort';
$cohort->idnumber = 'testid';
$cohort->description = 'test cohort desc';
$cohort->descriptionformat = FORMAT_HTML;
$cohort->customfield_testfield1 = 'Test value 1';
$id = cohort_add_cohort($cohort);
$this->assertNotEmpty($id);
$DB->set_field('cohort', 'timecreated', $cohort->timecreated - 10, array('id'=>$id));
$DB->set_field('cohort', 'timemodified', $cohort->timemodified - 10, array('id'=>$id));
$cohort = $DB->get_record('cohort', array('id'=>$id));
$cohort->name = 'test cohort 2';
$cohort->customfield_testfield1 = 'Test value updated';
cohort_update_cohort($cohort);
$newcohort = $DB->get_record('cohort', array('id'=>$id));
$this->assertSame($cohort->contextid, $newcohort->contextid);
$this->assertSame($cohort->name, $newcohort->name);
$this->assertSame($cohort->description, $newcohort->description);
$this->assertSame($cohort->descriptionformat, $newcohort->descriptionformat);
$this->assertSame($cohort->timecreated, $newcohort->timecreated);
$this->assertSame($cohort->component, $newcohort->component);
$this->assertSame($newcohort->theme, '');
$this->assertGreaterThan($newcohort->timecreated, $newcohort->timemodified);
$this->assertLessThanOrEqual(time(), $newcohort->timemodified);
$handler = cohort_handler::create();
$customfieldsdata = $handler->export_instance_data_object($id);
$this->assertEquals('Test value updated', $customfieldsdata->testfield1);
}
public function test_cohort_update_cohort_event(): void {
global $DB;
$this->resetAfterTest();
// Setup the cohort data structure.
$cohort = new \stdClass();
$cohort->contextid = \context_system::instance()->id;
$cohort->name = 'test cohort';
$cohort->idnumber = 'testid';
$cohort->description = 'test cohort desc';
$cohort->descriptionformat = FORMAT_HTML;
$cohort->theme = '';
$id = cohort_add_cohort($cohort);
$this->assertNotEmpty($id);
$cohort->name = 'test cohort 2';
// Catch Events.
$sink = $this->redirectEvents();
// Peform the update.
cohort_update_cohort($cohort);
// Add again theme property to the cohort object for comparing it to the event snapshop.
$cohort->theme = '';
$events = $sink->get_events();
$sink->close();
// Validate the event.
$this->assertCount(1, $events);
$event = $events[0];
$updatedcohort = $DB->get_record('cohort', array('id'=>$id));
$this->assertInstanceOf('\core\event\cohort_updated', $event);
$this->assertEquals('cohort', $event->objecttable);
$this->assertEquals($updatedcohort->id, $event->objectid);
$this->assertEquals($updatedcohort->contextid, $event->contextid);
$url = new \moodle_url('/cohort/edit.php', array('id' => $event->objectid));
$this->assertEquals($url, $event->get_url());
$this->assertEquals($cohort, $event->get_record_snapshot('cohort', $id));
$this->assertEventContextNotUsed($event);
}
public function test_cohort_delete_cohort(): void {
global $DB;
$this->resetAfterTest();
$this->setAdminUser();
$field = $this->create_cohort_custom_field();
$cohort = $this->getDataGenerator()->create_cohort(['customfield_testfield1' => 'Test value 1']);
$this->assertTrue($DB->record_exists('customfield_data', ['instanceid' => $cohort->id, 'fieldid' => $field->get('id')]));
cohort_delete_cohort($cohort);
$this->assertFalse($DB->record_exists('cohort', array('id'=>$cohort->id)));
$this->assertFalse($DB->record_exists('customfield_data', ['instanceid' => $cohort->id, 'fieldid' => $field->get('id')]));
}
public function test_cohort_delete_cohort_event(): void {
$this->resetAfterTest();
$cohort = $this->getDataGenerator()->create_cohort();
// Capture the events.
$sink = $this->redirectEvents();
// Perform the delete.
cohort_delete_cohort($cohort);
$events = $sink->get_events();
$sink->close();
// Validate the event structure.
$this->assertCount(1, $events);
$event = $events[0];
$this->assertInstanceOf('\core\event\cohort_deleted', $event);
$this->assertEquals('cohort', $event->objecttable);
$this->assertEquals($cohort->id, $event->objectid);
$url = new \moodle_url('/cohort/index.php', array('contextid' => $event->contextid));
$this->assertEquals($url, $event->get_url());
$this->assertEquals($cohort, $event->get_record_snapshot('cohort', $cohort->id));
$this->assertEventContextNotUsed($event);
}
public function test_cohort_delete_category(): void {
global $DB;
$this->resetAfterTest();
$category = $this->getDataGenerator()->create_category();
$cohort = $this->getDataGenerator()->create_cohort(array('contextid'=>\context_coursecat::instance($category->id)->id));
cohort_delete_category($category);
$this->assertTrue($DB->record_exists('cohort', array('id'=>$cohort->id)));
$newcohort = $DB->get_record('cohort', array('id'=>$cohort->id));
$this->assertEquals(\context_system::instance()->id, $newcohort->contextid);
}
public function test_cohort_add_member(): void {
global $DB;
$this->resetAfterTest();
$cohort = $this->getDataGenerator()->create_cohort();
$user = $this->getDataGenerator()->create_user();
$this->assertFalse($DB->record_exists('cohort_members', array('cohortid'=>$cohort->id, 'userid'=>$user->id)));
cohort_add_member($cohort->id, $user->id);
$this->assertTrue($DB->record_exists('cohort_members', array('cohortid'=>$cohort->id, 'userid'=>$user->id)));
}
public function test_cohort_add_member_event(): void {
global $USER;
$this->resetAfterTest();
// Setup the data.
$cohort = $this->getDataGenerator()->create_cohort();
$user = $this->getDataGenerator()->create_user();
// Capture the events.
$sink = $this->redirectEvents();
// Peform the add member operation.
cohort_add_member($cohort->id, $user->id);
$events = $sink->get_events();
$sink->close();
// Validate the event.
$this->assertCount(1, $events);
$event = $events[0];
$this->assertInstanceOf('\core\event\cohort_member_added', $event);
$this->assertEquals('cohort', $event->objecttable);
$this->assertEquals($cohort->id, $event->objectid);
$this->assertEquals($user->id, $event->relateduserid);
$this->assertEquals($USER->id, $event->userid);
$url = new \moodle_url('/cohort/assign.php', array('id' => $event->objectid));
$this->assertEquals($url, $event->get_url());
$this->assertEventContextNotUsed($event);
}
public function test_cohort_remove_member(): void {
global $DB;
$this->resetAfterTest();
$cohort = $this->getDataGenerator()->create_cohort();
$user = $this->getDataGenerator()->create_user();
cohort_add_member($cohort->id, $user->id);
$this->assertTrue($DB->record_exists('cohort_members', array('cohortid'=>$cohort->id, 'userid'=>$user->id)));
cohort_remove_member($cohort->id, $user->id);
$this->assertFalse($DB->record_exists('cohort_members', array('cohortid'=>$cohort->id, 'userid'=>$user->id)));
}
public function test_cohort_remove_member_event(): void {
global $USER;
$this->resetAfterTest();
// Setup the data.
$cohort = $this->getDataGenerator()->create_cohort();
$user = $this->getDataGenerator()->create_user();
cohort_add_member($cohort->id, $user->id);
// Capture the events.
$sink = $this->redirectEvents();
// Peform the remove operation.
cohort_remove_member($cohort->id, $user->id);
$events = $sink->get_events();
$sink->close();
// Validate the event.
$this->assertCount(1, $events);
$event = $events[0];
$this->assertInstanceOf('\core\event\cohort_member_removed', $event);
$this->assertEquals('cohort', $event->objecttable);
$this->assertEquals($cohort->id, $event->objectid);
$this->assertEquals($user->id, $event->relateduserid);
$this->assertEquals($USER->id, $event->userid);
$url = new \moodle_url('/cohort/assign.php', array('id' => $event->objectid));
$this->assertEquals($url, $event->get_url());
$this->assertEventContextNotUsed($event);
}
public function test_cohort_is_member(): void {
global $DB;
$this->resetAfterTest();
$cohort = $this->getDataGenerator()->create_cohort();
$user = $this->getDataGenerator()->create_user();
$this->assertFalse(cohort_is_member($cohort->id, $user->id));
cohort_add_member($cohort->id, $user->id);
$this->assertTrue(cohort_is_member($cohort->id, $user->id));
}
public function test_cohort_get_cohorts(): void {
global $DB;
$this->resetAfterTest();
$category1 = $this->getDataGenerator()->create_category();
$category2 = $this->getDataGenerator()->create_category();
$cohort1 = $this->getDataGenerator()->create_cohort(array('contextid'=>\context_coursecat::instance($category1->id)->id, 'name'=>'aaagrrryyy', 'idnumber'=>'','description'=>''));
$cohort2 = $this->getDataGenerator()->create_cohort(array('contextid'=>\context_coursecat::instance($category1->id)->id, 'name'=>'bbb', 'idnumber'=>'', 'description'=>'yyybrrr'));
$cohort3 = $this->getDataGenerator()->create_cohort(array('contextid'=>\context_coursecat::instance($category1->id)->id, 'name'=>'ccc', 'idnumber'=>'xxarrrghyyy', 'description'=>'po_us'));
$cohort4 = $this->getDataGenerator()->create_cohort(array('contextid'=>\context_system::instance()->id));
$result = cohort_get_cohorts(\context_coursecat::instance($category2->id)->id);
$this->assertEquals(0, $result['totalcohorts']);
$this->assertEquals(0, count($result['cohorts']));
$this->assertEquals(0, $result['allcohorts']);
$result = cohort_get_cohorts(\context_coursecat::instance($category1->id)->id);
$this->assertEquals(3, $result['totalcohorts']);
$this->assertEquals(array($cohort1->id=>$cohort1, $cohort2->id=>$cohort2, $cohort3->id=>$cohort3), $result['cohorts']);
$this->assertEquals(3, $result['allcohorts']);
$result = cohort_get_cohorts(\context_coursecat::instance($category1->id)->id, 0, 100, 'arrrgh');
$this->assertEquals(1, $result['totalcohorts']);
$this->assertEquals(array($cohort3->id=>$cohort3), $result['cohorts']);
$this->assertEquals(3, $result['allcohorts']);
$result = cohort_get_cohorts(\context_coursecat::instance($category1->id)->id, 0, 100, 'brrr');
$this->assertEquals(1, $result['totalcohorts']);
$this->assertEquals(array($cohort2->id=>$cohort2), $result['cohorts']);
$this->assertEquals(3, $result['allcohorts']);
$result = cohort_get_cohorts(\context_coursecat::instance($category1->id)->id, 0, 100, 'grrr');
$this->assertEquals(1, $result['totalcohorts']);
$this->assertEquals(array($cohort1->id=>$cohort1), $result['cohorts']);
$this->assertEquals(3, $result['allcohorts']);
$result = cohort_get_cohorts(\context_coursecat::instance($category1->id)->id, 1, 1, 'yyy');
$this->assertEquals(3, $result['totalcohorts']);
$this->assertEquals(array($cohort2->id=>$cohort2), $result['cohorts']);
$this->assertEquals(3, $result['allcohorts']);
$result = cohort_get_cohorts(\context_coursecat::instance($category1->id)->id, 0, 100, 'po_us');
$this->assertEquals(1, $result['totalcohorts']);
$this->assertEquals(array($cohort3->id=>$cohort3), $result['cohorts']);
$this->assertEquals(3, $result['allcohorts']);
$result = cohort_get_cohorts(\context_coursecat::instance($category1->id)->id, 0, 100, 'pokus');
$this->assertEquals(0, $result['totalcohorts']);
$this->assertEquals(array(), $result['cohorts']);
$this->assertEquals(3, $result['allcohorts']);
$result = cohort_get_cohorts(\context_system::instance()->id);
$this->assertEquals(1, $result['totalcohorts']);
$this->assertEquals(array($cohort4->id=>$cohort4), $result['cohorts']);
$this->assertEquals(1, $result['allcohorts']);
}
public function test_cohort_get_all_cohorts(): void {
global $DB;
$this->resetAfterTest();
$category1 = $this->getDataGenerator()->create_category();
$category2 = $this->getDataGenerator()->create_category();
$cohort1 = $this->getDataGenerator()->create_cohort(array('contextid'=>\context_coursecat::instance($category1->id)->id, 'name'=>'aaagrrryyy', 'idnumber'=>'','description'=>''));
$cohort2 = $this->getDataGenerator()->create_cohort(array('contextid'=>\context_coursecat::instance($category1->id)->id, 'name'=>'bbb', 'idnumber'=>'', 'description'=>'yyybrrr'));
$cohort3 = $this->getDataGenerator()->create_cohort(array('contextid'=>\context_coursecat::instance($category2->id)->id, 'name'=>'ccc', 'idnumber'=>'xxarrrghyyy', 'description'=>'po_us'));
$cohort4 = $this->getDataGenerator()->create_cohort(array('contextid'=>\context_system::instance()->id));
// Get list of all cohorts as admin.
$this->setAdminUser();
$result = cohort_get_all_cohorts(0, 100, '');
$this->assertEquals(4, $result['totalcohorts']);
$this->assertEquals(array($cohort1->id=>$cohort1, $cohort2->id=>$cohort2, $cohort3->id=>$cohort3, $cohort4->id=>$cohort4), $result['cohorts']);
$this->assertEquals(4, $result['allcohorts']);
$result = cohort_get_all_cohorts(0, 100, 'grrr');
$this->assertEquals(1, $result['totalcohorts']);
$this->assertEquals(array($cohort1->id=>$cohort1), $result['cohorts']);
$this->assertEquals(4, $result['allcohorts']);
// Get list of all cohorts as manager who has capability everywhere.
$user = $this->getDataGenerator()->create_user();
$managerrole = $DB->get_record('role', array('shortname' => 'manager'));
role_assign($managerrole->id, $user->id, \context_system::instance()->id);
$this->setUser($user);
$result = cohort_get_all_cohorts(0, 100, '');
$this->assertEquals(4, $result['totalcohorts']);
$this->assertEquals(array($cohort1->id=>$cohort1, $cohort2->id=>$cohort2, $cohort3->id=>$cohort3, $cohort4->id=>$cohort4), $result['cohorts']);
$this->assertEquals(4, $result['allcohorts']);
$result = cohort_get_all_cohorts(0, 100, 'grrr');
$this->assertEquals(1, $result['totalcohorts']);
$this->assertEquals(array($cohort1->id=>$cohort1), $result['cohorts']);
$this->assertEquals(4, $result['allcohorts']);
// Get list of all cohorts as manager who has capability everywhere except category2.
$context2 = \context_coursecat::instance($category2->id);
role_change_permission($managerrole->id, $context2, 'moodle/cohort:view', CAP_PROHIBIT);
role_change_permission($managerrole->id, $context2, 'moodle/cohort:manage', CAP_PROHIBIT);
$this->assertFalse(has_any_capability(array('moodle/cohort:view', 'moodle/cohort:manage'), $context2));
$result = cohort_get_all_cohorts(0, 100, '');
$this->assertEquals(3, $result['totalcohorts']);
$this->assertEquals(array($cohort1->id=>$cohort1, $cohort2->id=>$cohort2, $cohort4->id=>$cohort4), $result['cohorts']);
$this->assertEquals(3, $result['allcohorts']);
$result = cohort_get_all_cohorts(0, 100, 'grrr');
$this->assertEquals(1, $result['totalcohorts']);
$this->assertEquals(array($cohort1->id=>$cohort1), $result['cohorts']);
$this->assertEquals(3, $result['allcohorts']);
$result = cohort_get_cohorts(\context_coursecat::instance($category1->id)->id, 1, 1, 'yyy');
$this->assertEquals(2, $result['totalcohorts']);
$this->assertEquals(array($cohort2->id=>$cohort2), $result['cohorts']);
$this->assertEquals(2, $result['allcohorts']);
}
public function test_cohort_get_available_cohorts(): void {
global $DB;
$this->resetAfterTest();
$category1 = $this->getDataGenerator()->create_category();
$category2 = $this->getDataGenerator()->create_category();
$course1 = $this->getDataGenerator()->create_course(array('category' => $category1->id));
$course2 = $this->getDataGenerator()->create_course(array('category' => $category2->id));
$category1ctx = \context_coursecat::instance($category1->id);
$category2ctx = \context_coursecat::instance($category2->id);
$course1ctx = \context_course::instance(($course1->id));
$course2ctx = \context_course::instance(($course2->id));
$systemctx = \context_system::instance();
$cohort1 = $this->getDataGenerator()->create_cohort(array('contextid'=>$category1ctx->id, 'name'=>'aaagrrryyy', 'idnumber'=>'','description'=>''));
$cohort2 = $this->getDataGenerator()->create_cohort(array('contextid'=>$category1ctx->id, 'name'=>'bbb', 'idnumber'=>'', 'description'=>'yyybrrr', 'visible'=>0));
$cohort3 = $this->getDataGenerator()->create_cohort(array('contextid'=>$category2ctx->id, 'name'=>'ccc', 'idnumber'=>'xxarrrghyyy', 'description'=>'po_us'));
$cohort4 = $this->getDataGenerator()->create_cohort(array('contextid'=>$systemctx->id, 'name' => 'ddd'));
$cohort5 = $this->getDataGenerator()->create_cohort(array('contextid'=>$systemctx->id, 'visible'=>0, 'name' => 'eee'));
/*
Structure of generated course categories, courses and cohort:
system
-cohort4 (visible, has 3 members)
-cohort5 (not visible, no members)
category1
-cohort1 (visible, no members)
-cohort2 (not visible, has 1 member)
course1
category2
-cohort3 (visible, has 2 member)
course2
In this test we call cohort_get_available_cohorts() for users with different roles
and with different paramteres ($withmembers, $search, $offset, $limit) to make sure we go
through all possible options of SQL query.
*/
// Admin can see visible and invisible cohorts defined in above contexts.
$this->setAdminUser();
$result = cohort_get_available_cohorts($course1ctx, COHORT_ALL, 0, 0, '');
$this->assertEquals(array($cohort1->id, $cohort2->id, $cohort4->id, $cohort5->id), array_keys($result));
$result = cohort_get_available_cohorts($course1ctx, COHORT_ALL, 0, 2, '');
$this->assertEquals(array($cohort1->id, $cohort2->id), array_keys($result));
$result = cohort_get_available_cohorts($course1ctx, COHORT_ALL, 1, 2, '');
$this->assertEquals(array($cohort2->id, $cohort4->id), array_keys($result));
$result = cohort_get_available_cohorts($course1ctx, COHORT_ALL, 0, 100, 'yyy');
$this->assertEquals(array($cohort1->id, $cohort2->id), array_keys($result));
$result = cohort_get_available_cohorts($course2ctx, COHORT_ALL, 0, 0, '');
$this->assertEquals(array($cohort3->id, $cohort4->id, $cohort5->id), array_keys($result));
$result = cohort_get_available_cohorts($course1ctx, COHORT_WITH_MEMBERS_ONLY);
$this->assertEmpty($result);
$result = cohort_get_available_cohorts($course2ctx, COHORT_WITH_MEMBERS_ONLY);
$this->assertEmpty($result);
// Get list of available cohorts as a teacher in the course.
$user1 = $this->getDataGenerator()->create_user();
$teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
role_assign($teacherrole->id, $user1->id, $course1ctx->id);
role_assign($teacherrole->id, $user1->id, $course2ctx->id);
$this->setUser($user1);
$result = cohort_get_available_cohorts($course1ctx, COHORT_ALL, 0, 0, '');
$this->assertEquals(array($cohort1->id, $cohort4->id), array_keys($result));
$result = cohort_get_available_cohorts($course1ctx, COHORT_ALL, 0, 1, '');
$this->assertEquals(array($cohort1->id), array_keys($result));
$result = cohort_get_available_cohorts($course1ctx, COHORT_ALL, 1, 1, '');
$this->assertEquals(array($cohort4->id), array_keys($result));
$result = cohort_get_available_cohorts($course1ctx, COHORT_ALL, 0, 100, 'yyy');
$this->assertEquals(array($cohort1->id), array_keys($result));
$result = cohort_get_available_cohorts($course2ctx, COHORT_ALL, 0, 0, '');
$this->assertEquals(array($cohort3->id, $cohort4->id), array_keys($result));
$result = cohort_get_available_cohorts($course1ctx, COHORT_WITH_MEMBERS_ONLY);
$this->assertEmpty($result);
// Now add members to cohorts.
$user2 = $this->getDataGenerator()->create_user();
$user3 = $this->getDataGenerator()->create_user();
$user4 = $this->getDataGenerator()->create_user();
$user5 = $this->getDataGenerator()->create_user();
$user6 = $this->getDataGenerator()->create_user();
cohort_add_member($cohort2->id, $user3->id);
cohort_add_member($cohort3->id, $user2->id);
cohort_add_member($cohort3->id, $user3->id);
cohort_add_member($cohort4->id, $user4->id);
cohort_add_member($cohort4->id, $user5->id);
cohort_add_member($cohort4->id, $user6->id);
// Check filtering non-empty cohorts as admin.
$this->setAdminUser();
$result = cohort_get_available_cohorts($course1ctx, COHORT_WITH_MEMBERS_ONLY, 0, 0, '');
$this->assertEquals(array($cohort2->id, $cohort4->id), array_keys($result));
$this->assertEquals(1, $result[$cohort2->id]->memberscnt);
$this->assertEquals(3, $result[$cohort4->id]->memberscnt);
$result = cohort_get_available_cohorts($course2ctx, COHORT_WITH_MEMBERS_ONLY, 0, 0, '');
$this->assertEquals(array($cohort3->id, $cohort4->id), array_keys($result));
$this->assertEquals(2, $result[$cohort3->id]->memberscnt);
$this->assertEquals(3, $result[$cohort4->id]->memberscnt);
$result = cohort_get_available_cohorts($course1ctx, COHORT_WITH_MEMBERS_ONLY, 0, 0, 'yyy');
$this->assertEquals(array($cohort2->id), array_keys($result));
$this->assertEquals(1, $result[$cohort2->id]->memberscnt);
// Check filtering non-empty cohorts as teacher.
$this->setUser($user1);
$result = cohort_get_available_cohorts($course1ctx, COHORT_WITH_MEMBERS_ONLY, 0, 0, '');
$this->assertEquals(array($cohort4->id), array_keys($result));
$this->assertEquals(3, $result[$cohort4->id]->memberscnt);
$result = cohort_get_available_cohorts($course2ctx, COHORT_WITH_MEMBERS_ONLY, 0, 0, '');
$this->assertEquals(array($cohort3->id, $cohort4->id), array_keys($result));
$this->assertEquals(2, $result[$cohort3->id]->memberscnt);
$this->assertEquals(3, $result[$cohort4->id]->memberscnt);
$result = cohort_get_available_cohorts($course1ctx, COHORT_WITH_MEMBERS_ONLY, 0, 0, 'yyy');
$this->assertEmpty($result);
// Enrol users.
$studentrole = $DB->get_record('role', array('shortname' => 'student'));
$this->getDataGenerator()->enrol_user($user2->id, $course1->id, $studentrole->id);
$this->getDataGenerator()->enrol_user($user3->id, $course1->id, $studentrole->id);
$this->getDataGenerator()->enrol_user($user5->id, $course1->id, $studentrole->id);
$this->getDataGenerator()->enrol_user($user6->id, $course1->id, $studentrole->id);
$this->getDataGenerator()->enrol_user($user3->id, $course2->id, $studentrole->id);
$this->getDataGenerator()->enrol_user($user4->id, $course2->id, $studentrole->id);
$this->getDataGenerator()->enrol_user($user5->id, $course2->id, $studentrole->id);
$this->getDataGenerator()->enrol_user($user6->id, $course2->id, $studentrole->id);
// Check cohorts with enrolments as admin.
$this->setAdminUser();
$result = cohort_get_available_cohorts($course1ctx, COHORT_WITH_ENROLLED_MEMBERS_ONLY, 0, 0, '');
$this->assertEquals(array($cohort2->id, $cohort4->id), array_keys($result));
$this->assertEquals(1, $result[$cohort2->id]->enrolledcnt);
$this->assertEquals(2, $result[$cohort4->id]->enrolledcnt);
$this->assertEquals(1, $result[$cohort2->id]->memberscnt);
$this->assertEquals(3, $result[$cohort4->id]->memberscnt);
$result = cohort_get_available_cohorts($course2ctx, COHORT_WITH_ENROLLED_MEMBERS_ONLY, 0, 0, '');
$this->assertEquals(array($cohort3->id, $cohort4->id), array_keys($result));
$this->assertEquals(1, $result[$cohort3->id]->enrolledcnt);
$this->assertEquals(3, $result[$cohort4->id]->enrolledcnt);
$this->assertEquals(2, $result[$cohort3->id]->memberscnt);
$this->assertEquals(3, $result[$cohort4->id]->memberscnt);
$result = cohort_get_available_cohorts($course1ctx, COHORT_WITH_ENROLLED_MEMBERS_ONLY, 0, 0, 'yyy');
$this->assertEquals(array($cohort2->id), array_keys($result));
$this->assertEquals(1, $result[$cohort2->id]->enrolledcnt);
$this->assertEquals(1, $result[$cohort2->id]->memberscnt);
$result = cohort_get_available_cohorts($course1ctx, COHORT_WITH_NOTENROLLED_MEMBERS_ONLY, 0, 0, '');
$this->assertEquals(array($cohort4->id), array_keys($result));
$this->assertEquals(2, $result[$cohort4->id]->enrolledcnt);
$this->assertEquals(3, $result[$cohort4->id]->memberscnt);
// Assign user1 additional 'manager' role in the category context. He can now see hidden cohort in category1
// but still can not see hidden category in system.
$managerrole = $DB->get_record('role', array('shortname' => 'manager'));
role_assign($managerrole->id, $user1->id, \context_coursecat::instance($category1->id));
$this->setUser($user1);
$result = cohort_get_available_cohorts($course1ctx, COHORT_ALL, 0, 0, '');
$this->assertEquals(array($cohort1->id, $cohort2->id, $cohort4->id), array_keys($result));
}
/**
* Test that all get functions return custom fields data.
*
* @covers \cohort_get_cohort, \cohort_get_cohorts, \cohort_get_all_cohorts
* @covers \cohort_get_available_cohorts, \cohort_get_user_cohorts
*/
public function test_get_functions_return_custom_fields(): void {
$this->resetAfterTest();
$this->setAdminUser();
$user = self::getDataGenerator()->create_user();
$course = self::getDataGenerator()->create_course();
$coursectx = \context_course::instance(($course->id));
$this->create_cohort_custom_field();
$cohort1 = $this->getDataGenerator()->create_cohort(['customfield_testfield1' => 'Test value 1']);
$cohort2 = $this->getDataGenerator()->create_cohort();
// Test cohort_get_cohort.
$result = cohort_get_cohort($cohort1->id, $coursectx, true);
$this->assertObjectHasProperty('customfields', $result);
$this->assertCount(1, $result->customfields);
$field = reset($result->customfields);
$this->assertInstanceOf(data_controller::class, $field);
$this->assertEquals('testfield1', $field->get_field()->get('shortname'));
$this->assertEquals('Test value 1', $field->get_value());
// Test custom fields are not returned if not needed.
$result = cohort_get_cohort($cohort1->id, $coursectx);
$this->assertObjectNotHasProperty('customfields', $result);
// Test cohort_get_cohorts.
$result = cohort_get_cohorts(\context_system::instance()->id, 0, 25, '', true);
$this->assertEquals(2, $result['totalcohorts']);
$this->assertEquals(2, $result['allcohorts']);
foreach ($result['cohorts'] as $cohort) {
$this->assertObjectHasProperty('customfields', $cohort);
$this->assertCount(1, $cohort->customfields);
$field = reset($cohort->customfields);
$this->assertInstanceOf(data_controller::class, $field);
$this->assertEquals('testfield1', $field->get_field()->get('shortname'));
if ($cohort->id == $cohort1->id ) {
$this->assertEquals('Test value 1', $field->get_value());
} else {
$this->assertEquals('', $field->get_value());
}
}
// Test custom fields are not returned if not needed.
$result = cohort_get_cohorts(\context_system::instance()->id, 0, 25, '');
$this->assertEquals(2, $result['totalcohorts']);
$this->assertEquals(2, $result['allcohorts']);
foreach ($result['cohorts'] as $cohort) {
$this->assertObjectNotHasProperty('customfields', $cohort);
}
// Test test_cohort_get_all_cohorts.
$result = cohort_get_all_cohorts(0, 100, '', true);
$this->assertEquals(2, $result['totalcohorts']);
$this->assertEquals(2, $result['allcohorts']);
foreach ($result['cohorts'] as $cohort) {
$this->assertObjectHasProperty('customfields', $cohort);
$this->assertCount(1, $cohort->customfields);
$field = reset($cohort->customfields);
$this->assertInstanceOf(data_controller::class, $field);
$this->assertEquals('testfield1', $field->get_field()->get('shortname'));
if ($cohort->id == $cohort1->id ) {
$this->assertEquals('Test value 1', $field->get_value());
} else {
$this->assertEquals('', $field->get_value());
}
}
// Test custom fields are not returned if not needed.
$result = cohort_get_all_cohorts(0, 100, '');
$this->assertEquals(2, $result['totalcohorts']);
$this->assertEquals(2, $result['allcohorts']);
foreach ($result['cohorts'] as $cohort) {
$this->assertObjectNotHasProperty('customfields', $cohort);
}
// Test cohort_get_available_cohorts.
$result = cohort_get_available_cohorts($coursectx, COHORT_ALL, 0, 25, '', true);
$this->assertCount(2, $result);
foreach ($result as $cohort) {
$this->assertObjectHasProperty('customfields', $cohort);
$this->assertCount(1, $cohort->customfields);
$field = reset($cohort->customfields);
$this->assertInstanceOf(data_controller::class, $field);
$this->assertEquals('testfield1', $field->get_field()->get('shortname'));
if ($cohort->id == $cohort1->id ) {
$this->assertEquals('Test value 1', $field->get_value());
} else {
$this->assertEquals('', $field->get_value());
}
}
// Test custom fields are not returned if not needed.
$result = cohort_get_available_cohorts($coursectx, COHORT_ALL, 0, 25, '');
$this->assertCount(2, $result);
foreach ($result as $cohort) {
$this->assertObjectNotHasProperty('customfields', $cohort);
}
// Test cohort_get_user_cohorts.
cohort_add_member($cohort1->id, $user->id);
cohort_add_member($cohort2->id, $user->id);
$result = cohort_get_user_cohorts($user->id, true);
$this->assertCount(2, $result);
foreach ($result as $cohort) {
$this->assertObjectHasProperty('customfields', $cohort);
$this->assertCount(1, $cohort->customfields);
$field = reset($cohort->customfields);
$this->assertInstanceOf(data_controller::class, $field);
$this->assertEquals('testfield1', $field->get_field()->get('shortname'));
if ($cohort->id == $cohort1->id ) {
$this->assertEquals('Test value 1', $field->get_value());
} else {
$this->assertEquals('', $field->get_value());
}
}
// Test that there is no custom fields returned if not required.
$result = cohort_get_user_cohorts($user->id);
$this->assertCount(2, $result);
foreach ($result as $cohort) {
$this->assertObjectNotHasProperty('customfields', $cohort);
}
}
/**
* Create a cohort with allowcohortthemes enabled/disabled.
*/
public function test_cohort_add_theme_cohort(): void {
global $DB;
$this->resetAfterTest();
// Theme is added when allowcohortthemes is enabled.
set_config('allowcohortthemes', 1);
set_config('theme', 'boost');
$systemctx = \context_system::instance();
$cohort1 = $this->getDataGenerator()->create_cohort(array('contextid' => $systemctx->id, 'name' => 'test cohort 1',
'idnumber' => 'testid1', 'description' => 'test cohort desc', 'descriptionformat' => FORMAT_HTML, 'theme' => 'classic'));
$id = cohort_add_cohort($cohort1);
$this->assertNotEmpty($id);
$newcohort = $DB->get_record('cohort', array('id' => $id));
$this->assertEquals($cohort1->contextid, $newcohort->contextid);
$this->assertSame($cohort1->name, $newcohort->name);
$this->assertSame($cohort1->description, $newcohort->description);
$this->assertEquals($cohort1->descriptionformat, $newcohort->descriptionformat);
$this->assertNotEmpty($newcohort->theme);
$this->assertSame($cohort1->theme, $newcohort->theme);
$this->assertNotEmpty($newcohort->timecreated);
$this->assertSame($newcohort->component, '');
$this->assertSame($newcohort->timecreated, $newcohort->timemodified);
// Theme is not added when allowcohortthemes is disabled.
set_config('allowcohortthemes', 0);
$cohort2 = $this->getDataGenerator()->create_cohort(array('contextid' => $systemctx->id, 'name' => 'test cohort 2',
'idnumber' => 'testid2', 'description' => 'test cohort desc', 'descriptionformat' => FORMAT_HTML, 'theme' => 'classic'));
$id = cohort_add_cohort($cohort2);
$this->assertNotEmpty($id);
$newcohort = $DB->get_record('cohort', array('id' => $id));
$this->assertSame($cohort2->name, $newcohort->name);
$this->assertEmpty($newcohort->theme);
}
/**
* Update a cohort with allowcohortthemes enabled/disabled.
*/
public function test_cohort_update_theme_cohort(): void {
global $DB;
$this->resetAfterTest();
// Enable cohort themes.
set_config('allowcohortthemes', 1);
set_config('theme', 'boost');
$systemctx = \context_system::instance();
$cohort1 = $this->getDataGenerator()->create_cohort(array('contextid' => $systemctx->id, 'name' => 'test cohort 1',
'idnumber' => 'testid1', 'description' => 'test cohort desc', 'descriptionformat' => FORMAT_HTML, 'theme' => 'classic'));
$id = cohort_add_cohort($cohort1);
$this->assertNotEmpty($id);
// Theme is updated when allowcohortthemes is enabled.
$cohort1 = $DB->get_record('cohort', array('id' => $id));
$cohort1->name = 'test cohort 1 updated';
$cohort1->theme = 'classic';
cohort_update_cohort($cohort1);
$updatedcohort = $DB->get_record('cohort', array('id' => $id));
$this->assertEquals($cohort1->contextid, $updatedcohort->contextid);
$this->assertSame($cohort1->name, $updatedcohort->name);
$this->assertSame($cohort1->description, $updatedcohort->description);
$this->assertNotEmpty($updatedcohort->theme);
$this->assertSame($cohort1->theme, $updatedcohort->theme);
// Theme is not updated neither overwritten when allowcohortthemes is disabled.
set_config('allowcohortthemes', 0);
$cohort2 = $DB->get_record('cohort', array('id' => $id));
$cohort2->theme = 'classic';
cohort_update_cohort($cohort2);
$updatedcohort = $DB->get_record('cohort', array('id' => $id));
$this->assertEquals($cohort2->contextid, $updatedcohort->contextid);
$this->assertNotEmpty($updatedcohort->theme);
$this->assertSame($cohort1->theme, $updatedcohort->theme);
}
/**
* Test that lib function returns custom field data for a cohorts.
*
* @covers \cohort_get_custom_fields_data
*/
public function test_cohort_get_custom_fields_data(): void {
$this->resetAfterTest();
$this->setAdminUser();
$this->create_cohort_custom_field();
$cohort1 = $this->getDataGenerator()->create_cohort(['customfield_testfield1' => 'Test value 1']);
$cohort2 = $this->getDataGenerator()->create_cohort();
$result = cohort_get_custom_fields_data([$cohort1->id, $cohort2->id, 777]);
$this->assertArrayHasKey($cohort1->id, $result);
$this->assertArrayHasKey($cohort2->id, $result);
$this->assertArrayHasKey(777, $result);
foreach ($result as $cohortid => $fieldcontrollers) {
foreach ($fieldcontrollers as $fieldcontroller) {
$this->assertInstanceOf(data_controller::class, $fieldcontroller);
if ($cohortid == $cohort1->id) {
$this->assertSame('Test value 1', $fieldcontroller->export_value());
} else {
$this->assertNull($fieldcontroller->export_value());
}
}
}
}
/**
* Test the behaviour of cohort_get_cohort().
*
* @covers ::cohort_get_cohort
*/
public function test_cohort_get_cohort(): void {
$this->resetAfterTest();
$cat = $this->getDataGenerator()->create_category();
$cat1 = $this->getDataGenerator()->create_category(['parent' => $cat->id]);
$cat2 = $this->getDataGenerator()->create_category(['parent' => $cat->id]);
$course1 = $this->getDataGenerator()->create_course(['category' => $cat1->id, 'shortname' => 'ANON1']);
$course2 = $this->getDataGenerator()->create_course(['category' => $cat2->id, 'shortname' => 'ANON2']);
$cohort1 = $this->getDataGenerator()->create_cohort(['contextid' => \context_coursecat::instance($cat1->id)->id]);
$result = cohort_get_cohort($cohort1->id, \context_course::instance($course2->id));
$this->assertFalse($result);
$result = cohort_get_cohort($cohort1->id, \context_course::instance($course2->id), true);
$this->assertFalse($result);
$result = cohort_get_cohort($cohort1->id, \context_course::instance($course1->id));
$this->assertEquals($cohort1->id, $result->id);
$result = cohort_get_cohort($cohort1->id, \context_course::instance($course1->id), true);
$this->assertEquals($cohort1->id, $result->id);
}
}
+343
View File
@@ -0,0 +1,343 @@
<?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/>.
/**
* Base class for unit tests for core_cohort.
*
* @package core_cohort
* @category test
* @copyright 2018 Sara Arjona <sara@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_cohort\privacy;
defined('MOODLE_INTERNAL') || die();
use core_cohort\privacy\provider;
use core_privacy\local\request\approved_contextlist;
use core_privacy\local\request\writer;
use core_privacy\tests\provider_testcase;
use core_privacy\local\request\approved_userlist;
/**
* Unit tests for cohort\classes\privacy\provider.php
*
* @copyright 2018 Sara Arjona <sara@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider_test extends provider_testcase {
/**
* Basic setup for these tests.
*/
public function setUp(): void {
$this->resetAfterTest(true);
}
/**
* Test getting the context for the user ID related to this plugin.
*/
public function test_get_contexts_for_userid(): void {
// Create system cohort and category cohort.
$coursecategory = $this->getDataGenerator()->create_category();
$coursecategoryctx = \context_coursecat::instance($coursecategory->id);
$systemctx = \context_system::instance();
$categorycohort = $this->getDataGenerator()->create_cohort([
'contextid' => $coursecategoryctx->id,
'name' => 'Category cohort 1',
]);
$systemcohort = $this->getDataGenerator()->create_cohort([
'contextid' => $systemctx->id,
'name' => 'System cohort 1'
]);
// Create user and add to the system and category cohorts.
$user = $this->getDataGenerator()->create_user();
cohort_add_member($categorycohort->id, $user->id);
cohort_add_member($systemcohort->id, $user->id);
// User is member of 2 cohorts.
$contextlist = provider::get_contexts_for_userid($user->id);
$this->assertCount(2, (array) $contextlist->get_contextids());
$this->assertContainsEquals($coursecategoryctx->id, $contextlist->get_contextids());
$this->assertContainsEquals($systemctx->id, $contextlist->get_contextids());
}
/**
* Test that data is exported correctly for this plugin.
*/
public function test_export_user_data(): void {
// Create system cohort and category cohort.
$coursecategory = $this->getDataGenerator()->create_category();
$coursecategoryctx = \context_coursecat::instance($coursecategory->id);
$systemctx = \context_system::instance();
$categorycohort = $this->getDataGenerator()->create_cohort([
'contextid' => $coursecategoryctx->id,
'name' => 'Category cohort 1',
]);
$systemcohort1 = $this->getDataGenerator()->create_cohort([
'contextid' => $systemctx->id,
'name' => 'System cohort 1'
]);
$systemcohort2 = $this->getDataGenerator()->create_cohort([
'contextid' => $systemctx->id,
'name' => 'System cohort 2'
]);
// Create user and add to the system and category cohorts.
$user = $this->getDataGenerator()->create_user();
cohort_add_member($categorycohort->id, $user->id);
cohort_add_member($systemcohort1->id, $user->id);
cohort_add_member($systemcohort2->id, $user->id);
// Validate system cohort exported data.
$writer = writer::with_context($systemctx);
$this->assertFalse($writer->has_any_data());
$this->export_context_data_for_user($user->id, $systemctx, 'core_cohort');
$data = $writer->get_related_data([], 'cohort');
$this->assertCount(2, $data);
// Validate category cohort exported data.
$writer = writer::with_context($coursecategoryctx);
$this->assertFalse($writer->has_any_data());
$this->export_context_data_for_user($user->id, $coursecategoryctx, 'core_cohort');
$data = $writer->get_related_data([], 'cohort');
$this->assertCount(1, $data);
$this->assertEquals($categorycohort->name, reset($data)->name);
}
/**
* Test for provider::delete_data_for_all_users_in_context().
*/
public function test_delete_data_for_all_users_in_context(): void {
global $DB;
// Create system cohort and category cohort.
$coursecategory = $this->getDataGenerator()->create_category();
$coursecategoryctx = \context_coursecat::instance($coursecategory->id);
$systemctx = \context_system::instance();
$categorycohort = $this->getDataGenerator()->create_cohort([
'contextid' => $coursecategoryctx->id,
'name' => 'Category cohort 1',
'idnumber' => '',
'description' => ''
]);
$systemcohort = $this->getDataGenerator()->create_cohort([
'contextid' => $systemctx->id,
'name' => 'System cohort 1'
]);
// Create user and add to the system and category cohorts.
$user = $this->getDataGenerator()->create_user();
cohort_add_member($categorycohort->id, $user->id);
cohort_add_member($systemcohort->id, $user->id);
// Before deletion, we should have 2 entries in the cohort_members table.
$count = $DB->count_records('cohort_members');
$this->assertEquals(2, $count);
// Delete data based on system context.
provider::delete_data_for_all_users_in_context($systemctx);
// After deletion, the cohort_members entries should have been deleted.
$count = $DB->count_records('cohort_members');
$this->assertEquals(1, $count);
// Delete data based on category context.
provider::delete_data_for_all_users_in_context($coursecategoryctx);
// After deletion, the cohort_members entries should have been deleted.
$count = $DB->count_records('cohort_members');
$this->assertEquals(0, $count);
}
/**
* Test for provider::delete_data_for_user().
*/
public function test_delete_data_for_user(): void {
global $DB;
// Create system cohort and category cohort.
$coursecategory = $this->getDataGenerator()->create_category();
$coursecategoryctx = \context_coursecat::instance($coursecategory->id);
$systemctx = \context_system::instance();
$categorycohort = $this->getDataGenerator()->create_cohort([
'contextid' => $coursecategoryctx->id,
'name' => 'Category cohort 1',
'idnumber' => '',
'description' => ''
]);
$systemcohort = $this->getDataGenerator()->create_cohort([
'contextid' => $systemctx->id,
'name' => 'System cohort 1'
]);
// Create user and add to the system and category cohorts.
$user1 = $this->getDataGenerator()->create_user();
cohort_add_member($categorycohort->id, $user1->id);
cohort_add_member($systemcohort->id, $user1->id);
// Create another user and add to the system and category cohorts.
$user2 = $this->getDataGenerator()->create_user();
cohort_add_member($categorycohort->id, $user2->id);
cohort_add_member($systemcohort->id, $user2->id);
// Create another user and add to the system cohort.
$user3 = $this->getDataGenerator()->create_user();
cohort_add_member($systemcohort->id, $user3->id);
// Before deletion, we should have 5 entries in the cohort_members table.
$count = $DB->count_records('cohort_members');
$this->assertEquals(5, $count);
$contextlist = provider::get_contexts_for_userid($user1->id);
$contexts = [];
$contexts[] = \context_user::instance($user1->id)->id;
$contexts = array_merge($contexts, $contextlist->get_contextids());
$approvedcontextlist = new approved_contextlist($user1, 'cohort', $contexts);
provider::delete_data_for_user($approvedcontextlist);
// After deletion, the cohort_members entries for the first student should have been deleted.
$count = $DB->count_records('cohort_members', ['userid' => $user1->id]);
$this->assertEquals(0, $count);
$count = $DB->count_records('cohort_members');
$this->assertEquals(3, $count);
// Confirm that the cohorts hasn't been removed.
$cohortscount = $DB->get_records('cohort');
$this->assertCount(2, (array) $cohortscount);
}
/**
* Test that only users within a course context are fetched.
*/
public function test_get_users_in_context(): void {
$component = 'core_cohort';
// Create system cohort and category cohort.
$coursecategory = $this->getDataGenerator()->create_category();
$coursecategoryctx = \context_coursecat::instance($coursecategory->id);
$systemctx = \context_system::instance();
$categorycohort = $this->getDataGenerator()->create_cohort([
'contextid' => $coursecategoryctx->id,
'name' => 'Category cohort 1',
]);
// Create user.
$user = $this->getDataGenerator()->create_user();
$userctx = \context_user::instance($user->id);
$userlist1 = new \core_privacy\local\request\userlist($coursecategoryctx, $component);
provider::get_users_in_context($userlist1);
$this->assertCount(0, $userlist1);
$userlist2 = new \core_privacy\local\request\userlist($systemctx, $component);
provider::get_users_in_context($userlist2);
$this->assertCount(0, $userlist2);
$systemcohort = $this->getDataGenerator()->create_cohort([
'contextid' => $systemctx->id,
'name' => 'System cohort 1'
]);
// Create user and add to the system and category cohorts.
cohort_add_member($categorycohort->id, $user->id);
cohort_add_member($systemcohort->id, $user->id);
// The list of users within the coursecat context should contain user.
$userlist1 = new \core_privacy\local\request\userlist($coursecategoryctx, $component);
provider::get_users_in_context($userlist1);
$this->assertCount(1, $userlist1);
$expected = [$user->id];
$actual = $userlist1->get_userids();
$this->assertEquals($expected, $actual);
// The list of users within the system context should contain user.
$userlist2 = new \core_privacy\local\request\userlist($systemctx, $component);
provider::get_users_in_context($userlist2);
$this->assertCount(1, $userlist2);
$expected = [$user->id];
$actual = $userlist2->get_userids();
$this->assertEquals($expected, $actual);
// The list of users within the user context should be empty.
$userlist3 = new \core_privacy\local\request\userlist($userctx, $component);
provider::get_users_in_context($userlist3);
$this->assertCount(0, $userlist3);
}
/**
* Test that data for users in approved userlist is deleted.
*/
public function test_delete_data_for_users(): void {
$component = 'core_cohort';
// Create system cohort and category cohort.
$coursecategory = $this->getDataGenerator()->create_category();
$coursecategoryctx = \context_coursecat::instance($coursecategory->id);
$systemctx = \context_system::instance();
$categorycohort = $this->getDataGenerator()->create_cohort([
'contextid' => $coursecategoryctx->id,
'name' => 'Category cohort 1',
]);
// Create user1.
$user1 = $this->getDataGenerator()->create_user();
$userctx1 = \context_user::instance($user1->id);
// Create user2.
$user2 = $this->getDataGenerator()->create_user();
$systemcohort = $this->getDataGenerator()->create_cohort([
'contextid' => $systemctx->id,
'name' => 'System cohort 1'
]);
// Create user and add to the system and category cohorts.
cohort_add_member($categorycohort->id, $user1->id);
cohort_add_member($systemcohort->id, $user1->id);
cohort_add_member($categorycohort->id, $user2->id);
$userlist1 = new \core_privacy\local\request\userlist($coursecategoryctx, $component);
provider::get_users_in_context($userlist1);
$this->assertCount(2, $userlist1);
$userlist2 = new \core_privacy\local\request\userlist($systemctx, $component);
provider::get_users_in_context($userlist2);
$this->assertCount(1, $userlist2);
// Convert $userlist1 into an approved_contextlist.
$approvedlist1 = new approved_userlist($coursecategoryctx, $component, $userlist1->get_userids());
// Delete using delete_data_for_user.
provider::delete_data_for_users($approvedlist1);
// Re-fetch users in coursecategoryctx.
$userlist1 = new \core_privacy\local\request\userlist($coursecategoryctx, $component);
provider::get_users_in_context($userlist1);
// The user data in coursecategoryctx should be deleted.
$this->assertCount(0, $userlist1);
// Re-fetch users in coursecategoryctx.
$userlist2 = new \core_privacy\local\request\userlist($systemctx, $component);
provider::get_users_in_context($userlist2);
// The user data in coursecontext2 should be still present.
$this->assertCount(1, $userlist2);
// Convert $userlist2 into an approved_contextlist in the user context.
$approvedlist3 = new approved_userlist($userctx1, $component, $userlist2->get_userids());
// Delete using delete_data_for_user.
provider::delete_data_for_users($approvedlist3);
// Re-fetch users in coursecontext1.
$userlist3 = new \core_privacy\local\request\userlist($systemctx, $component);
provider::get_users_in_context($userlist3);
// The user data in systemcontext should not be deleted.
$this->assertCount(1, $userlist3);
}
}
@@ -0,0 +1,193 @@
<?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/>.
declare(strict_types=1);
namespace core_cohort\reportbuilder\audience;
use advanced_testcase;
use context;
use core_reportbuilder_generator;
use core_user\reportbuilder\datasource\users;
/**
* Unit tests for cohort member report audience type
*
* @package core_reportbuilder
* @covers \core_cohort\reportbuilder\audience\cohortmember
* @copyright 2021 David Matamoros <davidmc@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cohortmember_test extends advanced_testcase {
/**
* Test that this audience type description is generated correctly
*/
public function test_get_description(): void {
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report([
'name' => 'My report',
'source' => users::class,
'default' => false,
]);
$cohort = self::getDataGenerator()->create_cohort();
$audience = cohortmember::create($report->get('id'), ['cohorts' => [$cohort->id]]);
$this->assertEquals($cohort->name, $audience->get_description());
}
/**
* Test if user can add this audience type to the report
*/
public function test_user_can_add(): void {
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report([
'name' => 'My report',
'source' => users::class,
'default' => false,
]);
// Admin user.
self::setAdminUser();
$cohort = self::getDataGenerator()->create_cohort();
$context = context::instance_by_id($cohort->contextid);
$audience = cohortmember::create($report->get('id'), ['cohorts' => [$cohort->id]]);
$this->assertTrue($audience->user_can_add());
// Non-priveleged user.
$user = self::getDataGenerator()->create_user();
self::setUser($user);
$this->assertFalse($audience->user_can_add());
// Grant priveleges to user (moodle/cohort:view).
$roleid = create_role('Dummy role', 'dummyrole', 'dummy role description');
assign_capability('moodle/cohort:view', CAP_ALLOW, $roleid, $context->id);
role_assign($roleid, $user->id, $context->id);
$this->assertTrue($audience->user_can_add());
}
/**
* Test if user can edit this audience type
*/
public function test_user_can_edit(): void {
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report([
'name' => 'My report',
'source' => users::class,
'default' => false,
]);
$cohort = self::getDataGenerator()->create_cohort();
$context = context::instance_by_id($cohort->contextid);
$audience = cohortmember::create($report->get('id'), ['cohorts' => [$cohort->id]]);
// Admin user.
self::setAdminUser();
$this->assertTrue($audience->user_can_edit());
// Non-priveleged user.
$user = self::getDataGenerator()->create_user();
self::setUser($user);
$this->assertFalse($audience->user_can_edit());
// Grant priveleges to user (moodle/cohort:view).
$roleid = create_role('Dummy role', 'dummyrole', 'dummy role description');
assign_capability('moodle/cohort:view', CAP_ALLOW, $roleid, $context->id);
role_assign($roleid, $user->id, $context->id);
$this->assertTrue($audience->user_can_edit());
}
/**
* Test that sql generated is correct
*/
public function test_get_sql(): void {
global $DB;
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report([
'name' => 'My report',
'source' => users::class,
'default' => false,
]);
$cohort = self::getDataGenerator()->create_cohort();
$user1 = $this->getDataGenerator()->create_user();
$user2 = $this->getDataGenerator()->create_user();
$user3 = $this->getDataGenerator()->create_user();
// Add user1 into cohort.
cohort_add_member($cohort->id, $user1->id);
// Add user3 into cohort.
cohort_add_member($cohort->id, $user3->id);
$audience = cohortmember::create($report->get('id'), ['cohorts' => [$cohort->id]]);
[$join, $where, $params] = $audience->get_sql('u');
$query = 'SELECT u.* FROM {user} u ' . $join . ' WHERE ' . $where;
$records = $DB->get_records_sql($query, $params);
$this->assertEqualsCanonicalizing([$user1->id, $user3->id], array_column($records, 'id'));
}
/**
* Test if this audience type is available to use
*/
public function test_is_available(): void {
$this->resetAfterTest();
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
self::setAdminUser();
// Check with no cohorts available in the system.
$report = $generator->create_report([
'name' => 'My report',
'source' => users::class,
'default' => false,
]);
$audience = cohortmember::create($report->get('id'), ['cohorts' => []]);
$this->assertFalse($audience->is_available());
// Check with cohorts available in the system.
self::getDataGenerator()->create_cohort();
$report = $generator->create_report([
'name' => 'My report',
'source' => users::class,
'default' => false,
]);
$audience2 = cohortmember::create($report->get('id'), ['cohorts' => []]);
$this->assertTrue($audience2->is_available());
}
}
@@ -0,0 +1,294 @@
<?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/>.
declare(strict_types=1);
namespace core_cohort\reportbuilder\datasource;
use context_coursecat;
use context_system;
use core_customfield_generator;
use core_reportbuilder_generator;
use core_reportbuilder_testcase;
use core_reportbuilder\local\filters\{boolean_select, date, select, text};
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("{$CFG->dirroot}/reportbuilder/tests/helpers.php");
/**
* Unit tests for cohorts datasource
*
* @package core_cohort
* @covers \core_cohort\reportbuilder\datasource\cohorts
* @copyright 2021 Paul Holden <paulh@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cohorts_test extends core_reportbuilder_testcase {
/**
* Test default datasource
*/
public function test_datasource_default(): void {
$this->resetAfterTest();
// Test subject.
$contextsystem = context_system::instance();
$cohortone = $this->getDataGenerator()->create_cohort([
'contextid' => $contextsystem->id,
'name' => 'Legends',
'idnumber' => 'C101',
'description' => 'Cohort for the legends',
]);
$category = $this->getDataGenerator()->create_category();
$contextcategory = context_coursecat::instance($category->id);
$cohorttwo = $this->getDataGenerator()->create_cohort([
'contextid' => $contextcategory->id,
'name' => 'Category cohort',
'description' => 'This is my category cohort',
]);
// Non-visible cohort (excluded by default).
$cohortnonvisible = $this->getDataGenerator()->create_cohort([
'contextid' => $contextsystem->id,
'name' => 'Non-visible',
'visible' => false,
]);
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'Cohorts', 'source' => cohorts::class, 'default' => 1]);
$content = $this->get_custom_report_content($report->get('id'));
// Default columns are name, context, idnumber, description. Sorted by name.
$this->assertEquals([
[$cohorttwo->name, $contextcategory->get_context_name(false), $cohorttwo->idnumber,
format_text($cohorttwo->description)],
[$cohortone->name, $contextsystem->get_context_name(false), $cohortone->idnumber,
format_text($cohortone->description)],
], array_map('array_values', $content));
}
/**
* Test datasource columns that aren't added by default
*/
public function test_datasource_non_default_columns(): void {
$this->resetAfterTest();
$this->setAdminUser();
set_config('allowcohortthemes', true);
/** @var core_customfield_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_customfield');
$fieldcategory = $generator->create_category(['component' => 'core_cohort', 'area' => 'cohort']);
$field = $generator->create_field(['categoryid' => $fieldcategory->get('id'), 'shortname' => 'hi']);
// Test subject.
$cohort = $this->getDataGenerator()->create_cohort([
'name' => 'Legends',
'idnumber' => 'C101',
'description' => 'Cohort for the legends',
'theme' => 'boost',
'customfield_hi' => 'Hello',
]);
$user = $this->getDataGenerator()->create_user(['firstname' => 'Lionel', 'lastname' => 'Richards']);
cohort_add_member($cohort->id, $user->id);
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
$report = $generator->create_report(['name' => 'Cohorts', 'source' => cohorts::class, 'default' => 0]);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'cohort:visible']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'cohort:timecreated']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'cohort:timemodified']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'cohort:component']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'cohort:theme']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'cohort:customfield_hi']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'cohort_member:timeadded']);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:fullname']);
$content = $this->get_custom_report_content($report->get('id'));
$this->assertCount(1, $content);
[$visible, $timecreated, $timemodified, $component, $theme, $custom, $timeadded, $fullname] = array_values($content[0]);
$this->assertEquals('Yes', $visible);
$this->assertNotEmpty($timecreated);
$this->assertNotEmpty($timemodified);
$this->assertEquals('Created manually', $component);
$this->assertEquals('Boost', $theme);
$this->assertEquals('Hello', $custom);
$this->assertNotEmpty($timeadded);
$this->assertEquals(fullname($user), $fullname);
}
/**
* Data provider for {@see test_datasource_filters}
*
* @return array[]
*/
public function datasource_filters_provider(): array {
return [
// Cohort.
'Filter cohort' => ['cohort:cohortselect', [
'cohort:cohortselect_values' => [-1],
], false],
'Filter context' => ['cohort:context', [
'cohort:context_operator' => select::EQUAL_TO,
'cohort:context_value' => context_system::instance()->id,
], true],
'Filter content (no match)' => ['cohort:context', [
'cohort:context_operator' => select::EQUAL_TO,
'cohort:context_value' => -1,
], false],
'Filter name' => ['cohort:name', [
'cohort:name_operator' => text::IS_EQUAL_TO,
'cohort:name_value' => 'Legends',
], true],
'Filter name (no match)' => ['cohort:name', [
'cohort:name_operator' => text::IS_EQUAL_TO,
'cohort:name_value' => 'Dancing',
], false],
'Filter idnumber' => ['cohort:idnumber', [
'cohort:idnumber_operator' => text::IS_EQUAL_TO,
'cohort:idnumber_value' => 'C101',
], true],
'Filter idnumber (no match)' => ['cohort:idnumber', [
'cohort:idnumber_operator' => text::IS_EQUAL_TO,
'cohort:idnumber_value' => 'C102',
], false],
'Filter time created' => ['cohort:timecreated', [
'cohort:timecreated_operator' => date::DATE_RANGE,
'cohort:timecreated_from' => 1622502000,
], true],
'Filter time created (no match)' => ['cohort:timecreated', [
'cohort:timecreated_operator' => date::DATE_RANGE,
'cohort:timecreated_to' => 1622502000,
], false],
'Filter description' => ['cohort:description', [
'cohort:description_operator' => text::CONTAINS,
'cohort:description_value' => 'legends',
], true],
'Filter description (no match)' => ['cohort:description', [
'cohort:description_operator' => text::IS_EMPTY,
], false],
'Filter theme' => ['cohort:theme', [
'cohort:theme_operator' => select::EQUAL_TO,
'cohort:theme_value' => 'boost',
], true],
'Filter theme (no match)' => ['cohort:theme', [
'cohort:theme_operator' => select::EQUAL_TO,
'cohort:theme_value' => 'classic',
], false],
'Filter visible' => ['cohort:visible', [
'cohort:visible_operator' => boolean_select::CHECKED,
], true],
'Filter visible (no match)' => ['cohort:visible', [
'cohort:visible_operator' => boolean_select::NOT_CHECKED,
], false],
// Cohort member.
'Filter time added' => ['cohort_member:timeadded', [
'cohort_member:timeadded_operator' => date::DATE_RANGE,
'cohort_member:timeadded_from' => 1622502000,
], true],
'Filter time added (no match)' => ['cohort_member:timeadded', [
'cohort_member:timeadded_operator' => date::DATE_RANGE,
'cohort_member:timeadded_to' => 1622502000,
], false],
// User.
'Filter user' => ['user:username', [
'user:username_operator' => text::IS_EQUAL_TO,
'user:username_value' => 'lionel',
], true],
'Filter user (no match)' => ['user:username', [
'user:username_operator' => text::IS_EQUAL_TO,
'user:username_value' => 'rick',
], false],
];
}
/**
* Test datasource filters
*
* @param string $filtername
* @param array $filtervalues
* @param bool $expectmatch
*
* @dataProvider datasource_filters_provider
*/
public function test_datasource_filters(string $filtername, array $filtervalues, bool $expectmatch): void {
$this->resetAfterTest();
set_config('allowcohortthemes', true);
// Test subject.
$cohort = $this->getDataGenerator()->create_cohort([
'name' => 'Legends',
'idnumber' => 'C101',
'description' => 'Cohort for the legends',
'theme' => 'boost',
]);
$user = $this->getDataGenerator()->create_user(['username' => 'lionel']);
cohort_add_member($cohort->id, $user->id);
/** @var core_reportbuilder_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder');
// Create report containing single column, and given filter.
$report = $generator->create_report(['name' => 'Cohorts', 'source' => cohorts::class, 'default' => 0]);
$generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'cohort:name']);
// Add filter, set it's values.
$generator->create_filter(['reportid' => $report->get('id'), 'uniqueidentifier' => $filtername]);
$content = $this->get_custom_report_content($report->get('id'), 0, $filtervalues);
if ($expectmatch) {
$this->assertCount(1, $content);
$this->assertEquals('Legends', reset($content[0]));
} else {
$this->assertEmpty($content);
}
}
/**
* Stress test datasource
*
* In order to execute this test PHPUNIT_LONGTEST should be defined as true in phpunit.xml or directly in config.php
*/
public function test_stress_datasource(): void {
if (!PHPUNIT_LONGTEST) {
$this->markTestSkipped('PHPUNIT_LONGTEST is not defined');
}
$this->resetAfterTest();
$cohort = $this->getDataGenerator()->create_cohort();
$user = $this->getDataGenerator()->create_user();
cohort_add_member($cohort->id, $user->id);
$this->datasource_stress_test_columns(cohorts::class);
$this->datasource_stress_test_columns_aggregation(cohorts::class);
$this->datasource_stress_test_conditions(cohorts::class, 'cohort:name');
}
}
+16
View File
@@ -0,0 +1,16 @@
This files describes API changes in /cohort/ information provided here is intended
especially for developers.
=== 4.2 ===
* Added cohort custom fields.
=== 3.1 ===
* The Webservice core_cohort_get_cohorts now has the added functionality of getting all cohorts
by not passing any parameters
=== 2.6 ===
* Webservice core_cohort_update_cohorts was incorrectly specifiying float as the parameter type
for cohort id. This field is actually int and input is now reported and processed as such.
* Webservice core_cohort_get_cohorts was incorrectly specifiying float as the return
type for cohort id. The actual return type is int and is now reported as such.
+94
View File
@@ -0,0 +1,94 @@
<?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/>.
/**
* A form for cohort upload.
*
* @package core_cohort
* @copyright 2014 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once('../config.php');
require_once($CFG->dirroot.'/cohort/lib.php');
require_once($CFG->dirroot.'/cohort/upload_form.php');
require_once($CFG->libdir . '/csvlib.class.php');
$contextid = optional_param('contextid', 0, PARAM_INT);
require_login();
if ($contextid) {
$context = context::instance_by_id($contextid, MUST_EXIST);
} else {
$context = context_system::instance();
}
if ($context->contextlevel != CONTEXT_COURSECAT && $context->contextlevel != CONTEXT_SYSTEM) {
throw new \moodle_exception('invalidcontext');
}
require_capability('moodle/cohort:manage', $context);
$PAGE->set_context($context);
$baseurl = new moodle_url('/cohort/upload.php', array('contextid' => $context->id));
$PAGE->set_url($baseurl);
$PAGE->set_pagelayout('admin');
if ($context->contextlevel == CONTEXT_COURSECAT) {
core_course_category::page_setup();
// Set the cohorts node active in the settings navigation block.
if ($cohortsnode = $PAGE->settingsnav->find('cohort', navigation_node::TYPE_SETTING)) {
$cohortsnode->make_active();
}
$PAGE->set_secondary_active_tab('cohort');
} else {
navigation_node::override_active_url(new moodle_url('/cohort/index.php', array()));
$PAGE->set_heading($COURSE->fullname);
}
$uploadform = new cohort_upload_form(null, array('contextid' => $context->id));
$returnurl = new moodle_url('/cohort/index.php', array('contextid' => $context->id));
if ($uploadform->is_cancelled()) {
redirect($returnurl);
}
$strheading = get_string('uploadcohorts', 'cohort');
$PAGE->set_title($strheading);
$PAGE->navbar->add($strheading);
echo $OUTPUT->header();
echo $OUTPUT->heading_with_help($strheading, 'uploadcohorts', 'cohort');
if ($editcontrols = cohort_edit_controls($context, $baseurl)) {
echo $OUTPUT->render($editcontrols);
}
if ($data = $uploadform->get_data()) {
$cohortsdata = $uploadform->get_cohorts_data();
foreach ($cohortsdata as $cohort) {
cohort_add_cohort($cohort);
}
echo $OUTPUT->notification(get_string('uploadedcohorts', 'cohort', count($cohortsdata)), 'notifysuccess');
echo $OUTPUT->continue_button($returnurl);
} else {
$uploadform->display();
}
echo $OUTPUT->footer();
+570
View File
@@ -0,0 +1,570 @@
<?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/>.
/**
* A form for cohort upload.
*
* @package core_cohort
* @copyright 2014 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir.'/formslib.php');
/**
* Cohort upload form class
*
* @package core_cohort
* @copyright 2014 Marina Glancy
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cohort_upload_form extends moodleform {
/** @var array new cohorts that need to be created */
public $processeddata = null;
/** @var array cached list of available contexts */
protected $contextoptions = null;
/** @var array temporary cache for retrieved categories */
protected $categoriescache = array();
/**
* Form definition
*/
public function definition() {
$mform = $this->_form;
$data = (object)$this->_customdata;
$mform->addElement('header', 'cohortfileuploadform', get_string('uploadafile'));
$filepickeroptions = array();
$filepickeroptions['filetypes'] = '*';
$filepickeroptions['maxbytes'] = get_max_upload_file_size();
$mform->addElement('filepicker', 'cohortfile', get_string('file'), null, $filepickeroptions);
$choices = csv_import_reader::get_delimiter_list();
$mform->addElement('select', 'delimiter', get_string('csvdelimiter', 'tool_uploadcourse'), $choices);
if (array_key_exists('cfg', $choices)) {
$mform->setDefault('delimiter', 'cfg');
} else if (get_string('listsep', 'langconfig') == ';') {
$mform->setDefault('delimiter', 'semicolon');
} else {
$mform->setDefault('delimiter', 'comma');
}
$mform->addHelpButton('delimiter', 'csvdelimiter', 'tool_uploadcourse');
$choices = core_text::get_encodings();
$mform->addElement('select', 'encoding', get_string('encoding', 'tool_uploadcourse'), $choices);
$mform->setDefault('encoding', 'UTF-8');
$mform->addHelpButton('encoding', 'encoding', 'tool_uploadcourse');
$options = $this->get_context_options();
$mform->addElement('select', 'contextid', get_string('defaultcontext', 'cohort'), $options);
$this->add_cohort_upload_buttons(true);
$this->set_data($data);
}
/**
* Add buttons to the form ("Upload cohorts", "Preview", "Cancel")
*/
protected function add_cohort_upload_buttons() {
$mform = $this->_form;
$buttonarray = array();
$submitlabel = get_string('uploadcohorts', 'cohort');
$buttonarray[] = $mform->createElement('submit', 'submitbutton', $submitlabel);
$previewlabel = get_string('preview', 'cohort');
$buttonarray[] = $mform->createElement('submit', 'previewbutton', $previewlabel);
$mform->registerNoSubmitButton('previewbutton');
$buttonarray[] = $mform->createElement('cancel');
$mform->addGroup($buttonarray, 'buttonar', '', array(' '), false);
$mform->closeHeaderBefore('buttonar');
}
/**
* Process the uploaded file and allow the submit button only if it doest not have errors.
*/
public function definition_after_data() {
$mform = $this->_form;
$cohortfile = $mform->getElementValue('cohortfile');
$allowsubmitform = false;
if ($cohortfile && ($file = $this->get_cohort_file($cohortfile))) {
// File was uploaded. Parse it.
$encoding = $mform->getElementValue('encoding')[0];
$delimiter = $mform->getElementValue('delimiter')[0];
$contextid = $mform->getElementValue('contextid')[0];
if (!empty($contextid) && ($context = context::instance_by_id($contextid, IGNORE_MISSING))) {
$this->processeddata = $this->process_upload_file($file, $encoding, $delimiter, $context);
if ($this->processeddata && count($this->processeddata) > 1 && !$this->processeddata[0]['errors']) {
$allowsubmitform = true;
}
}
}
if (!$allowsubmitform) {
// Hide submit button.
$el = $mform->getElement('buttonar')->getElements()[0];
$el->setValue('');
$el->freeze();
} else {
$mform->setExpanded('cohortfileuploadform', false);
}
}
/**
* Returns the list of contexts where current user can create cohorts.
*
* @return array
*/
protected function get_context_options() {
if ($this->contextoptions === null) {
$this->contextoptions = array();
$displaylist = core_course_category::make_categories_list('moodle/cohort:manage');
// We need to index the options array by context id instead of category id and add option for system context.
$syscontext = context_system::instance();
if (has_capability('moodle/cohort:manage', $syscontext)) {
$this->contextoptions[$syscontext->id] = $syscontext->get_context_name();
}
foreach ($displaylist as $cid => $name) {
$context = context_coursecat::instance($cid);
$this->contextoptions[$context->id] = $name;
}
}
return $this->contextoptions;
}
public function validation($data, $files) {
$errors = parent::validation($data, $files);
if (empty($errors)) {
if (empty($data['cohortfile']) || !($file = $this->get_cohort_file($data['cohortfile']))) {
$errors['cohortfile'] = get_string('required');
} else {
if (!empty($this->processeddata[0]['errors'])) {
// Any value in $errors will notify that validation did not pass. The detailed errors will be shown in preview.
$errors['dummy'] = '';
}
}
}
return $errors;
}
/**
* Returns the uploaded file if it is present.
*
* @param int $draftid
* @return stored_file|null
*/
protected function get_cohort_file($draftid) {
global $USER;
// We can not use moodleform::get_file_content() method because we need the content before the form is validated.
if (!$draftid) {
return null;
}
$fs = get_file_storage();
$context = context_user::instance($USER->id);
if (!$files = $fs->get_area_files($context->id, 'user', 'draft', $draftid, 'id DESC', false)) {
return null;
}
$file = reset($files);
return $file;
}
/**
* Returns the list of prepared objects to be added as cohorts
*
* @return array of stdClass objects, each can be passed to {@link cohort_add_cohort()}
*/
public function get_cohorts_data() {
$cohorts = array();
if ($this->processeddata) {
foreach ($this->processeddata as $idx => $line) {
if ($idx && !empty($line['data'])) {
$cohorts[] = (object)$line['data'];
}
}
}
return $cohorts;
}
/**
* Displays the preview of the uploaded file
*/
protected function preview_uploaded_cohorts() {
global $OUTPUT;
if (empty($this->processeddata)) {
return;
}
foreach ($this->processeddata[0]['errors'] as $error) {
echo $OUTPUT->notification($error);
}
foreach ($this->processeddata[0]['warnings'] as $warning) {
echo $OUTPUT->notification($warning, 'notifymessage');
}
$table = new html_table();
$table->id = 'previewuploadedcohorts';
$columns = $this->processeddata[0]['data'];
$columns['contextid'] = get_string('context', 'role');
// Add column names to the preview table.
$table->head = array('');
foreach ($columns as $key => $value) {
$table->head[] = $value;
}
$table->head[] = get_string('status');
// Add (some) rows to the preview table.
$previewdrows = $this->get_previewed_rows();
foreach ($previewdrows as $idx) {
$line = $this->processeddata[$idx];
$cells = array(new html_table_cell($idx));
$context = context::instance_by_id($line['data']['contextid']);
foreach ($columns as $key => $value) {
if ($key === 'contextid') {
$text = html_writer::link(new moodle_url('/cohort/index.php', array('contextid' => $context->id)),
$context->get_context_name(false));
} else {
$text = s($line['data'][$key]);
}
$cells[] = new html_table_cell($text);
}
$text = '';
if ($line['errors']) {
$text .= html_writer::div(join('<br>', $line['errors']), 'notifyproblem');
}
if ($line['warnings']) {
$text .= html_writer::div(join('<br>', $line['warnings']));
}
$cells[] = new html_table_cell($text);
$table->data[] = new html_table_row($cells);
}
if ($notdisplayed = count($this->processeddata) - count($previewdrows) - 1) {
$cell = new html_table_cell(get_string('displayedrows', 'cohort',
(object)array('displayed' => count($previewdrows), 'total' => count($this->processeddata) - 1)));
$cell->colspan = count($columns) + 2;
$table->data[] = new html_table_row(array($cell));
}
echo html_writer::table($table);
}
/**
* Find up rows to show in preview
*
* Number of previewed rows is limited but rows with errors and warnings have priority.
*
* @return array
*/
protected function get_previewed_rows() {
$previewlimit = 10;
if (count($this->processeddata) <= 1) {
$rows = array();
} else if (count($this->processeddata) < $previewlimit + 1) {
// Return all rows.
$rows = range(1, count($this->processeddata) - 1);
} else {
// First find rows with errors and warnings (no more than 10 of each).
$errorrows = $warningrows = array();
foreach ($this->processeddata as $rownum => $line) {
if ($rownum && $line['errors']) {
$errorrows[] = $rownum;
if (count($errorrows) >= $previewlimit) {
return $errorrows;
}
} else if ($rownum && $line['warnings']) {
if (count($warningrows) + count($errorrows) < $previewlimit) {
$warningrows[] = $rownum;
}
}
}
// Include as many error rows as possible and top them up with warning rows.
$rows = array_merge($errorrows, array_slice($warningrows, 0, $previewlimit - count($errorrows)));
// Keep adding good rows until we reach limit.
for ($rownum = 1; count($rows) < $previewlimit; $rownum++) {
if (!in_array($rownum, $rows)) {
$rows[] = $rownum;
}
}
asort($rows);
}
return $rows;
}
public function display() {
// Finalize the form definition if not yet done.
if (!$this->_definition_finalized) {
$this->_definition_finalized = true;
$this->definition_after_data();
}
// Difference from the parent display() method is that we want to show preview above the form if applicable.
$this->preview_uploaded_cohorts();
$this->_form->display();
}
/**
* @param stored_file $file
* @param string $encoding
* @param string $delimiter
* @param context $defaultcontext
* @return array
*/
protected function process_upload_file($file, $encoding, $delimiter, $defaultcontext) {
global $CFG, $DB;
require_once($CFG->libdir . '/csvlib.class.php');
$cohorts = array(
0 => array('errors' => array(), 'warnings' => array(), 'data' => array())
);
// Read and parse the CSV file using csv library.
$content = $file->get_content();
if (!$content) {
$cohorts[0]['errors'][] = new lang_string('csvemptyfile', 'error');
return $cohorts;
}
$uploadid = csv_import_reader::get_new_iid('uploadcohort');
$cir = new csv_import_reader($uploadid, 'uploadcohort');
$readcount = $cir->load_csv_content($content, $encoding, $delimiter);
unset($content);
if (!$readcount) {
$cohorts[0]['errors'][] = get_string('csvloaderror', 'error', $cir->get_error());
return $cohorts;
}
$columns = $cir->get_columns();
// Check that columns include 'name' and warn about extra columns.
$allowedcolumns = array('contextid', 'name', 'idnumber', 'description', 'descriptionformat', 'visible', 'theme');
$additionalcolumns = array('context', 'category', 'category_id', 'category_idnumber', 'category_path');
$displaycolumns = array();
$extracolumns = array();
$columnsmapping = array();
foreach ($columns as $i => $columnname) {
$columnnamelower = preg_replace('/ /', '', core_text::strtolower($columnname));
$columnsmapping[$i] = null;
if (in_array($columnnamelower, $allowedcolumns)) {
$displaycolumns[$columnnamelower] = $columnname;
$columnsmapping[$i] = $columnnamelower;
} else if (in_array($columnnamelower, $additionalcolumns)) {
$columnsmapping[$i] = $columnnamelower;
} else {
$extracolumns[] = $columnname;
}
}
if (!in_array('name', $columnsmapping)) {
$cohorts[0]['errors'][] = new lang_string('namecolumnmissing', 'cohort');
return $cohorts;
}
if ($extracolumns) {
$cohorts[0]['warnings'][] = new lang_string('csvextracolumns', 'cohort', s(join(', ', $extracolumns)));
}
if (!isset($displaycolumns['contextid'])) {
$displaycolumns['contextid'] = 'contextid';
}
$cohorts[0]['data'] = $displaycolumns;
// Parse data rows.
$cir->init();
$rownum = 0;
$idnumbers = array();
$haserrors = false;
$haswarnings = false;
while ($row = $cir->next()) {
$rownum++;
$cohorts[$rownum] = array(
'errors' => array(),
'warnings' => array(),
'data' => array(),
);
$hash = array();
foreach ($row as $i => $value) {
if ($columnsmapping[$i]) {
$hash[$columnsmapping[$i]] = $value;
}
}
$this->clean_cohort_data($hash);
$warnings = $this->resolve_context($hash, $defaultcontext);
$cohorts[$rownum]['warnings'] = array_merge($cohorts[$rownum]['warnings'], $warnings);
if (!empty($hash['idnumber'])) {
if (isset($idnumbers[$hash['idnumber']]) || $DB->record_exists('cohort', array('idnumber' => $hash['idnumber']))) {
$cohorts[$rownum]['errors'][] = new lang_string('duplicateidnumber', 'cohort');
}
$idnumbers[$hash['idnumber']] = true;
}
if (empty($hash['name'])) {
$cohorts[$rownum]['errors'][] = new lang_string('namefieldempty', 'cohort');
}
if (!empty($hash['theme']) && !empty($CFG->allowcohortthemes)) {
$availablethemes = cohort_get_list_of_themes();
if (empty($availablethemes[$hash['theme']])) {
$cohorts[$rownum]['errors'][] = new lang_string('invalidtheme', 'cohort');
}
}
$cohorts[$rownum]['data'] = array_intersect_key($hash, $cohorts[0]['data']);
$haserrors = $haserrors || !empty($cohorts[$rownum]['errors']);
$haswarnings = $haswarnings || !empty($cohorts[$rownum]['warnings']);
}
if ($haserrors) {
$cohorts[0]['errors'][] = new lang_string('csvcontainserrors', 'cohort');
}
if ($haswarnings) {
$cohorts[0]['warnings'][] = new lang_string('csvcontainswarnings', 'cohort');
}
return $cohorts;
}
/**
* Cleans input data about one cohort.
*
* @param array $hash
*/
protected function clean_cohort_data(&$hash) {
foreach ($hash as $key => $value) {
switch ($key) {
case 'contextid': $hash[$key] = clean_param($value, PARAM_INT); break;
case 'name': $hash[$key] = core_text::substr(clean_param($value, PARAM_TEXT), 0, 254); break;
case 'idnumber': $hash[$key] = core_text::substr(clean_param($value, PARAM_RAW), 0, 254); break;
case 'description': $hash[$key] = clean_param($value, PARAM_RAW); break;
case 'descriptionformat': $hash[$key] = clean_param($value, PARAM_INT); break;
case 'visible':
$tempstr = trim(core_text::strtolower($value));
if ($tempstr === '') {
// Empty string is treated as "YES" (the default value for cohort visibility).
$hash[$key] = 1;
} else {
if ($tempstr === core_text::strtolower(get_string('no')) || $tempstr === 'n') {
// Special treatment for 'no' string that is not included in clean_param().
$value = 0;
}
$hash[$key] = clean_param($value, PARAM_BOOL) ? 1 : 0;
}
break;
case 'theme':
$hash[$key] = core_text::substr(clean_param($value, PARAM_TEXT), 0, 50);
break;
}
}
}
/**
* Determines in which context the particular cohort will be created
*
* @param array $hash
* @param context $defaultcontext
* @return array array of warning strings
*/
protected function resolve_context(&$hash, $defaultcontext) {
global $DB;
$warnings = array();
if (!empty($hash['contextid'])) {
// Contextid was specified, verify we can post there.
$contextoptions = $this->get_context_options();
if (!isset($contextoptions[$hash['contextid']])) {
$warnings[] = new lang_string('contextnotfound', 'cohort', $hash['contextid']);
$hash['contextid'] = $defaultcontext->id;
}
return $warnings;
}
if (!empty($hash['context'])) {
$systemcontext = context_system::instance();
if ((core_text::strtolower(trim($hash['context'])) ===
core_text::strtolower($systemcontext->get_context_name())) ||
('' . $hash['context'] === '' . $systemcontext->id)) {
// User meant system context.
$hash['contextid'] = $systemcontext->id;
$contextoptions = $this->get_context_options();
if (!isset($contextoptions[$hash['contextid']])) {
$warnings[] = new lang_string('contextnotfound', 'cohort', $hash['context']);
$hash['contextid'] = $defaultcontext->id;
}
} else {
// Assume it is a category.
$hash['category'] = trim($hash['context']);
}
}
if (!empty($hash['category_path'])) {
// We already have array with available categories, look up the value.
$contextoptions = $this->get_context_options();
if (!$hash['contextid'] = array_search($hash['category_path'], $contextoptions)) {
$warnings[] = new lang_string('categorynotfound', 'cohort', s($hash['category_path']));
$hash['contextid'] = $defaultcontext->id;
}
return $warnings;
}
if (!empty($hash['category'])) {
// Quick search by category path first.
// Do not issue warnings or return here, further we'll try to search by id or idnumber.
$contextoptions = $this->get_context_options();
if ($hash['contextid'] = array_search($hash['category'], $contextoptions)) {
return $warnings;
}
}
// Now search by category id or category idnumber.
if (!empty($hash['category_id'])) {
$field = 'id';
$value = clean_param($hash['category_id'], PARAM_INT);
} else if (!empty($hash['category_idnumber'])) {
$field = 'idnumber';
$value = $hash['category_idnumber'];
} else if (!empty($hash['category'])) {
$field = is_numeric($hash['category']) ? 'id' : 'idnumber';
$value = $hash['category'];
} else {
// No category field was specified, assume default category.
$hash['contextid'] = $defaultcontext->id;
return $warnings;
}
if (empty($this->categoriescache[$field][$value])) {
$record = $DB->get_record_sql("SELECT c.id, ctx.id contextid
FROM {context} ctx JOIN {course_categories} c ON ctx.contextlevel = ? AND ctx.instanceid = c.id
WHERE c.$field = ?", array(CONTEXT_COURSECAT, $value));
if ($record && ($contextoptions = $this->get_context_options()) && isset($contextoptions[$record->contextid])) {
$contextid = $record->contextid;
} else {
$warnings[] = new lang_string('categorynotfound', 'cohort', s($value));
$contextid = $defaultcontext->id;
}
// Next time when we can look up and don't search by this value again.
$this->categoriescache[$field][$value] = $contextid;
}
$hash['contextid'] = $this->categoriescache[$field][$value];
return $warnings;
}
}