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/>.
/**
* Updates the contents of the survey with the provided data. If no data is provided, it checks for posted data.
*
* This library replaces the phpESP application with Moodle specific code. It will eventually
* replace all of the phpESP application, removing the dependency on that.
*
* @package mod_questionnaire
* @copyright 2016 Mike Churchward (mike.churchward@poetgroup.org)
* @author Mike Churchward
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot.'/calendar/lib.php');
// Constants.
define ('QUESTIONNAIREUNLIMITED', 0);
define ('QUESTIONNAIREONCE', 1);
define ('QUESTIONNAIREDAILY', 2);
define ('QUESTIONNAIREWEEKLY', 3);
define ('QUESTIONNAIREMONTHLY', 4);
define ('QUESTIONNAIRE_STUDENTVIEWRESPONSES_NEVER', 0);
define ('QUESTIONNAIRE_STUDENTVIEWRESPONSES_WHENANSWERED', 1);
define ('QUESTIONNAIRE_STUDENTVIEWRESPONSES_WHENCLOSED', 2);
define ('QUESTIONNAIRE_STUDENTVIEWRESPONSES_ALWAYS', 3);
define('QUESTIONNAIRE_MAX_EVENT_LENGTH', 5 * 24 * 60 * 60); // 5 days maximum.
define('QUESTIONNAIRE_DEFAULT_PAGE_COUNT', 20);
global $questionnairetypes;
$questionnairetypes = array (QUESTIONNAIREUNLIMITED => get_string('qtypeunlimited', 'questionnaire'),
QUESTIONNAIREONCE => get_string('qtypeonce', 'questionnaire'),
QUESTIONNAIREDAILY => get_string('qtypedaily', 'questionnaire'),
QUESTIONNAIREWEEKLY => get_string('qtypeweekly', 'questionnaire'),
QUESTIONNAIREMONTHLY => get_string('qtypemonthly', 'questionnaire'));
global $questionnairerespondents;
$questionnairerespondents = array ('fullname' => get_string('respondenttypefullname', 'questionnaire'),
'anonymous' => get_string('respondenttypeanonymous', 'questionnaire'));
global $questionnairerealms;
$questionnairerealms = array ('private' => get_string('private', 'questionnaire'),
'public' => get_string('public', 'questionnaire'),
'template' => get_string('template', 'questionnaire'));
global $questionnaireresponseviewers;
$questionnaireresponseviewers = array (
QUESTIONNAIRE_STUDENTVIEWRESPONSES_WHENANSWERED => get_string('responseviewstudentswhenanswered', 'questionnaire'),
QUESTIONNAIRE_STUDENTVIEWRESPONSES_WHENCLOSED => get_string('responseviewstudentswhenclosed', 'questionnaire'),
QUESTIONNAIRE_STUDENTVIEWRESPONSES_ALWAYS => get_string('responseviewstudentsalways', 'questionnaire'),
QUESTIONNAIRE_STUDENTVIEWRESPONSES_NEVER => get_string('responseviewstudentsnever', 'questionnaire'));
global $autonumbering;
$autonumbering = array (0 => get_string('autonumberno', 'questionnaire'),
1 => get_string('autonumberquestions', 'questionnaire'),
2 => get_string('autonumberpages', 'questionnaire'),
3 => get_string('autonumberpagesandquestions', 'questionnaire'));
/**
* Return the choice values for the content.
* @param string $content
* @return stdClass
*/
function questionnaire_choice_values($content) {
// If we run the content through format_text first, any filters we want to use (e.g. multilanguage) should work.
// examines the content of a possible answer from radio button, check boxes or rate question
// returns ->text to be displayed, ->image if present, ->modname name of modality, image ->title.
$contents = new stdClass();
$contents->text = '';
$contents->image = '';
$contents->modname = '';
$contents->title = '';
// Has image.
if (preg_match('/(<img)\s .*(src="(.[^"]{1,})")/isxmU', $content, $matches)) {
$contents->image = $matches[0];
$imageurl = $matches[3];
// Image has a title or alt text: use one of them.
if (preg_match('/(title=.)([^"]{1,})/', $content, $matches)
|| preg_match('/(alt=.)([^"]{1,})/', $content, $matches) ) {
$contents->title = $matches[2];
} else {
// Image has no title nor alt text: use its filename (without the extension).
preg_match("/.*\/(.*)\..*$/", $imageurl, $matches);
$contents->title = $matches[1];
}
// Content has text or named modality plus an image.
if (preg_match('/(.*)(<img.*)/', $content, $matches)) {
$content = $matches[1];
} else {
// Just an image.
return $contents;
}
}
// Check for score value first (used e.g. by personality test feature).
$r = preg_match_all("/^(\d{1,2}=)(.*)$/", $content, $matches);
if ($r) {
$content = $matches[2][0];
}
// Look for named modalities.
$contents->text = $content;
// DEV JR from version 2.5, a double colon :: must be used here instead of the equal sign.
if ($pos = strpos($content, '::')) {
$contents->text = substr($content, $pos + 2);
$contents->modname = substr($content, 0, $pos);
}
return $contents;
}
/**
* Get the information about the standard questionnaire JavaScript module.
* @return array a standard jsmodule structure.
*/
function questionnaire_get_js_module() {
return array(
'name' => 'mod_questionnaire',
'fullpath' => '/mod/questionnaire/module.js',
'requires' => array('base', 'dom', 'event-delegate', 'event-key',
'core_question_engine', 'moodle-core-formchangechecker'),
'strings' => array(
array('cancel', 'moodle'),
array('flagged', 'question'),
array('functiondisabledbysecuremode', 'quiz'),
array('startattempt', 'quiz'),
array('timesup', 'quiz'),
array('changesmadereallygoaway', 'moodle'),
),
);
}
/**
* Get all the questionnaire responses for a user.
* @param int $questionnaireid
* @param int $userid
* @param bool $complete
* @return array
*/
function questionnaire_get_user_responses($questionnaireid, $userid, $complete=true) {
global $DB;
$andcomplete = '';
if ($complete) {
$andcomplete = " AND complete = 'y' ";
}
return $DB->get_records_sql ("SELECT *
FROM {questionnaire_response}
WHERE questionnaireid = ?
AND userid = ?
".$andcomplete."
ORDER BY submitted ASC ", array($questionnaireid, $userid)) ?? [];
}
/**
* get the capabilities for the questionnaire
* @param int $cmid
* @return object the available capabilities from current user
*/
function questionnaire_load_capabilities($cmid) {
static $cb;
if (isset($cb)) {
return $cb;
}
$context = questionnaire_get_context($cmid);
$cb = new stdClass();
$cb->view = has_capability('mod/questionnaire:view', $context);
$cb->submit = has_capability('mod/questionnaire:submit', $context);
$cb->viewsingleresponse = has_capability('mod/questionnaire:viewsingleresponse', $context);
$cb->submissionnotification = has_capability('mod/questionnaire:submissionnotification', $context);
$cb->downloadresponses = has_capability('mod/questionnaire:downloadresponses', $context);
$cb->deleteresponses = has_capability('mod/questionnaire:deleteresponses', $context);
$cb->manage = has_capability('mod/questionnaire:manage', $context);
$cb->editquestions = has_capability('mod/questionnaire:editquestions', $context);
$cb->createtemplates = has_capability('mod/questionnaire:createtemplates', $context);
$cb->createpublic = has_capability('mod/questionnaire:createpublic', $context);
$cb->readownresponses = has_capability('mod/questionnaire:readownresponses', $context);
$cb->readallresponses = has_capability('mod/questionnaire:readallresponses', $context);
$cb->readallresponseanytime = has_capability('mod/questionnaire:readallresponseanytime', $context);
$cb->printblank = has_capability('mod/questionnaire:printblank', $context);
$cb->preview = has_capability('mod/questionnaire:preview', $context);
$cb->viewhiddenactivities = has_capability('moodle/course:viewhiddenactivities', $context, null, false);
return $cb;
}
/**
* returns the context-id related to the given coursemodule-id
* @param int $cmid the coursemodule-id
* @return object $context
*/
function questionnaire_get_context($cmid) {
static $context;
if (isset($context)) {
return $context;
}
if (!$context = context_module::instance($cmid)) {
throw new \moodle_exception('badcontext', 'mod_questionnaire');
}
return $context;
}
/**
* This function *really* shouldn't be needed, but since sometimes we can end up with
* orphaned surveys, this will clean them up.
* @return bool
* @throws dml_exception
*/
function questionnaire_cleanup() {
global $DB;
// Find surveys that don't have questionnaires associated with them.
$sql = 'SELECT qs.* FROM {questionnaire_survey} qs '.
'LEFT JOIN {questionnaire} q ON q.sid = qs.id '.
'WHERE q.sid IS NULL';
if ($surveys = $DB->get_records_sql($sql)) {
foreach ($surveys as $survey) {
questionnaire_delete_survey($survey->id, 0);
}
}
// Find deleted questions and remove them from database (with their associated choices, etc.).
return true;
}
/**
* Delete the survey.
* @param int $sid
* @param int $questionnaireid
* @return bool
*/
function questionnaire_delete_survey($sid, $questionnaireid) {
global $DB;
$status = true;
// Delete all survey attempts and responses.
if ($responses = $DB->get_records('questionnaire_response', ['questionnaireid' => $questionnaireid], 'id')) {
foreach ($responses as $response) {
$status = $status && questionnaire_delete_response($response);
}
}
// There really shouldn't be any more, but just to make sure...
$DB->delete_records('questionnaire_response', ['questionnaireid' => $questionnaireid]);
// Delete all question data for the survey.
if ($questions = $DB->get_records('questionnaire_question', ['surveyid' => $sid], 'id')) {
foreach ($questions as $question) {
$DB->delete_records('questionnaire_quest_choice', ['question_id' => $question->id]);
questionnaire_delete_dependencies($question->id);
}
$status = $status && $DB->delete_records('questionnaire_question', ['surveyid' => $sid]);
// Just to make sure.
$status = $status && $DB->delete_records('questionnaire_dependency', ['surveyid' => $sid]);
}
// Delete all feedback sections and feedback messages for the survey.
if ($fbsections = $DB->get_records('questionnaire_fb_sections', ['surveyid' => $sid], 'id')) {
foreach ($fbsections as $fbsection) {
$DB->delete_records('questionnaire_feedback', ['sectionid' => $fbsection->id]);
}
$status = $status && $DB->delete_records('questionnaire_fb_sections', ['surveyid' => $sid]);
}
$status = $status && $DB->delete_records('questionnaire_survey', ['id' => $sid]);
return $status;
}
/**
* Delete the response.
* @param stdClass $response
* @param string $questionnaire
* @return bool
*/
function questionnaire_delete_response($response, $questionnaire='') {
global $DB;
$status = true;
$cm = '';
$rid = $response->id;
// The questionnaire_delete_survey function does not send the questionnaire array.
if ($questionnaire != '') {
$cm = get_coursemodule_from_instance("questionnaire", $questionnaire->id, $questionnaire->course->id);
}
// Delete all of the response data for a response.
$DB->delete_records('questionnaire_response_bool', array('response_id' => $rid));
$DB->delete_records('questionnaire_response_date', array('response_id' => $rid));
$DB->delete_records('questionnaire_resp_multiple', array('response_id' => $rid));
$DB->delete_records('questionnaire_response_other', array('response_id' => $rid));
$DB->delete_records('questionnaire_response_rank', array('response_id' => $rid));
$DB->delete_records('questionnaire_resp_single', array('response_id' => $rid));
$DB->delete_records('questionnaire_response_text', array('response_id' => $rid));
$status = $status && $DB->delete_records('questionnaire_response', array('id' => $rid));
if ($status && $cm) {
// Update completion state if necessary.
$completion = new completion_info($questionnaire->course);
if ($completion->is_enabled($cm) == COMPLETION_TRACKING_AUTOMATIC && $questionnaire->completionsubmit) {
$completion->update_state($cm, COMPLETION_INCOMPLETE, $response->userid);
}
}
return $status;
}
/**
* Delete all responses for the questionnaire.
* @param int $qid
* @return bool
*/
function questionnaire_delete_responses($qid) {
global $DB;
// Delete all of the response data for a question.
$DB->delete_records('questionnaire_response_bool', ['question_id' => $qid]);
$DB->delete_records('questionnaire_response_date', ['question_id' => $qid]);
$DB->delete_records('questionnaire_resp_multiple', ['question_id' => $qid]);
$DB->delete_records('questionnaire_response_other', ['question_id' => $qid]);
$DB->delete_records('questionnaire_response_rank', ['question_id' => $qid]);
$DB->delete_records('questionnaire_resp_single', ['question_id' => $qid]);
$DB->delete_records('questionnaire_response_text', ['question_id' => $qid]);
return true;
}
/**
* Delete all dependencies for the questionnaire.
* @param int $qid
* @return bool
*/
function questionnaire_delete_dependencies($qid) {
global $DB;
// Delete all dependencies for this question.
$DB->delete_records('questionnaire_dependency', ['questionid' => $qid]);
$DB->delete_records('questionnaire_dependency', ['dependquestionid' => $qid]);
return true;
}
/**
* Get a survey selection records.
* @param int $courseid
* @param string $type
* @return array|false
*/
function questionnaire_get_survey_list($courseid=0, $type='') {
global $DB;
if ($courseid == 0) {
if (isadmin()) {
$sql = "SELECT id,name,courseid,realm,status " .
"{questionnaire_survey} " .
"ORDER BY realm,name ";
$params = null;
} else {
return false;
}
} else {
if ($type == 'public') {
$sql = "SELECT s.id,s.name,s.courseid,s.realm,s.status,s.title,q.id as qid,q.name as qname " .
"FROM {questionnaire} q " .
"INNER JOIN {questionnaire_survey} s ON s.id = q.sid AND s.courseid = q.course " .
"WHERE realm = ? " .
"ORDER BY realm,name ";
$params = [$type];
} else if ($type == 'template') {
$sql = "SELECT s.id,s.name,s.courseid,s.realm,s.status,s.title,q.id as qid,q.name as qname " .
"FROM {questionnaire} q " .
"INNER JOIN {questionnaire_survey} s ON s.id = q.sid AND s.courseid = q.course " .
"WHERE (realm = ?) " .
"ORDER BY realm,name ";
$params = [$type];
} else if ($type == 'private') {
$sql = "SELECT s.id,s.name,s.courseid,s.realm,s.status,q.id as qid,q.name as qname " .
"FROM {questionnaire} q " .
"INNER JOIN {questionnaire_survey} s ON s.id = q.sid " .
"WHERE s.courseid = ? and realm = ? " .
"ORDER BY realm,name ";
$params = [$courseid, $type];
} else {
// Current get_survey_list is called from function questionnaire_reset_userdata so we need to get a
// complete list of all questionnaires in current course to reset them.
$sql = "SELECT s.id,s.name,s.courseid,s.realm,s.status,q.id as qid,q.name as qname " .
"FROM {questionnaire} q " .
"INNER JOIN {questionnaire_survey} s ON s.id = q.sid AND s.courseid = q.course " .
"WHERE s.courseid = ? " .
"ORDER BY realm,name ";
$params = [$courseid];
}
}
return $DB->get_records_sql($sql, $params) ?? [];
}
/**
* Get survey selection list.
* @param int $courseid
* @param string $type
* @return array
*/
function questionnaire_get_survey_select($courseid=0, $type='') {
global $OUTPUT, $DB;
$surveylist = array();
if ($surveys = questionnaire_get_survey_list($courseid, $type)) {
$strpreview = get_string('preview_questionnaire', 'questionnaire');
foreach ($surveys as $survey) {
$originalcourse = $DB->get_record('course', ['id' => $survey->courseid]);
if (!$originalcourse) {
// This should not happen, but we found a case where a public survey
// still existed in a course that had been deleted, and so this
// code lead to a notice, and a broken link. Since that is useless
// we just skip surveys like this.
continue;
}
// Prevent creating a copy of a public questionnaire IN THE SAME COURSE as the original.
if (($type == 'public') && ($survey->courseid == $courseid)) {
continue;
} else {
$args = "sid={$survey->id}&popup=1";
if (!empty($survey->qid)) {
$args .= "&qid={$survey->qid}";
}
$link = new moodle_url("/mod/questionnaire/preview.php?{$args}");
$action = new popup_action('click', $link);
$label = $OUTPUT->action_link($link, $survey->qname.' ['.$originalcourse->fullname.']',
$action, array('title' => $strpreview));
$surveylist[$type.'-'.$survey->id] = $label;
}
}
}
return $surveylist;
}
/**
* Return the language string for the specified question type.
* @param int $id
* @return lang_string|mixed|string
* @throws coding_exception
*/
function questionnaire_get_type ($id) {
switch ($id) {
case 1:
return get_string('yesno', 'questionnaire');
case 2:
return get_string('textbox', 'questionnaire');
case 3:
return get_string('essaybox', 'questionnaire');
case 4:
return get_string('radiobuttons', 'questionnaire');
case 5:
return get_string('checkboxes', 'questionnaire');
case 6:
return get_string('dropdown', 'questionnaire');
case 8:
return get_string('ratescale', 'questionnaire');
case 9:
return get_string('date', 'questionnaire');
case 10:
return get_string('numeric', 'questionnaire');
case 11:
return get_string('slider', 'questionnaire');
case 100:
return get_string('sectiontext', 'questionnaire');
case 99:
return get_string('sectionbreak', 'questionnaire');
default:
return $id;
}
}
/**
* This creates new events given as opendate and closedate by $questionnaire.
* @param object $questionnaire
* @return void
*/
function questionnaire_set_events($questionnaire) {
// Adding the questionnaire to the eventtable.
global $DB;
if ($events = $DB->get_records('event', array('modulename' => 'questionnaire', 'instance' => $questionnaire->id))) {
foreach ($events as $event) {
$event = calendar_event::load($event);
$event->delete();
}
}
// The open-event.
$event = new stdClass;
$event->description = $questionnaire->name;
$event->courseid = $questionnaire->course;
$event->groupid = 0;
$event->userid = 0;
$event->modulename = 'questionnaire';
$event->instance = $questionnaire->id;
$event->eventtype = 'open';
$event->type = CALENDAR_EVENT_TYPE_ACTION;
$event->timestart = $questionnaire->opendate;
$event->visible = instance_is_visible('questionnaire', $questionnaire);
$event->timeduration = ($questionnaire->closedate - $questionnaire->opendate);
if ($questionnaire->closedate && $questionnaire->opendate && ($event->timeduration <= QUESTIONNAIRE_MAX_EVENT_LENGTH)) {
// Single event for the whole questionnaire.
$event->name = $questionnaire->name;
$event->timesort = $questionnaire->opendate;
calendar_event::create($event);
} else {
// Separate start and end events.
$event->timeduration = 0;
if ($questionnaire->opendate) {
$event->name = $questionnaire->name.' ('.get_string('questionnaireopens', 'questionnaire').')';
$event->timesort = $questionnaire->opendate;
calendar_event::create($event);
unset($event->id); // So we can use the same object for the close event.
}
if ($questionnaire->closedate) {
$event->name = $questionnaire->name.' ('.get_string('questionnairecloses', 'questionnaire').')';
$event->timestart = $questionnaire->closedate;
$event->timesort = $questionnaire->closedate;
$event->eventtype = 'close';
calendar_event::create($event);
}
}
}
/**
* Get users who have not completed the questionnaire
*
* @param object $cm
* @param int $sid
* @param bool $group single groupid
* @param string $sort
* @param bool $startpage
* @param bool $pagecount
* @return object the userrecords
* @throws coding_exception
* @throws dml_exception
*/
function questionnaire_get_incomplete_users($cm, $sid,
$group = false,
$sort = '',
$startpage = false,
$pagecount = false) {
global $DB;
$context = context_module::instance($cm->id);
// First get all users who can complete this questionnaire.
$cap = 'mod/questionnaire:submit';
$fields = 'u.id, u.username';
if (!$allusers = get_enrolled_users($context, $cap, $group, $fields, $sort)) {
return false;
}
$allusers = array_keys($allusers);
// Nnow get all completed questionnaires.
$params = array('questionnaireid' => $cm->instance, 'complete' => 'y');
$sql = "SELECT userid FROM {questionnaire_response} " .
"WHERE questionnaireid = :questionnaireid AND complete = :complete " .
"GROUP BY userid ";
if (!$completedusers = $DB->get_records_sql($sql, $params)) {
return $allusers;
}
$completedusers = array_keys($completedusers);
// Now strike all completedusers from allusers.
$allusers = array_diff($allusers, $completedusers);
// For paging I use array_slice().
if (($startpage !== false) && ($pagecount !== false)) {
$allusers = array_slice($allusers, $startpage, $pagecount);
}
return $allusers;
}
/**
* Called by HTML editor in showrespondents and Essay question. Based on question/essay/renderer.
* Pending general solution to using the HTML editor outside of moodleforms in Moodle pages.
* @param int $context
* @return array
*/
function questionnaire_get_editor_options($context) {
return array(
'subdirs' => 0,
'maxbytes' => 0,
'maxfiles' => -1,
'context' => $context,
'noclean' => 0,
'trusttext' => 0
);
}
/**
* Get the parent of a child question.
* @param stdClass $question
* @return array
*/
function questionnaire_get_parent ($question) {
global $DB;
$qid = $question->id;
$parent = array();
$dependquestion = $DB->get_record('questionnaire_question', ['id' => $question->dependquestionid],
'id, position, name, type_id');
if (is_object($dependquestion)) {
$qdependchoice = '';
switch ($dependquestion->type_id) {
case QUESRADIO:
case QUESDROP:
case QUESCHECK:
$dependchoice = $DB->get_record('questionnaire_quest_choice', ['id' => $question->dependchoiceid], 'id,content');
$qdependchoice = $dependchoice->id;
$dependchoice = $dependchoice->content;
$contents = questionnaire_choice_values($dependchoice);
if ($contents->modname) {
$dependchoice = $contents->modname;
}
break;
case QUESYESNO:
switch ($question->dependchoiceid) {
case 0:
$dependchoice = get_string('yes');
$qdependchoice = 'y';
break;
case 1:
$dependchoice = get_string('no');
$qdependchoice = 'n';
break;
}
break;
}
// Qdependquestion, parenttype and qdependchoice fields to be used in preview mode.
$parent[$qid]['qdependquestion'] = 'q'.$dependquestion->id;
$parent[$qid]['qdependchoice'] = $qdependchoice;
$parent[$qid]['parenttype'] = $dependquestion->type_id;
// Other fields to be used in Questions edit mode.
$parent[$qid]['position'] = $question->position;
$parent[$qid]['name'] = $question->name;
$parent[$qid]['content'] = $question->content;
$parent[$qid]['parentposition'] = $dependquestion->position;
$parent[$qid]['parent'] = format_string($dependquestion->name) . '->' . format_string ($dependchoice);
}
return $parent;
}
/**
* Get parent position of all child questions in current questionnaire.
* Use the parent with the largest position value.
*
* @param array $questions
* @return array An array with Child-ID->Parentposition.
*/
function questionnaire_get_parent_positions ($questions) {
$parentpositions = array();
foreach ($questions as $question) {
foreach ($question->dependencies as $dependency) {
$dependquestion = $dependency->dependquestionid;
if (isset($dependquestion) && $dependquestion != 0) {
$childid = $question->id;
$parentpos = $questions[$dependquestion]->position;
if (!isset($parentpositions[$childid])) {
$parentpositions[$childid] = $parentpos;
}
if (isset ($parentpositions[$childid]) && $parentpos > $parentpositions[$childid]) {
$parentpositions[$childid] = $parentpos;
}
}
}
}
return $parentpositions;
}
/**
* Get child position of all parent questions in current questionnaire.
* Use the child with the smallest position value.
*
* @param array $questions
* @return array An array with Parent-ID->Childposition.
*/
function questionnaire_get_child_positions ($questions) {
$childpositions = array();
foreach ($questions as $question) {
foreach ($question->dependencies as $dependency) {
$dependquestion = $dependency->dependquestionid;
if (isset($dependquestion) && $dependquestion != 0) {
$parentid = $questions[$dependquestion]->id; // Equals $dependquestion?.
$childpos = $question->position;
if (!isset($childpositions[$parentid])) {
$childpositions[$parentid] = $childpos;
}
if (isset ($childpositions[$parentid]) && $childpos < $childpositions[$parentid]) {
$childpositions[$parentid] = $childpos;
}
}
}
}
return $childpositions;
}
/**
* Check that the needed page breaks are present to separate child questions.
* @param stdClass $questionnaire
* @return false|lang_string|string
*/
function questionnaire_check_page_breaks($questionnaire) {
global $DB;
$msg = '';
// Store the new page breaks ids.
$newpbids = array();
$delpb = 0;
$sid = $questionnaire->survey->id;
$positions = array();
if ($questions = $DB->get_records('questionnaire_question', ['surveyid' => $sid, 'deleted' => 'n'], 'position')) {
foreach ($questions as $key => $qu) {
$newqu = new stdClass();
$newqu->question_id = $key;
$newqu->type_id = $qu->type_id;
$newqu->qname = $qu->name;
$newqu->qpos = $qu->position;
$dependencies = $DB->get_records('questionnaire_dependency', ['questionid' => $key, 'surveyid' => $sid],
'id ASC', 'id, dependquestionid, dependchoiceid, dependlogic');
$newqu->dependencies = $dependencies ?? [];
$positions[] = (array)$newqu;
}
}
$count = count($positions);
for ($i = $count - 1; $i >= 0; $i--) {
$qu = $positions[$i];
$questionnb = $i;
$prevqu = null;
$prevtypeid = null;
if ($i > 0) {
$prevqu = $positions[$i - 1];
$prevtypeid = $prevqu['type_id'];
}
if ($qu['type_id'] == QUESPAGEBREAK) {
$questionnb--;
// If more than one consecutive page breaks, remove extra one(s).
// Remove that extra page break in 1st position.
if ($prevtypeid == QUESPAGEBREAK || $i == $count - 1 || $qu['qpos'] == 1) {
$qid = $qu['question_id'];
$delpb ++;
$msg .= get_string("checkbreaksremoved", "questionnaire", $delpb).'<br />';
// Need to reload questions.
if ($questions = $DB->get_records('questionnaire_question', ['surveyid' => $sid, 'deleted' => 'n'], 'id')) {
$DB->set_field('questionnaire_question', 'deleted', 'y', ['id' => $qid, 'surveyid' => $sid]);
$select = 'surveyid = ' . $sid . ' AND deleted = \'n\' AND position > ' .
$questions[$qid]->position;
if ($records = $DB->get_records_select('questionnaire_question', $select, null, 'position ASC')) {
foreach ($records as $record) {
$DB->set_field('questionnaire_question', 'position', $record->position - 1, ['id' => $record->id]);
}
}
}
}
}
// Add pagebreak between question child and not dependent question that follows.
if ($qu['type_id'] != QUESPAGEBREAK) {
if ($prevqu) {
$prevdependencies = $prevqu['dependencies'];
$outerdependencies = count($qu['dependencies']) >= count($prevdependencies) ?
$qu['dependencies'] : $prevdependencies;
$innerdependencies = count($qu['dependencies']) < count($prevdependencies) ?
$qu['dependencies'] : $prevdependencies;
$okeys = [];
$ikeys = [];
foreach ($outerdependencies as $okey => $outerdependency) {
foreach ($innerdependencies as $ikey => $innerdependency) {
if ($outerdependency->dependquestionid === $innerdependency->dependquestionid &&
$outerdependency->dependchoiceid === $innerdependency->dependchoiceid &&
$outerdependency->dependlogic === $innerdependency->dependlogic) {
$okeys[] = $okey;
$ikeys[] = $ikey;
}
}
}
foreach ($okeys as $key) {
if (key_exists($key, $outerdependencies)) {
unset($outerdependencies[$key]);
}
}
foreach ($ikeys as $key) {
if (key_exists($key, $innerdependencies)) {
unset($innerdependencies[$key]);
}
}
$diffdependencies = count($outerdependencies) + count($innerdependencies);
if (($prevtypeid != QUESPAGEBREAK && $diffdependencies != 0)
|| (!isset($qu['dependencies']) && isset($prevdependencies))) {
$sql = 'SELECT MAX(position) as maxpos FROM {questionnaire_question} ' .
'WHERE surveyid = ' . $questionnaire->survey->id . ' AND deleted = \'n\'';
if ($record = $DB->get_record_sql($sql)) {
$pos = $record->maxpos + 1;
} else {
$pos = 1;
}
$question = new stdClass();
$question->surveyid = $questionnaire->survey->id;
$question->type_id = QUESPAGEBREAK;
$question->position = $pos;
$question->content = 'break';
if (!($newqid = $DB->insert_record('questionnaire_question', $question))) {
return (false);
}
$newpbids[] = $newqid;
$questionnaire = new questionnaire($course, $cm, $questionnaire->id, null);
$questionnaire->move_question($newqid, $qu['qpos']);
}
}
}
}
if (empty($newpbids) && !$msg) {
$msg = get_string('checkbreaksok', 'questionnaire');
} else if ($newpbids) {
$msg .= get_string('checkbreaksadded', 'questionnaire').' ';
$newpbids = array_reverse ($newpbids);
$questionnaire = new questionnaire($course, $cm, $questionnaire->id, null);
foreach ($newpbids as $newpbid) {
$msg .= $questionnaire->questions[$newpbid]->position.' ';
}
}
return($msg);
}
/**
* Code snippet used to set up the questionform.
* @param stdClass $questionnaire
* @param int $qid
* @param int $qtype
* @return mixed|\mod_questionnaire\question\question
*/
function questionnaire_prep_for_questionform($questionnaire, $qid, $qtype) {
$context = context_module::instance($questionnaire->cm->id);
if ($qid != 0) {
$question = clone($questionnaire->questions[$qid]);
$question->qid = $question->id;
$question->sid = $questionnaire->survey->id;
$question->id = $questionnaire->cm->id;
$draftideditor = file_get_submitted_draft_itemid('question');
$content = file_prepare_draft_area($draftideditor, $context->id, 'mod_questionnaire', 'question',
$qid, array('subdirs' => true), $question->content);
$question->content = array('text' => $content, 'format' => FORMAT_HTML, 'itemid' => $draftideditor);
if (isset($question->dependencies)) {
foreach ($question->dependencies as $dependencies) {
if ($dependencies->dependandor === "and") {
$question->dependquestions_and[] = $dependencies->dependquestionid.','.$dependencies->dependchoiceid;
$question->dependlogic_and[] = $dependencies->dependlogic;
} else if ($dependencies->dependandor === "or") {
$question->dependquestions_or[] = $dependencies->dependquestionid.','.$dependencies->dependchoiceid;
$question->dependlogic_or[] = $dependencies->dependlogic;
}
}
}
} else {
$question = \mod_questionnaire\question\question::question_builder($qtype);
$question->sid = $questionnaire->survey->id;
$question->id = $questionnaire->cm->id;
$question->type_id = $qtype;
$question->type = '';
$draftideditor = file_get_submitted_draft_itemid('question');
$content = file_prepare_draft_area($draftideditor, $context->id, 'mod_questionnaire', 'question',
null, array('subdirs' => true), '');
$question->content = array('text' => $content, 'format' => FORMAT_HTML, 'itemid' => $draftideditor);
}
return $question;
}
/**
* Get the standard page contructs and check for validity.
* @param int $id The coursemodule id.
* @param int $a The module instance id.
* @return array An array with the $cm, $course, and $questionnaire records in that order.
*/
function questionnaire_get_standard_page_items($id = null, $a = null) {
global $DB;
if ($id) {
if (! $cm = get_coursemodule_from_id('questionnaire', $id)) {
throw new \moodle_exception('invalidcoursemodule', 'mod_questionnaire');
}
if (! $course = $DB->get_record("course", array("id" => $cm->course))) {
throw new \moodle_exception('coursemisconf', 'mod_questionnaire');
}
if (! $questionnaire = $DB->get_record("questionnaire", array("id" => $cm->instance))) {
throw new \moodle_exception('invalidcoursemodule', 'mod_questionnaire');
}
} else {
if (! $questionnaire = $DB->get_record("questionnaire", array("id" => $a))) {
throw new \moodle_exception('invalidcoursemodule', 'mod_questionnaire');
}
if (! $course = $DB->get_record("course", array("id" => $questionnaire->course))) {
throw new \moodle_exception('coursemisconf', 'mod_questionnaire');
}
if (! $cm = get_coursemodule_from_instance("questionnaire", $questionnaire->id, $course->id)) {
throw new \moodle_exception('invalidcoursemodule', 'mod_questionnaire');
}
}
return (array($cm, $course, $questionnaire));
}