Skip to content

Commit

Permalink
GHI535 Support for user identity fields in Download Responses
Browse files Browse the repository at this point in the history
  • Loading branch information
Emanoil Manoylov committed Nov 29, 2023
1 parent e8607f8 commit 2abf871
Show file tree
Hide file tree
Showing 6 changed files with 199 additions and 17 deletions.
5 changes: 5 additions & 0 deletions db/upgrade.php
Original file line number Diff line number Diff line change
Expand Up @@ -997,6 +997,11 @@ function xmldb_questionnaire_upgrade($oldversion=0) {
upgrade_mod_savepoint(true, 2022092200, 'questionnaire');
}

if ($oldversion < 2023112700) {
// Upgrade for downloadoptions - useridentityfields setting.
upgrade_mod_savepoint(true, 2023112700, 'questionnaire');
}

return true;
}

Expand Down
81 changes: 68 additions & 13 deletions questionnaire.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -3066,29 +3066,23 @@ public function survey_is_public_master() {
* @param array $questionsbyposition
* @param int $nbinfocols
* @param int $numrespcols
* @param int $showincompletes
* @param array $options
* @param array $identityfields
* @return array
*/
protected function process_csv_row(array &$row,
stdClass $resprow,
$currentgroupid,
array &$questionsbyposition,
$nbinfocols,
$numrespcols, $showincompletes = 0) {
$numrespcols,
$options,
$identityfields) {
global $DB;

static $config = null;
// If using an anonymous response, map users to unique user numbers so that number of unique anonymous users can be seen.
static $anonumap = [];

if ($config === null) {
$config = get_config('questionnaire', 'downloadoptions');
}
$options = empty($config) ? array() : explode(',', $config);
if ($showincompletes == 1) {
$options[] = 'complete';
}

$positioned = [];
$user = new stdClass();
foreach ($this->user_fields() as $userfield) {
Expand Down Expand Up @@ -3184,6 +3178,9 @@ protected function process_csv_row(array &$row,
if (in_array('complete', $options)) {
array_push($positioned, $resprow->complete);
}
foreach ($identityfields as $field) {
array_push($positioned, $resprow->$field);
}

for ($c = $nbinfocols; $c < $numrespcols; $c++) {
if (isset($row[$c])) {
Expand Down Expand Up @@ -3234,11 +3231,18 @@ public function generate_csv($currentgroupid, $rid='', $userid='', $choicecodes=
if (in_array($option, array('response', 'submitted', 'id'))) {
$columns[] = get_string($option, 'questionnaire');
$types[] = 0;
} else if ($option == 'useridentityfields') {
// Ignore option.
continue;
} else {
$columns[] = get_string($option);
$types[] = 1;
}
}
$identityfields = $this->get_identity_fields($options);
foreach ($identityfields as $field) {
$columns[] = \core_user\fields::get_display_name($field);
}
$nbinfocols = count($columns);

$idtocsvmap = array(
Expand Down Expand Up @@ -3469,6 +3473,7 @@ public function generate_csv($currentgroupid, $rid='', $userid='', $choicecodes=
if ($rankaverages) {
$averagerow = [];
}
$useridentityfields = [];
foreach ($allresponsesrs as $responserow) {
$rid = $responserow->rid;
$qid = $responserow->question_id;
Expand All @@ -3478,6 +3483,21 @@ public function generate_csv($currentgroupid, $rid='', $userid='', $choicecodes=
continue;
}

if (!empty($identityfields)) {
// Get identity fields for user.
if (isset($useridentityfields[$responserow->userid])) {
$customfields = $useridentityfields[$responserow->userid];
} else {
$customfields = self::get_user_identity_fields($this->context, $responserow->userid);
$useridentityfields[$responserow->userid] = $customfields;
}

// Set profile fields for user in response row.
foreach ($identityfields as $field) {
$responserow->{$field} = $customfields->{$field};
}
}

$question = $this->questions[$qid];
$qtype = intval($question->type_id);
if ($rankaverages) {
Expand All @@ -3494,7 +3514,7 @@ public function generate_csv($currentgroupid, $rid='', $userid='', $choicecodes=

if ($prevresprow !== false && $prevresprow->rid !== $rid) {
$output[] = $this->process_csv_row($row, $prevresprow, $currentgroupid, $questionsbyposition,
$nbinfocols, $numrespcols, $showincompletes);
$nbinfocols, $numrespcols, $options, $identityfields);
$row = [];
}

Expand Down Expand Up @@ -3576,7 +3596,7 @@ public function generate_csv($currentgroupid, $rid='', $userid='', $choicecodes=
if ($prevresprow !== false) {
// Add final row to output. May not exist if no response data was ever present.
$output[] = $this->process_csv_row($row, $prevresprow, $currentgroupid, $questionsbyposition,
$nbinfocols, $numrespcols, $showincompletes);
$nbinfocols, $numrespcols, $options, $identityfields);
}

// Add averages row if appropriate.
Expand Down Expand Up @@ -4121,4 +4141,39 @@ public function get_all_file_areas() {

return $areas;
}

/**
* Gets the identity fields.
*
* @param array $options
* @return array
*/
protected function get_identity_fields($options) {
$fields = !in_array('useridentityfields', $options) || $this->respondenttype == 'anonymous' ? [] :
\core_user\fields::get_identity_fields($this->context);
return $fields;
}

/**
* Gets the identity fields values for a user.
*
* @param object $context
* @param int $userid
* @return array
*/
public static function get_user_identity_fields($context, $userid) {
global $DB;

$fields = \core_user\fields::for_identity($context);
[
'selects' => $selects,
'joins' => $joins,
'params' => $params
] = (array)$fields->get_sql('u', false, '', '', false);
$sql = "SELECT $selects
FROM {user} u $joins
WHERE u.id = ?";
$row = $DB->get_record_sql($sql, array_merge($params, [$userid]));
return $row;
}
}
3 changes: 2 additions & 1 deletion settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@
'group' => get_string('group'),
'id' => get_string('id', 'questionnaire'),
'fullname' => get_string('fullname'),
'username' => get_string('username')
'username' => get_string('username'),
'useridentityfields' => get_string('showuseridentity', 'admin')
);

$settings->add(new admin_setting_configmultiselect('questionnaire/downloadoptions',
Expand Down
104 changes: 104 additions & 0 deletions tests/csvexport_test.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ private function get_csv_text(array $rows) {
return $lines;
}

/**
* Tests the CSV export.
*/
public function test_csvexport() {
$this->resetAfterTest();
$dg = $this->getDataGenerator();
Expand Down Expand Up @@ -79,6 +82,107 @@ public function test_csvexport() {
}
}

/**
* Tests the CSV export with identity fields and anonymous questionnaires.
*/
public function test_csvexport_identity_fields() {
global $DB;
$this->resetAfterTest();

$config = get_config('questionnaire', 'downloadoptions');
if (strpos($config, 'useridentityfields') === false) {
set_config('downloadoptions', "{$config},useridentityfields", 'questionnaire');
}

$dg = $this->getDataGenerator();
$qdg = $dg->get_plugin_generator('mod_questionnaire');
$profilefields = ['specialid' => 'Special id', 'staffno' => 'Staff number'];
$qdg->create_and_fully_populate(1, 2, 1, 1, $profilefields);

$user = $dg->create_user();
$this->setUser($user);
$roleid = $DB->get_field('role', 'id', ['shortname' => 'student']);

$questionnaires = $qdg->questionnaires();
foreach ($questionnaires as $item) {
list($course, $cm) = get_course_and_cm_from_instance($item->id, 'questionnaire', $item->course);

$this->do_test_csvexport_identity_fields($course, $cm, $user, $roleid, $profilefields, $item, false);
$this->do_test_csvexport_identity_fields($course, $cm, $user, $roleid, $profilefields, $item, true);
}
}

/**
* Tests the CSV export with identity fields for a questionnaire.
*
* @param object $course
* @param object $cm
* @param object $user
* @param int $roleid
* @param array $profilefields
* @param object $item
* @param bool $anonymous
* @throws coding_exception
* @throws dml_exception
* @throws moodle_exception
*/
private function do_test_csvexport_identity_fields($course, $cm, $user, $roleid, $profilefields, $item, $anonymous): void {
global $DB;

if ($anonymous) {
// Make questionnaire anonymous.
$row = new \stdClass();
$row->id = $item->id;
$row->respondenttype = 'anonymous';
$DB->update_record('questionnaire', $row);
}

$context = \context_course::instance($course->id);
role_assign($roleid, $user->id, $context);
assign_capability('moodle/site:viewuseridentity', CAP_ALLOW, $roleid, $context);

// Generate CSV output.
$questionnaire = new questionnaire($course, $cm, $item->id);
$output = $questionnaire->generate_csv(0, '', '', 0, 0, 1);

$this->assertNotNull($output);
$this->assertCount(3, $output);

// Check profile field columns.
$errortext = $anonymous ? 'exists' : 'missing';
$columns = $output[0];
$columns1 = [];
foreach ($profilefields as $field => $name) {
$col = array_search($name, $columns);
$this->assertEquals(!$anonymous, $col, "Profile field {$field} {$errortext}");
if (!$anonymous) {
$columns1[] = $col;
}
}

// Check profile field values.
for ($i = 1; $i < count($output); $i++) {
$columns2 = [];
foreach ($profilefields as $field => $name) {
$values = $output[$i];
$id = $field . ($i - 1);
$col = array_search($id, $values);
$this->assertEquals(!$anonymous, $col, "Profile field {$field} {$errortext}");
if (!$anonymous) {
$columns2[] = $col;
}
}

if (!$anonymous) {
// Check indexes of columns and values.
$this->assertEquals(count($columns1), count($columns2), "Indexes of columns and values");
for ($j = 0; $j < count($columns1); $j++) {
$this->assertEquals($columns1[$j], $columns2[$j], "Indexes of columns and values");
}
}
}
}

/**
* Return the expected output.
* @return string[]
Expand Down
21 changes: 19 additions & 2 deletions tests/generator/lib.php
Original file line number Diff line number Diff line change
Expand Up @@ -678,9 +678,10 @@ public function generate_response($questionnaire, $questions, $userid, $complete
* @param int $studentcount
* @param int $questionnairecount
* @param int $questionspertype
* @param array $profilefields in format ['<shortname>' => '<name>']
*/
public function create_and_fully_populate($coursecount = 4, $studentcount = 20, $questionnairecount = 2,
$questionspertype = 5) {
$questionspertype = 5, $profilefields = []) {
global $DB;

$dg = $this->datagenerator;
Expand All @@ -692,8 +693,24 @@ public function create_and_fully_populate($coursecount = 4, $studentcount = 20,
$courses = [];
$questionnaires = [];

if (!empty($profilefields)) {
// Create profile fields and set them to show for user identity.
$fields = [];
foreach ($profilefields as $field => $name) {
$dg->create_custom_profile_field(['datatype' => 'text',
'shortname' => $field, 'name' => $name]);
$fields[] = "profile_field_{$field}";
}
set_config('showuseridentity', implode(',', $fields));
}

for ($u = 0; $u < $studentcount; $u++) {
$students[] = $dg->create_user(['firstname' => 'Testy']);
$user = ['firstname' => 'Testy'];
// Set values for the profile fields.
foreach ($profilefields as $field => $name) {
$user["profile_field_{$field}"] = "{$field}{$u}";
}
$students[] = $dg->create_user($user);
}

$manplugin = enrol_get_plugin('manual');
Expand Down
2 changes: 1 addition & 1 deletion version.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

defined('MOODLE_INTERNAL') || die();

$plugin->version = 2022121600.01; // The current module version (Date: YYYYMMDDXX).
$plugin->version = 2023112700; // The current module version (Date: YYYYMMDDXX).
$plugin->requires = 2022112800.00; // Moodle version (4.1.0).

$plugin->component = 'mod_questionnaire';
Expand Down

0 comments on commit 2abf871

Please sign in to comment.