AutorÃa | Ultima modificación | Ver Log |
<?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/>.
/**
* Library of functions and constants for module questionnaire.
* @package mod_questionnaire
* @copyright 2016 Mike Churchward (mike.churchward@poetopensource.org)
* @author Mike Churchward
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/** This may no longer be needed. */
define('QUESTIONNAIRE_RESETFORM_RESET', 'questionnaire_reset_data_');
/** This may no longer be needed. */
define('QUESTIONNAIRE_RESETFORM_DROP', 'questionnaire_drop_questionnaire_');
/**
* Library supports implementation.
* @param string $feature
* @return bool|null
*/
function questionnaire_supports($feature) {
switch($feature) {
case FEATURE_BACKUP_MOODLE2:
return true;
case FEATURE_COMPLETION_TRACKS_VIEWS:
return false;
case FEATURE_COMPLETION_HAS_RULES:
return true;
case FEATURE_GRADE_HAS_GRADE:
return false;
case FEATURE_GRADE_OUTCOMES:
return false;
case FEATURE_GROUPINGS:
return true;
case FEATURE_GROUPS:
return true;
case FEATURE_MOD_INTRO:
return true;
case FEATURE_SHOW_DESCRIPTION:
return true;
case FEATURE_MOD_PURPOSE:
return MOD_PURPOSE_COMMUNICATION;
default:
return null;
}
}
/**
* Return any extra capabilities.
* @return array all other caps used in module
*/
function questionnaire_get_extra_capabilities() {
return array('moodle/site:accessallgroups');
}
/**
* Implementation of get_instance.
* @param int $questionnaireid
* @return false|mixed|stdClass
*/
function questionnaire_get_instance($questionnaireid) {
global $DB;
return $DB->get_record('questionnaire', array('id' => $questionnaireid));
}
/**
* Implementation of add_instance.
* @param stdClass $questionnaire
* @return bool|int
*/
function questionnaire_add_instance($questionnaire) {
// Given an object containing all the necessary data,
// (defined by the form in mod.html) this function
// will create a new instance and return the id number
// of the new instance.
global $DB, $CFG;
require_once($CFG->dirroot.'/mod/questionnaire/questionnaire.class.php');
require_once($CFG->dirroot.'/mod/questionnaire/locallib.php');
$copyfiles = false;
// Check the realm and set it to the survey if it's set.
if (empty($questionnaire->sid)) {
// Create a new survey.
$course = get_course($questionnaire->course);
$cm = new stdClass();
$qobject = new questionnaire($course, $cm, 0, $questionnaire);
if ($questionnaire->create == 'new-0') {
$sdata = new stdClass();
$sdata->name = $questionnaire->name;
$sdata->realm = 'private';
$sdata->title = $questionnaire->name;
$sdata->subtitle = '';
$sdata->info = '';
$sdata->theme = ''; // Theme is deprecated.
$sdata->thanks_page = '';
$sdata->thank_head = '';
$sdata->thank_body = '';
$sdata->email = '';
$sdata->feedbacknotes = '';
$sdata->courseid = $course->id;
if (!($sid = $qobject->survey_update($sdata))) {
throw new \moodle_exception('couldnotcreatenewsurvey', 'mod_questionnaire');
}
} else {
$copyid = explode('-', $questionnaire->create);
$copyrealm = $copyid[0];
$copyid = $copyid[1];
if (empty($qobject->survey)) {
$qobject->add_survey($copyid);
$qobject->add_questions($copyid);
}
// New questionnaires created as "use public" should not create a new survey instance.
if ($copyrealm == 'public') {
$sid = $copyid;
} else {
$sid = $qobject->sid = $qobject->survey_copy($course->id);
// All new questionnaires should be created as "private".
// Even if they are *copies* of public or template questionnaires.
$DB->set_field('questionnaire_survey', 'realm', 'private', array('id' => $sid));
// Need to copy any files from the old questionnaire instance to the new one.
$questionnaire->copyid = $copyid;
}
// If the survey has dependency data, need to set the questionnaire to allow dependencies.
if ($DB->count_records('questionnaire_dependency', ['surveyid' => $sid]) > 0) {
$questionnaire->navigate = 1;
}
}
$questionnaire->sid = $sid;
}
$questionnaire->timemodified = time();
if ($questionnaire->resume == '1') {
$questionnaire->resume = 1;
} else {
$questionnaire->resume = 0;
}
if (!$questionnaire->id = $DB->insert_record("questionnaire", $questionnaire)) {
return false;
}
questionnaire_set_events($questionnaire);
$completiontimeexpected = !empty($questionnaire->completionexpected) ? $questionnaire->completionexpected : null;
\core_completion\api::update_completion_date_event($questionnaire->coursemodule, 'questionnaire',
$questionnaire->id, $completiontimeexpected);
return $questionnaire->id;
}
/**
* Given an object containing all the necessary data, (defined by the form in mod.html) this function will update an existing
* instance with new data.
* @param stdClass $questionnaire
* @return bool
*/
function questionnaire_update_instance($questionnaire) {
global $DB, $CFG;
require_once($CFG->dirroot.'/mod/questionnaire/locallib.php');
// Check the realm and set it to the survey if its set.
if (!empty($questionnaire->sid) && !empty($questionnaire->realm)) {
$DB->set_field('questionnaire_survey', 'realm', $questionnaire->realm, array('id' => $questionnaire->sid));
}
$questionnaire->timemodified = time();
$questionnaire->id = $questionnaire->instance;
if ($questionnaire->resume == '1') {
$questionnaire->resume = 1;
} else {
$questionnaire->resume = 0;
}
// Get existing grade item.
questionnaire_grade_item_update($questionnaire);
questionnaire_set_events($questionnaire);
$completiontimeexpected = !empty($questionnaire->completionexpected) ? $questionnaire->completionexpected : null;
\core_completion\api::update_completion_date_event($questionnaire->coursemodule, 'questionnaire',
$questionnaire->id, $completiontimeexpected);
return $DB->update_record("questionnaire", $questionnaire);
}
/**
* Given an ID of an instance of this module, this function will permanently delete the instance and any data that depends on it.
* @param int $id
* @return bool
*/
function questionnaire_delete_instance($id) {
global $DB, $CFG;
require_once($CFG->dirroot.'/mod/questionnaire/locallib.php');
if (! $questionnaire = $DB->get_record('questionnaire', array('id' => $id))) {
return false;
}
$result = true;
if ($events = $DB->get_records('event', array("modulename" => 'questionnaire', "instance" => $questionnaire->id))) {
foreach ($events as $event) {
$event = calendar_event::load($event);
$event->delete();
}
}
if (! $DB->delete_records('questionnaire', array('id' => $questionnaire->id))) {
$result = false;
}
if ($survey = $DB->get_record('questionnaire_survey', array('id' => $questionnaire->sid))) {
// If this survey is owned by this course, delete all of the survey records and responses.
if ($survey->courseid == $questionnaire->course) {
$result = $result && questionnaire_delete_survey($questionnaire->sid, $questionnaire->id);
}
}
return $result;
}
/**
* Add a get_coursemodule_info function in case any questionnaire type wants to add 'extra' information
* for the course (see resource).
*
* Given a course_module object, this function returns any "extra" information that may be needed
* when printing this activity in a course listing. See get_array_of_activities() in course/lib.php.
*
* @param stdClass $coursemodule The coursemodule object (record).
* @return cached_cm_info An object on information that the courses
* will know about (most noticeably, an icon).
*/
function questionnaire_get_coursemodule_info($coursemodule) {
global $DB;
$questionnaire = $DB->get_record('questionnaire',
array('id' => $coursemodule->instance), 'id, name, intro, introformat,
completionsubmit');
if (!$questionnaire) {
return null;
}
$info = new cached_cm_info();
$info->customdata = (object)[];
// Populate the custom completion rules as key => value pairs, but only if the completion mode is 'automatic'.
if ($coursemodule->completion == COMPLETION_TRACKING_AUTOMATIC) {
$info->customdata->customcompletionrules['completionsubmit'] = $questionnaire->completionsubmit;
}
return $info;
}
/**
* Return a small object with summary information about what a user has done with a given particular instance of this module.
* Used for user activity reports.
* $return->time = the time they did it
* $return->info = a short text description.
* $course and $mod are unused, but API requires them. Suppress PHPMD warning.
* @param stdClass $course
* @param stdClass $user
* @param stdClass $mod
* @param stdClass $questionnaire
* @return stdClass
*/
function questionnaire_user_outline($course, $user, $mod, $questionnaire) {
global $CFG;
require_once($CFG->dirroot . '/mod/questionnaire/locallib.php');
$result = new stdClass();
if ($responses = questionnaire_get_user_responses($questionnaire->id, $user->id, true)) {
$n = count($responses);
if ($n == 1) {
$result->info = $n.' '.get_string("response", "questionnaire");
} else {
$result->info = $n.' '.get_string("responses", "questionnaire");
}
$lastresponse = array_pop($responses);
$result->time = $lastresponse->submitted;
} else {
$result->info = get_string("noresponses", "questionnaire");
}
return $result;
}
/**
* Print a detailed representation of what a user has done with a given particular instance of this module, for user
* activity reports.
* $course and $mod are unused, but API requires them. Suppress PHPMD warning.
* @param stdClass $course
* @param stdClass $user
* @param stdClass $mod
* @param stdClass $questionnaire
* @return bool
*/
function questionnaire_user_complete($course, $user, $mod, $questionnaire) {
global $CFG;
require_once($CFG->dirroot . '/mod/questionnaire/locallib.php');
if ($responses = questionnaire_get_user_responses($questionnaire->id, $user->id, false)) {
foreach ($responses as $response) {
if ($response->complete == 'y') {
echo get_string('submitted', 'questionnaire').' '.userdate($response->submitted).'<br />';
} else {
echo get_string('attemptstillinprogress', 'questionnaire').' '.userdate($response->submitted).'<br />';
}
}
} else {
print_string('noresponses', 'questionnaire');
}
return true;
}
/**
* Given a course and a time, this module should find recent activity that has occurred in questionnaire activities and print it
* out.
* Return true if there was output, or false is there was none.
* $course, $isteacher and $timestart are unused, but API requires them. Suppress PHPMD warning.
* @param stdClass $course
* @param bool $isteacher
* @param int $timestart
* @return false
*/
function questionnaire_print_recent_activity($course, $isteacher, $timestart) {
return false; // True if anything was printed, otherwise false.
}
/**
* Must return an array of grades for a given instance of this module, indexed by user. It also returns a maximum allowed grade.
* $questionnaireid is unused, but API requires it. Suppress PHPMD warning.
* @param int $questionnaireid
* @return null
*/
function questionnaire_grades($questionnaireid) {
return null;
}
/**
* Return grade for given user or all users.
*
* @param stdClass $questionnaire
* @param int $userid optional user id, 0 means all users
* @return array array of grades, false if none
*/
function questionnaire_get_user_grades($questionnaire, $userid=0) {
global $DB;
$params = array();
$usersql = '';
if (!empty($userid)) {
$usersql = "AND u.id = ?";
$params[] = $userid;
}
$sql = "SELECT r.id, u.id AS userid, r.grade AS rawgrade, r.submitted AS dategraded, r.submitted AS datesubmitted
FROM {user} u, {questionnaire_response} r
WHERE u.id = r.userid AND r.questionnaireid = $questionnaire->id AND r.complete = 'y' $usersql";
return $DB->get_records_sql($sql, $params) ?? [];
}
/**
* Update grades by firing grade_updated event.
* $nullifnone is unused, but API requires it. Suppress PHPMD warning.
* @param stdClass $questionnaire
* @param int $userid
* @param bool $nullifnone
*/
function questionnaire_update_grades($questionnaire=null, $userid=0, $nullifnone=true) {
global $CFG, $DB;
if (!function_exists('grade_update')) { // Workaround for buggy PHP versions.
require_once($CFG->libdir.'/gradelib.php');
}
if ($questionnaire != null) {
if ($graderecs = questionnaire_get_user_grades($questionnaire, $userid)) {
$grades = array();
foreach ($graderecs as $v) {
if (!isset($grades[$v->userid])) {
$grades[$v->userid] = new stdClass();
if ($v->rawgrade == -1) {
$grades[$v->userid]->rawgrade = null;
} else {
$grades[$v->userid]->rawgrade = $v->rawgrade;
}
$grades[$v->userid]->userid = $v->userid;
} else if (isset($grades[$v->userid]) && ($v->rawgrade > $grades[$v->userid]->rawgrade)) {
$grades[$v->userid]->rawgrade = $v->rawgrade;
}
}
questionnaire_grade_item_update($questionnaire, $grades);
} else {
questionnaire_grade_item_update($questionnaire);
}
} else {
$sql = "SELECT q.*, cm.idnumber as cmidnumber, q.course as courseid
FROM {questionnaire} q, {course_modules} cm, {modules} m
WHERE m.name='questionnaire' AND m.id=cm.module AND cm.instance=q.id";
if ($rs = $DB->get_recordset_sql($sql)) {
foreach ($rs as $questionnaire) {
if ($questionnaire->grade != 0) {
questionnaire_update_grades($questionnaire);
} else {
questionnaire_grade_item_update($questionnaire);
}
}
$rs->close();
}
}
}
/**
* Create grade item for given questionnaire
*
* @param stdClass $questionnaire object with extra cmidnumber
* @param mixed $grades optional array/object of grade(s); 'reset' means reset grades in gradebook
* @return int 0 if ok, error code otherwise
*/
function questionnaire_grade_item_update($questionnaire, $grades = null) {
global $CFG;
if (!function_exists('grade_update')) { // Workaround for buggy PHP versions.
require_once($CFG->libdir.'/gradelib.php');
}
if (!isset($questionnaire->courseid)) {
$questionnaire->courseid = $questionnaire->course;
}
if ($questionnaire->cmidnumber != '') {
$params = array('itemname' => $questionnaire->name, 'idnumber' => $questionnaire->cmidnumber);
} else {
$params = array('itemname' => $questionnaire->name);
}
if ($questionnaire->grade > 0) {
$params['gradetype'] = GRADE_TYPE_VALUE;
$params['grademax'] = $questionnaire->grade;
$params['grademin'] = 0;
} else if ($questionnaire->grade < 0) {
$params['gradetype'] = GRADE_TYPE_SCALE;
$params['scaleid'] = -$questionnaire->grade;
} else if ($questionnaire->grade == 0) { // No Grade..be sure to delete the grade item if it exists.
$grades = null;
$params = array('deleted' => 1);
} else {
$params = null; // Allow text comments only.
}
if ($grades === 'reset') {
$params['reset'] = true;
$grades = null;
}
return grade_update('mod/questionnaire', $questionnaire->courseid, 'mod', 'questionnaire',
$questionnaire->id, 0, $grades, $params);
}
/**
* This function returns if a scale is being used by one questionnaire
* it it has support for grading and scales. Commented code should be
* modified if necessary. See forum, glossary or journal modules
* as reference.
* @param int $questionnaireid
* @param int $scaleid
* @return boolean True if the scale is used by any questionnaire
*
* Function parameters are unused, but API requires them. Suppress PHPMD warning.
*/
function questionnaire_scale_used ($questionnaireid, $scaleid) {
return false;
}
/**
* Checks if scale is being used by any instance of questionnaire
*
* This is used to find out if scale used anywhere
* @param int $scaleid
* @return boolean True if the scale is used by any questionnaire
*
* Function parameters are unused, but API requires them. Suppress PHPMD warning.
*/
function questionnaire_scale_used_anywhere($scaleid) {
return false;
}
/**
* Serves the questionnaire attachments. Implements needed access control ;-)
*
* @param stdClass $course
* @param stdClass $cm
* @param stdClass $context
* @param string $filearea
* @param array $args
* @param bool $forcedownload
* @return bool false if file not found, does not return if found - justsend the file
*
* $forcedownload is unused, but API requires it. Suppress PHPMD warning.
*/
function questionnaire_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload) {
global $DB;
if ($context->contextlevel != CONTEXT_MODULE) {
return false;
}
require_course_login($course, true, $cm);
$fileareas = ['intro', 'info', 'thankbody', 'question', 'feedbacknotes', 'sectionheading', 'feedback'];
if (!in_array($filearea, $fileareas)) {
return false;
}
$componentid = (int)array_shift($args);
if ($filearea == 'question') {
if (!$DB->record_exists('questionnaire_question', ['id' => $componentid])) {
return false;
}
} else if ($filearea == 'sectionheading') {
if (!$DB->record_exists('questionnaire_fb_sections', ['id' => $componentid])) {
return false;
}
} else if ($filearea == 'feedback') {
if (!$DB->record_exists('questionnaire_feedback', ['id' => $componentid])) {
return false;
}
} else {
if (!$DB->record_exists('questionnaire_survey', ['id' => $componentid])) {
return false;
}
}
if (!$DB->record_exists('questionnaire', ['id' => $cm->instance])) {
return false;
}
$fs = get_file_storage();
$relativepath = implode('/', $args);
$fullpath = "/$context->id/mod_questionnaire/$filearea/$componentid/$relativepath";
if (!($file = $fs->get_file_by_hash(sha1($fullpath))) || $file->is_directory()) {
return false;
}
// Finally send the file.
send_stored_file($file, 0, 0, true); // Download MUST be forced - security!
}
/**
* Adds module specific settings to the settings block
*
* @param settings_navigation $settings The settings navigation object
* @param navigation_node $questionnairenode The node to add module settings to
*
* $settings is unused, but API requires it. Suppress PHPMD warning.
*/
function questionnaire_extend_settings_navigation(settings_navigation $settings, navigation_node $questionnairenode) {
global $DB, $USER, $CFG;
$individualresponse = optional_param('individualresponse', false, PARAM_INT);
$rid = optional_param('rid', false, PARAM_INT); // Response id.
$currentgroupid = optional_param('group', 0, PARAM_INT); // Group id.
require_once($CFG->dirroot.'/mod/questionnaire/questionnaire.class.php');
$cm = $settings->get_page()->cm;
$context = $cm->context;
$cmid = $cm->id;
$course = $settings->get_page()->course;
if (! $questionnaire = $DB->get_record("questionnaire", array("id" => $cm->instance))) {
throw new \moodle_exception('invalidcoursemodule', 'mod_questionnaire');
}
$courseid = $course->id;
$questionnaire = new questionnaire($course, $cm, 0, $questionnaire);
if ($owner = $DB->get_field('questionnaire_survey', 'courseid', ['id' => $questionnaire->sid])) {
$owner = (trim($owner) == trim($courseid));
} else {
$owner = true;
}
// On view page, currentgroupid is not yet sent as an optional_param, so get it.
$groupmode = groups_get_activity_groupmode($cm, $course);
if ($groupmode > 0 && $currentgroupid == 0) {
$currentgroupid = groups_get_activity_group($questionnaire->cm);
if (!groups_is_member($currentgroupid, $USER->id)) {
$currentgroupid = 0;
}
}
// We want to add these new nodes after the Edit settings node, and before the
// Locally assigned roles node. Of course, both of those are controlled by capabilities.
$keys = $questionnairenode->get_children_key_list();
$beforekey = null;
$i = array_search('modedit', $keys);
if (($i === false) && array_key_exists(0, $keys)) {
$beforekey = $keys[0];
} else if (array_key_exists($i + 1, $keys)) {
$beforekey = $keys[$i + 1];
}
if (has_capability('mod/questionnaire:manage', $context) && $owner) {
$url = '/mod/questionnaire/qsettings.php';
$node = navigation_node::create(get_string('advancedsettings'),
new moodle_url($url, array('id' => $cmid)),
navigation_node::TYPE_SETTING, null, 'advancedsettings',
new pix_icon('t/edit', ''));
$questionnairenode->add_node($node, $beforekey);
}
if (has_capability('mod/questionnaire:editquestions', $context) && $owner) {
$url = '/mod/questionnaire/questions.php';
$node = navigation_node::create(get_string('questions', 'questionnaire'),
new moodle_url($url, array('id' => $cmid)),
navigation_node::TYPE_SETTING, null, 'questions',
new pix_icon('t/edit', ''));
$questionnairenode->add_node($node, $beforekey);
}
if (has_capability('mod/questionnaire:editquestions', $context) && $owner) {
$url = '/mod/questionnaire/feedback.php';
$node = navigation_node::create(get_string('feedback', 'questionnaire'),
new moodle_url($url, array('id' => $cmid)),
navigation_node::TYPE_SETTING, null, 'feedback',
new pix_icon('t/edit', ''));
$questionnairenode->add_node($node, $beforekey);
}
if (has_capability('mod/questionnaire:preview', $context)) {
$url = '/mod/questionnaire/preview.php';
$node = navigation_node::create(get_string('preview_label', 'questionnaire'),
new moodle_url($url, array('id' => $cmid)),
navigation_node::TYPE_SETTING, null, 'preview',
new pix_icon('t/preview', ''));
$questionnairenode->add_node($node, $beforekey);
}
if ($questionnaire->user_can_take($USER->id)) {
$url = '/mod/questionnaire/complete.php';
if ($questionnaire->user_has_saved_response($USER->id)) {
$args = ['id' => $cmid, 'resume' => 1];
$text = get_string('resumesurvey', 'questionnaire');
} else {
$args = ['id' => $cmid];
$text = get_string('answerquestions', 'questionnaire');
}
$node = navigation_node::create($text, new moodle_url($url, $args),
navigation_node::TYPE_SETTING, null, '', new pix_icon('i/info', 'answerquestions'));
$questionnairenode->add_node($node, $beforekey);
}
$usernumresp = $questionnaire->count_submissions($USER->id);
if ($questionnaire->capabilities->readownresponses && ($usernumresp > 0)) {
$url = '/mod/questionnaire/myreport.php';
if ($usernumresp > 1) {
$urlargs = array('instance' => $questionnaire->id, 'userid' => $USER->id,
'byresponse' => 0, 'action' => 'summary', 'group' => $currentgroupid);
$node = navigation_node::create(get_string('yourresponses', 'questionnaire'),
new moodle_url($url, $urlargs), navigation_node::TYPE_SETTING, null, 'yourresponses');
$myreportnode = $questionnairenode->add_node($node, $beforekey);
$urlargs = array('instance' => $questionnaire->id, 'userid' => $USER->id,
'byresponse' => 0, 'action' => 'summary', 'group' => $currentgroupid);
$myreportnode->add(get_string('summary', 'questionnaire'), new moodle_url($url, $urlargs));
$urlargs = array('instance' => $questionnaire->id, 'userid' => $USER->id,
'byresponse' => 1, 'action' => 'vresp', 'group' => $currentgroupid);
$byresponsenode = $myreportnode->add(get_string('viewindividualresponse', 'questionnaire'),
new moodle_url($url, $urlargs));
$urlargs = array('instance' => $questionnaire->id, 'userid' => $USER->id,
'byresponse' => 0, 'action' => 'vall', 'group' => $currentgroupid);
$myreportnode->add(get_string('myresponses', 'questionnaire'), new moodle_url($url, $urlargs));
if ($questionnaire->capabilities->downloadresponses) {
$urlargs = array('instance' => $questionnaire->id, 'user' => $USER->id,
'action' => 'dwnpg', 'group' => $currentgroupid);
$myreportnode->add(get_string('downloadtextformat', 'questionnaire'),
new moodle_url('/mod/questionnaire/report.php', $urlargs));
}
} else {
$urlargs = array('instance' => $questionnaire->id, 'userid' => $USER->id,
'byresponse' => 1, 'action' => 'vresp', 'group' => $currentgroupid);
$node = navigation_node::create(get_string('yourresponse', 'questionnaire'),
new moodle_url($url, $urlargs), navigation_node::TYPE_SETTING, null, 'yourresponse');
$myreportnode = $questionnairenode->add_node($node, $beforekey);
}
}
// If questionnaire is set to separate groups, prevent user who is not member of any group
// and is not a non-editing teacher to view All responses.
if ($questionnaire->can_view_all_responses($usernumresp)) {
$url = '/mod/questionnaire/report.php';
$node = navigation_node::create(get_string('viewallresponses', 'questionnaire'),
new moodle_url($url, array('instance' => $questionnaire->id, 'action' => 'vall')),
navigation_node::TYPE_SETTING, null, 'vall');
$reportnode = $questionnairenode->add_node($node, $beforekey);
if ($questionnaire->capabilities->viewsingleresponse) {
$summarynode = $reportnode->add(get_string('summary', 'questionnaire'),
new moodle_url('/mod/questionnaire/report.php',
array('instance' => $questionnaire->id, 'action' => 'vall')));
} else {
$summarynode = $reportnode;
}
$summarynode->add(get_string('order_default', 'questionnaire'),
new moodle_url('/mod/questionnaire/report.php',
array('instance' => $questionnaire->id, 'action' => 'vall', 'group' => $currentgroupid)));
$summarynode->add(get_string('order_ascending', 'questionnaire'),
new moodle_url('/mod/questionnaire/report.php',
array('instance' => $questionnaire->id, 'action' => 'vallasort', 'group' => $currentgroupid)));
$summarynode->add(get_string('order_descending', 'questionnaire'),
new moodle_url('/mod/questionnaire/report.php',
array('instance' => $questionnaire->id, 'action' => 'vallarsort', 'group' => $currentgroupid)));
if ($questionnaire->capabilities->deleteresponses) {
$summarynode->add(get_string('deleteallresponses', 'questionnaire'),
new moodle_url('/mod/questionnaire/report.php',
array('instance' => $questionnaire->id, 'action' => 'delallresp', 'group' => $currentgroupid)));
}
if ($questionnaire->capabilities->downloadresponses) {
$summarynode->add(get_string('downloadtextformat', 'questionnaire'),
new moodle_url('/mod/questionnaire/report.php',
array('instance' => $questionnaire->id, 'action' => 'dwnpg', 'group' => $currentgroupid)));
}
if ($questionnaire->capabilities->viewsingleresponse) {
$byresponsenode = $reportnode->add(get_string('viewbyresponse', 'questionnaire'),
new moodle_url('/mod/questionnaire/report.php',
array('instance' => $questionnaire->id, 'action' => 'vresp', 'byresponse' => 1, 'group' => $currentgroupid)));
$byresponsenode->add(get_string('view', 'questionnaire'),
new moodle_url('/mod/questionnaire/report.php',
array('instance' => $questionnaire->id, 'action' => 'vresp', 'byresponse' => 1, 'group' => $currentgroupid)));
if ($individualresponse) {
$byresponsenode->add(get_string('deleteresp', 'questionnaire'),
new moodle_url('/mod/questionnaire/report.php',
array('instance' => $questionnaire->id, 'action' => 'dresp', 'byresponse' => 1,
'rid' => $rid, 'group' => $currentgroupid, 'individualresponse' => 1)));
}
}
}
$canviewgroups = true;
$groupmode = groups_get_activity_groupmode($cm, $course);
if ($groupmode == 1) {
$canviewgroups = groups_has_membership($cm, $USER->id);
}
$canviewallgroups = has_capability('moodle/site:accessallgroups', $context);
if ($questionnaire->capabilities->viewsingleresponse && ($canviewallgroups || $canviewgroups)) {
$url = '/mod/questionnaire/show_nonrespondents.php';
$node = navigation_node::create(get_string('show_nonrespondents', 'questionnaire'),
new moodle_url($url, array('id' => $cmid)),
navigation_node::TYPE_SETTING, null, 'nonrespondents');
$questionnairenode->add_node($node, $beforekey);
}
}
// Any other questionnaire functions go here. Each of them must have a name that
// starts with questionnaire_.
/**
* Return the view actions.
* @return string[]
*/
function questionnaire_get_view_actions() {
return array('view', 'view all');
}
/**
* Return the post actions.
* @return string[]
*/
function questionnaire_get_post_actions() {
return array('submit', 'update');
}
/**
* Return the recent activity.
* @param array $activities
* @param int $index
* @param int $timestart
* @param int $courseid
* @param int $cmid
* @param int $userid
* @param int $groupid
* @return mixed|void
*/
function questionnaire_get_recent_mod_activity(&$activities, &$index, $timestart,
$courseid, $cmid, $userid = 0, $groupid = 0) {
global $CFG, $COURSE, $USER, $DB;
require_once($CFG->dirroot . '/mod/questionnaire/locallib.php');
require_once($CFG->dirroot.'/mod/questionnaire/questionnaire.class.php');
if ($COURSE->id == $courseid) {
$course = $COURSE;
} else {
$course = $DB->get_record('course', ['id' => $courseid]);
}
$modinfo = get_fast_modinfo($course);
$cm = $modinfo->cms[$cmid];
$questionnaire = $DB->get_record('questionnaire', ['id' => $cm->instance]);
$questionnaire = new questionnaire($course, $cm, 0, $questionnaire);
$context = context_module::instance($cm->id);
$grader = has_capability('mod/questionnaire:viewsingleresponse', $context);
// If this is a copy of a public questionnaire whose original is located in another course,
// current user (teacher) cannot view responses.
if ($grader) {
// For a public questionnaire, look for the original public questionnaire that it is based on.
if (!$questionnaire->survey_is_public_master()) {
// For a public questionnaire, look for the original public questionnaire that it is based on.
$originalquestionnaire = $DB->get_record('questionnaire',
['sid' => $questionnaire->survey->id, 'course' => $questionnaire->survey->courseid]);
$cmoriginal = get_coursemodule_from_instance("questionnaire", $originalquestionnaire->id,
$questionnaire->survey->courseid);
$contextoriginal = context_course::instance($questionnaire->survey->courseid, MUST_EXIST);
if (!has_capability('mod/questionnaire:viewsingleresponse', $contextoriginal)) {
$tmpactivity = new stdClass();
$tmpactivity->type = 'questionnaire';
$tmpactivity->cmid = $cm->id;
$tmpactivity->cannotview = true;
$tmpactivity->anonymous = false;
$activities[$index++] = $tmpactivity;
return $activities;
}
}
}
if ($userid) {
$userselect = "AND u.id = :userid";
$params['userid'] = $userid;
} else {
$userselect = '';
}
if ($groupid) {
$groupselect = 'AND gm.groupid = :groupid';
$groupjoin = 'JOIN {groups_members} gm ON gm.userid=u.id';
$params['groupid'] = $groupid;
} else {
$groupselect = '';
$groupjoin = '';
}
$params['timestart'] = $timestart;
$params['questionnaireid'] = $questionnaire->id;
$ufields = user_picture::fields('u', null, 'useridagain');
if (!$attempts = $DB->get_records_sql("
SELECT qr.*,
{$ufields}
FROM {questionnaire_response} qr
JOIN {user} u ON u.id = qr.userid
$groupjoin
WHERE qr.submitted > :timestart
AND qr.questionnaireid = :questionnaireid
$userselect
$groupselect
ORDER BY qr.submitted ASC", $params)) {
return;
}
$accessallgroups = has_capability('moodle/site:accessallgroups', $context);
$viewfullnames = has_capability('moodle/site:viewfullnames', $context);
$groupmode = groups_get_activity_groupmode($cm, $course);
$usersgroups = null;
$aname = format_string($cm->name, true);
$userattempts = array();
foreach ($attempts as $attempt) {
if ($questionnaire->respondenttype != 'anonymous') {
if (!isset($userattempts[$attempt->lastname])) {
$userattempts[$attempt->lastname] = 1;
} else {
$userattempts[$attempt->lastname]++;
}
}
if ($attempt->userid != $USER->id) {
if (!$grader) {
// View complete individual responses permission required.
continue;
}
if (($groupmode == SEPARATEGROUPS) && !$accessallgroups) {
if ($usersgroups === null) {
$usersgroups = groups_get_all_groups($course->id,
$attempt->userid, $cm->groupingid);
if (is_array($usersgroups)) {
$usersgroups = array_keys($usersgroups);
} else {
$usersgroups = array();
}
}
if (!array_intersect($usersgroups, $modinfo->groups[$cm->id])) {
continue;
}
}
}
$tmpactivity = new stdClass();
$tmpactivity->type = 'questionnaire';
$tmpactivity->cmid = $cm->id;
$tmpactivity->cminstance = $cm->instance;
// Current user is admin - or teacher enrolled in original public course.
if (isset($cmoriginal)) {
$tmpactivity->cminstance = $cmoriginal->instance;
}
$tmpactivity->cannotview = false;
$tmpactivity->anonymous = false;
$tmpactivity->name = $aname;
$tmpactivity->sectionnum = $cm->sectionnum;
$tmpactivity->timestamp = $attempt->submitted;
$tmpactivity->groupid = $groupid;
if (isset($userattempts[$attempt->lastname])) {
$tmpactivity->nbattempts = $userattempts[$attempt->lastname];
}
$tmpactivity->content = new stdClass();
$tmpactivity->content->attemptid = $attempt->id;
$userfields = explode(',', user_picture::fields());
$tmpactivity->user = new stdClass();
foreach ($userfields as $userfield) {
if ($userfield == 'id') {
$tmpactivity->user->{$userfield} = $attempt->userid;
} else {
if (!empty($attempt->{$userfield})) {
$tmpactivity->user->{$userfield} = $attempt->{$userfield};
} else {
$tmpactivity->user->{$userfield} = null;
}
}
}
if ($questionnaire->respondenttype != 'anonymous') {
$tmpactivity->user->fullname = fullname($attempt, $viewfullnames);
} else {
$tmpactivity->user = '';
unset ($tmpactivity->user);
$tmpactivity->anonymous = true;
}
$activities[$index++] = $tmpactivity;
}
}
/**
* Prints all users who have completed a specified questionnaire since a given time
*
* @param stdClass $activity
* @param int $courseid
* @param string $detail not used but needed for compability
* @param array $modnames
* @return void Output is echo'd
*
* $details and $modenames are unused, but API requires them. Suppress PHPMD warning.
*/
function questionnaire_print_recent_mod_activity($activity, $courseid, $detail, $modnames) {
global $OUTPUT;
// If the questionnaire is "anonymous", then $activity->user won't have been set, so do not display respondent info.
if ($activity->anonymous) {
$stranonymous = ' ('.get_string('anonymous', 'questionnaire').')';
$activity->nbattempts = '';
} else {
$stranonymous = '';
}
// Current user cannot view responses to public questionnaire.
if ($activity->cannotview) {
$strcannotview = get_string('cannotviewpublicresponses', 'questionnaire');
}
echo html_writer::start_tag('div');
echo html_writer::start_tag('span', array('class' => 'clearfix',
'style' => 'margin-top:0px; background-color: white; display: inline-block;'));
if (!$activity->anonymous && !$activity->cannotview) {
echo html_writer::tag('div', $OUTPUT->user_picture($activity->user, array('courseid' => $courseid)),
array('style' => 'float: left; padding-right: 10px;'));
}
if (!$activity->cannotview) {
echo html_writer::start_tag('div');
echo html_writer::start_tag('div');
$urlparams = array('action' => 'vresp', 'instance' => $activity->cminstance,
'group' => $activity->groupid, 'rid' => $activity->content->attemptid, 'individualresponse' => 1);
$context = context_module::instance($activity->cmid);
if (has_capability('mod/questionnaire:viewsingleresponse', $context)) {
$report = 'report.php';
} else {
$report = 'myreport.php';
}
echo html_writer::tag('a', get_string('response', 'questionnaire').' '.$activity->nbattempts.$stranonymous,
array('href' => new moodle_url('/mod/questionnaire/'.$report, $urlparams)));
echo html_writer::end_tag('div');
} else {
echo html_writer::start_tag('div');
echo html_writer::start_tag('div');
echo html_writer::tag('div', $strcannotview);
echo html_writer::end_tag('div');
}
if (!$activity->anonymous && !$activity->cannotview) {
$url = new moodle_url('/user/view.php', array('course' => $courseid, 'id' => $activity->user->id));
$name = $activity->user->fullname;
$link = html_writer::link($url, $name);
echo html_writer::start_tag('div', array('class' => 'user'));
echo $link .' - '. userdate($activity->timestamp);
echo html_writer::end_tag('div');
}
echo html_writer::end_tag('div');
echo html_writer::end_tag('span');
echo html_writer::end_tag('div');
return;
}
/**
* Prints questionnaire summaries on 'My home' page
*
* Prints questionnaire name, due date and attempt information on
* questionnaires that have a deadline that has not already passed
* and it is available for taking.
*
* @param array $courses An array of course objects to get questionnaire instances from
* @param array $htmlarray Store overview output array( course ID => 'questionnaire' => HTML output )
* @return void
*/
function questionnaire_print_overview($courses, &$htmlarray) {
global $USER, $CFG, $DB, $OUTPUT;
require_once($CFG->dirroot . '/mod/questionnaire/locallib.php');
if (!$questionnaires = get_all_instances_in_courses('questionnaire', $courses)) {
return;
}
// Get Necessary Strings.
$strquestionnaire = get_string('modulename', 'questionnaire');
$strnotattempted = get_string('noattempts', 'questionnaire');
$strattempted = get_string('attempted', 'questionnaire');
$strsavedbutnotsubmitted = get_string('savedbutnotsubmitted', 'questionnaire');
$now = time();
foreach ($questionnaires as $questionnaire) {
// The questionnaire has a deadline.
if (($questionnaire->closedate != 0)
// And it is before the deadline has been met.
&& ($questionnaire->closedate >= $now)
// And the questionnaire is available.
&& (($questionnaire->opendate == 0) || ($questionnaire->opendate <= $now))) {
if (!$questionnaire->visible) {
$class = ' class="dimmed"';
} else {
$class = '';
}
$str = $OUTPUT->box("$strquestionnaire:
<a$class href=\"$CFG->wwwroot/mod/questionnaire/view.php?id=$questionnaire->coursemodule\">".
format_string($questionnaire->name).'</a>', 'name');
// Deadline.
$str .= $OUTPUT->box(get_string('closeson', 'questionnaire', userdate($questionnaire->closedate)), 'info');
$attempts = $DB->get_records('questionnaire_response',
['questionnaireid' => $questionnaire->id, 'userid' => $USER->id, 'complete' => 'y']) ?? [];
$nbattempts = count($attempts);
// Do not display a questionnaire as due if it can only be sumbitted once and it has already been submitted!
if ($nbattempts != 0 && $questionnaire->qtype == QUESTIONNAIREONCE) {
continue;
}
// Attempt information.
if (has_capability('mod/questionnaire:manage', context_module::instance($questionnaire->coursemodule))) {
// Number of user attempts.
$attempts = $DB->count_records('questionnaire_response',
['questionnaireid' => $questionnaire->id, 'complete' => 'y']);
$str .= $OUTPUT->box(get_string('numattemptsmade', 'questionnaire', $attempts), 'info');
} else {
if ($responses = questionnaire_get_user_responses($questionnaire->id, $USER->id, false)) {
foreach ($responses as $response) {
if ($response->complete == 'y') {
$str .= $OUTPUT->box($strattempted, 'info');
break;
} else {
$str .= $OUTPUT->box($strsavedbutnotsubmitted, 'info');
}
}
} else {
$str .= $OUTPUT->box($strnotattempted, 'info');
}
}
$str = $OUTPUT->box($str, 'questionnaire overview');
if (empty($htmlarray[$questionnaire->course]['questionnaire'])) {
$htmlarray[$questionnaire->course]['questionnaire'] = $str;
} else {
$htmlarray[$questionnaire->course]['questionnaire'] .= $str;
}
}
}
}
/**
* Implementation of the function for printing the form elements that control
* whether the course reset functionality affects the questionnaire.
*
* @param stdClass $mform the course reset form that is being built.
*/
function questionnaire_reset_course_form_definition($mform) {
$mform->addElement('header', 'questionnaireheader', get_string('modulenameplural', 'questionnaire'));
$mform->addElement('advcheckbox', 'reset_questionnaire',
get_string('removeallquestionnaireattempts', 'questionnaire'));
}
/**
* Course reset form defaults.
* @param stdClass $course
* @return array the defaults.
*
* Function parameters are unused, but API requires them. Suppress PHPMD warning.
*/
function questionnaire_reset_course_form_defaults($course) {
return array('reset_questionnaire' => 1);
}
/**
* Actual implementation of the reset course functionality, delete all the
* questionnaire responses for course $data->courseid.
*
* @param stdClass $data the data submitted from the reset course.
* @return array status array
*/
function questionnaire_reset_userdata($data) {
global $CFG, $DB;
require_once($CFG->libdir . '/questionlib.php');
require_once($CFG->dirroot.'/mod/questionnaire/locallib.php');
$componentstr = get_string('modulenameplural', 'questionnaire');
$status = array();
if (!empty($data->reset_questionnaire)) {
$surveys = questionnaire_get_survey_list($data->courseid, '');
// Delete responses.
foreach ($surveys as $survey) {
// Get all responses for this questionnaire.
$sql = "SELECT qr.id, qr.questionnaireid, qr.submitted, qr.userid, q.sid
FROM {questionnaire} q
INNER JOIN {questionnaire_response} qr ON q.id = qr.questionnaireid
WHERE q.sid = ?
ORDER BY qr.id";
$resps = $DB->get_records_sql($sql, [$survey->id]);
if (!empty($resps)) {
$questionnaire = $DB->get_record("questionnaire", ["sid" => $survey->id, "course" => $survey->courseid]);
$questionnaire->course = $DB->get_record("course", array("id" => $questionnaire->course));
foreach ($resps as $response) {
questionnaire_delete_response($response, $questionnaire);
}
}
// Remove this questionnaire's grades (and feedback) from gradebook (if any).
$select = "itemmodule = 'questionnaire' AND iteminstance = ".$survey->qid;
$fields = 'id';
if ($itemid = $DB->get_record_select('grade_items', $select, null, $fields)) {
$itemid = $itemid->id;
$DB->delete_records_select('grade_grades', 'itemid = '.$itemid);
}
}
$status[] = array(
'component' => $componentstr,
'item' => get_string('deletedallresp', 'questionnaire'),
'error' => false);
$status[] = array(
'component' => $componentstr,
'item' => get_string('gradesdeleted', 'questionnaire'),
'error' => false);
}
return $status;
}
/**
* Obtains the automatic completion state for this questionnaire based on the condition
* in questionnaire settings.
*
* @param object $cm Course-module
* @param int $userid User ID
* @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
* @return bool True if completed, false if not, $type if conditions not set.
*
*/
function questionnaire_get_completion_state($cm, $userid, $type) {
global $DB;
// Get questionnaire details.
$questionnaire = $DB->get_record('questionnaire', array('id' => $cm->instance), '*', MUST_EXIST);
// If completion option is enabled, evaluate it and return true/false.
if ($questionnaire->completionsubmit) {
$params = ['userid' => $userid, 'questionnaireid' => $questionnaire->id, 'complete' => 'y'];
return $DB->record_exists('questionnaire_response', $params);
} else {
// Completion option is not enabled so just return $type.
return $type;
}
}
/**
* This function receives a calendar event and returns the action associated with it, or null if there is none.
*
* This is used by block_myoverview in order to display the event appropriately. If null is returned then the event
* is not displayed on the block.
*
* @param calendar_event $event
* @param \core_calendar\action_factory $factory
* @return \core_calendar\local\event\entities\action_interface|null
*/
function mod_questionnaire_core_calendar_provide_event_action(calendar_event $event,
\core_calendar\action_factory $factory) {
$cm = get_fast_modinfo($event->courseid)->instances['questionnaire'][$event->instance];
$completion = new \completion_info($cm->get_course());
$completiondata = $completion->get_data($cm, false);
if ($completiondata->completionstate != COMPLETION_INCOMPLETE) {
return null;
}
return $factory->create_instance(
get_string('view'),
new \moodle_url('/mod/questionnaire/view.php', ['id' => $cm->id]),
1,
true
);
}
/**
* Called after the activity and module have been created. Use this to copy any images if the questionnaire was created from another
* questionnaire survey.
*
* @param stdClass $data
* @param stdClass $course
* @throws coding_exception
*/
function mod_questionnaire_coursemodule_edit_post_actions($data, $course) {
global $DB;
if (!empty($data->copyid)) {
$cm = (object)['id' => $data->coursemodule];
$questionnaire = new questionnaire($course, $cm, 0, $data);
$oldquestionnaireid = $DB->get_field('questionnaire', 'id', ['sid' => $data->copyid]);
$oldcm = get_coursemodule_from_instance('questionnaire', $oldquestionnaireid);
$oldquestionnaire = new questionnaire($course, $oldcm, $oldquestionnaireid, null);
$oldcontext = context_module::instance($oldcm->id);
$newcontext = context_module::instance($data->coursemodule);
$areas = $questionnaire->get_all_file_areas();
$oldareas = $oldquestionnaire->get_all_file_areas();
$fs = new \mod_questionnaire\file_storage();
foreach ($areas as $area => $ids) {
if (is_array($ids)) {
$oldid = current($oldareas[$area]);
foreach ($ids as $id) {
$fs->copy_area_files_to_new_context($oldcontext->id, $newcontext->id, 'mod_questionnaire', $area, $oldid, $id);
$oldid = next($oldareas[$area]);
}
} else {
$fs->copy_area_files_to_new_context($oldcontext->id, $newcontext->id, 'mod_questionnaire', $area,
$oldareas[$area], $ids);
}
}
}
return $data;
}