Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
 
17
/**
18
 * Updates the contents of the survey with the provided data. If no data is provided, it checks for posted data.
19
 *
20
 * This library replaces the phpESP application with Moodle specific code. It will eventually
21
 * replace all of the phpESP application, removing the dependency on that.
22
 *
23
 * @package mod_questionnaire
24
 * @copyright  2016 Mike Churchward (mike.churchward@poetgroup.org)
25
 * @author     Mike Churchward
26
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
27
 *
28
 */
29
 
30
defined('MOODLE_INTERNAL') || die();
31
 
32
require_once($CFG->dirroot.'/calendar/lib.php');
33
// Constants.
34
 
35
define ('QUESTIONNAIREUNLIMITED', 0);
36
define ('QUESTIONNAIREONCE', 1);
37
define ('QUESTIONNAIREDAILY', 2);
38
define ('QUESTIONNAIREWEEKLY', 3);
39
define ('QUESTIONNAIREMONTHLY', 4);
40
 
41
define ('QUESTIONNAIRE_STUDENTVIEWRESPONSES_NEVER', 0);
42
define ('QUESTIONNAIRE_STUDENTVIEWRESPONSES_WHENANSWERED', 1);
43
define ('QUESTIONNAIRE_STUDENTVIEWRESPONSES_WHENCLOSED', 2);
44
define ('QUESTIONNAIRE_STUDENTVIEWRESPONSES_ALWAYS', 3);
45
 
46
define('QUESTIONNAIRE_MAX_EVENT_LENGTH', 5 * 24 * 60 * 60);   // 5 days maximum.
47
 
48
define('QUESTIONNAIRE_DEFAULT_PAGE_COUNT', 20);
49
 
50
global $questionnairetypes;
51
$questionnairetypes = array (QUESTIONNAIREUNLIMITED => get_string('qtypeunlimited', 'questionnaire'),
52
                              QUESTIONNAIREONCE => get_string('qtypeonce', 'questionnaire'),
53
                              QUESTIONNAIREDAILY => get_string('qtypedaily', 'questionnaire'),
54
                              QUESTIONNAIREWEEKLY => get_string('qtypeweekly', 'questionnaire'),
55
                              QUESTIONNAIREMONTHLY => get_string('qtypemonthly', 'questionnaire'));
56
 
57
global $questionnairerespondents;
58
$questionnairerespondents = array ('fullname' => get_string('respondenttypefullname', 'questionnaire'),
59
                                    'anonymous' => get_string('respondenttypeanonymous', 'questionnaire'));
60
 
61
global $questionnairerealms;
62
$questionnairerealms = array ('private' => get_string('private', 'questionnaire'),
63
                               'public' => get_string('public', 'questionnaire'),
64
                               'template' => get_string('template', 'questionnaire'));
65
 
66
global $questionnaireresponseviewers;
67
$questionnaireresponseviewers = array (
68
            QUESTIONNAIRE_STUDENTVIEWRESPONSES_WHENANSWERED => get_string('responseviewstudentswhenanswered', 'questionnaire'),
69
            QUESTIONNAIRE_STUDENTVIEWRESPONSES_WHENCLOSED => get_string('responseviewstudentswhenclosed', 'questionnaire'),
70
            QUESTIONNAIRE_STUDENTVIEWRESPONSES_ALWAYS => get_string('responseviewstudentsalways', 'questionnaire'),
71
            QUESTIONNAIRE_STUDENTVIEWRESPONSES_NEVER => get_string('responseviewstudentsnever', 'questionnaire'));
72
 
73
global $autonumbering;
74
$autonumbering = array (0 => get_string('autonumberno', 'questionnaire'),
75
        1 => get_string('autonumberquestions', 'questionnaire'),
76
        2 => get_string('autonumberpages', 'questionnaire'),
77
        3 => get_string('autonumberpagesandquestions', 'questionnaire'));
78
 
79
/**
80
 * Return the choice values for the content.
81
 * @param string $content
82
 * @return stdClass
83
 */
84
function questionnaire_choice_values($content) {
85
 
86
    // If we run the content through format_text first, any filters we want to use (e.g. multilanguage) should work.
87
    // examines the content of a possible answer from radio button, check boxes or rate question
88
    // returns ->text to be displayed, ->image if present, ->modname name of modality, image ->title.
89
    $contents = new stdClass();
90
    $contents->text = '';
91
    $contents->image = '';
92
    $contents->modname = '';
93
    $contents->title = '';
94
    // Has image.
95
    if (preg_match('/(<img)\s .*(src="(.[^"]{1,})")/isxmU', $content, $matches)) {
96
        $contents->image = $matches[0];
97
        $imageurl = $matches[3];
98
        // Image has a title or alt text: use one of them.
99
        if (preg_match('/(title=.)([^"]{1,})/', $content, $matches)
100
             || preg_match('/(alt=.)([^"]{1,})/', $content, $matches) ) {
101
            $contents->title = $matches[2];
102
        } else {
103
            // Image has no title nor alt text: use its filename (without the extension).
104
            preg_match("/.*\/(.*)\..*$/", $imageurl, $matches);
105
            $contents->title = $matches[1];
106
        }
107
        // Content has text or named modality plus an image.
108
        if (preg_match('/(.*)(<img.*)/', $content, $matches)) {
109
            $content = $matches[1];
110
        } else {
111
            // Just an image.
112
            return $contents;
113
        }
114
    }
115
 
116
    // Check for score value first (used e.g. by personality test feature).
117
    $r = preg_match_all("/^(\d{1,2}=)(.*)$/", $content, $matches);
118
    if ($r) {
119
        $content = $matches[2][0];
120
    }
121
 
122
    // Look for named modalities.
123
    $contents->text = $content;
124
    // DEV JR from version 2.5, a double colon :: must be used here instead of the equal sign.
125
    if ($pos = strpos($content, '::')) {
126
        $contents->text = substr($content, $pos + 2);
127
        $contents->modname = substr($content, 0, $pos);
128
    }
129
    return $contents;
130
}
131
 
132
/**
133
 * Get the information about the standard questionnaire JavaScript module.
134
 * @return array a standard jsmodule structure.
135
 */
136
function questionnaire_get_js_module() {
137
    return array(
138
            'name' => 'mod_questionnaire',
139
            'fullpath' => '/mod/questionnaire/module.js',
140
            'requires' => array('base', 'dom', 'event-delegate', 'event-key',
141
                    'core_question_engine', 'moodle-core-formchangechecker'),
142
            'strings' => array(
143
                    array('cancel', 'moodle'),
144
                    array('flagged', 'question'),
145
                    array('functiondisabledbysecuremode', 'quiz'),
146
                    array('startattempt', 'quiz'),
147
                    array('timesup', 'quiz'),
148
                    array('changesmadereallygoaway', 'moodle'),
149
            ),
150
    );
151
}
152
 
153
/**
154
 * Get all the questionnaire responses for a user.
155
 * @param int $questionnaireid
156
 * @param int $userid
157
 * @param bool $complete
158
 * @return array
159
 */
160
function questionnaire_get_user_responses($questionnaireid, $userid, $complete=true) {
161
    global $DB;
162
    $andcomplete = '';
163
    if ($complete) {
164
        $andcomplete = " AND complete = 'y' ";
165
    }
166
    return $DB->get_records_sql ("SELECT *
167
        FROM {questionnaire_response}
168
        WHERE questionnaireid = ?
169
        AND userid = ?
170
        ".$andcomplete."
171
        ORDER BY submitted ASC ", array($questionnaireid, $userid)) ?? [];
172
}
173
 
174
/**
175
 * get the capabilities for the questionnaire
176
 * @param int $cmid
177
 * @return object the available capabilities from current user
178
 */
179
function questionnaire_load_capabilities($cmid) {
180
    static $cb;
181
 
182
    if (isset($cb)) {
183
        return $cb;
184
    }
185
 
186
    $context = questionnaire_get_context($cmid);
187
 
188
    $cb = new stdClass();
189
    $cb->view = has_capability('mod/questionnaire:view', $context);
190
    $cb->submit = has_capability('mod/questionnaire:submit', $context);
191
    $cb->viewsingleresponse = has_capability('mod/questionnaire:viewsingleresponse', $context);
192
    $cb->submissionnotification = has_capability('mod/questionnaire:submissionnotification', $context);
193
    $cb->downloadresponses = has_capability('mod/questionnaire:downloadresponses', $context);
194
    $cb->deleteresponses = has_capability('mod/questionnaire:deleteresponses', $context);
195
    $cb->manage = has_capability('mod/questionnaire:manage', $context);
196
    $cb->editquestions = has_capability('mod/questionnaire:editquestions', $context);
197
    $cb->createtemplates = has_capability('mod/questionnaire:createtemplates', $context);
198
    $cb->createpublic = has_capability('mod/questionnaire:createpublic', $context);
199
    $cb->readownresponses = has_capability('mod/questionnaire:readownresponses', $context);
200
    $cb->readallresponses = has_capability('mod/questionnaire:readallresponses', $context);
201
    $cb->readallresponseanytime = has_capability('mod/questionnaire:readallresponseanytime', $context);
202
    $cb->printblank = has_capability('mod/questionnaire:printblank', $context);
203
    $cb->preview = has_capability('mod/questionnaire:preview', $context);
204
 
205
    $cb->viewhiddenactivities = has_capability('moodle/course:viewhiddenactivities', $context, null, false);
206
 
207
    return $cb;
208
}
209
 
210
/**
211
 * returns the context-id related to the given coursemodule-id
212
 * @param int $cmid the coursemodule-id
213
 * @return object $context
214
 */
215
function questionnaire_get_context($cmid) {
216
    static $context;
217
 
218
    if (isset($context)) {
219
        return $context;
220
    }
221
 
222
    if (!$context = context_module::instance($cmid)) {
223
        throw new \moodle_exception('badcontext', 'mod_questionnaire');
224
    }
225
    return $context;
226
}
227
 
228
/**
229
 * This function *really* shouldn't be needed, but since sometimes we can end up with
230
 * orphaned surveys, this will clean them up.
231
 * @return bool
232
 * @throws dml_exception
233
 */
234
function questionnaire_cleanup() {
235
    global $DB;
236
 
237
    // Find surveys that don't have questionnaires associated with them.
238
    $sql = 'SELECT qs.* FROM {questionnaire_survey} qs '.
239
           'LEFT JOIN {questionnaire} q ON q.sid = qs.id '.
240
           'WHERE q.sid IS NULL';
241
 
242
    if ($surveys = $DB->get_records_sql($sql)) {
243
        foreach ($surveys as $survey) {
244
            questionnaire_delete_survey($survey->id, 0);
245
        }
246
    }
247
    // Find deleted questions and remove them from database (with their associated choices, etc.).
248
    return true;
249
}
250
 
251
/**
252
 * Delete the survey.
253
 * @param int $sid
254
 * @param int $questionnaireid
255
 * @return bool
256
 */
257
function questionnaire_delete_survey($sid, $questionnaireid) {
258
    global $DB;
259
    $status = true;
260
    // Delete all survey attempts and responses.
261
    if ($responses = $DB->get_records('questionnaire_response', ['questionnaireid' => $questionnaireid], 'id')) {
262
        foreach ($responses as $response) {
263
            $status = $status && questionnaire_delete_response($response);
264
        }
265
    }
266
 
267
    // There really shouldn't be any more, but just to make sure...
268
    $DB->delete_records('questionnaire_response', ['questionnaireid' => $questionnaireid]);
269
 
270
    // Delete all question data for the survey.
271
    if ($questions = $DB->get_records('questionnaire_question', ['surveyid' => $sid], 'id')) {
272
        foreach ($questions as $question) {
273
            $DB->delete_records('questionnaire_quest_choice', ['question_id' => $question->id]);
274
            questionnaire_delete_dependencies($question->id);
275
        }
276
        $status = $status && $DB->delete_records('questionnaire_question', ['surveyid' => $sid]);
277
        // Just to make sure.
278
        $status = $status && $DB->delete_records('questionnaire_dependency', ['surveyid' => $sid]);
279
    }
280
 
281
    // Delete all feedback sections and feedback messages for the survey.
282
    if ($fbsections = $DB->get_records('questionnaire_fb_sections', ['surveyid' => $sid], 'id')) {
283
        foreach ($fbsections as $fbsection) {
284
            $DB->delete_records('questionnaire_feedback', ['sectionid' => $fbsection->id]);
285
        }
286
        $status = $status && $DB->delete_records('questionnaire_fb_sections', ['surveyid' => $sid]);
287
    }
288
 
289
    $status = $status && $DB->delete_records('questionnaire_survey', ['id' => $sid]);
290
 
291
    return $status;
292
}
293
 
294
/**
295
 * Delete the response.
296
 * @param stdClass $response
297
 * @param string $questionnaire
298
 * @return bool
299
 */
300
function questionnaire_delete_response($response, $questionnaire='') {
301
    global $DB;
302
    $status = true;
303
    $cm = '';
304
    $rid = $response->id;
305
    // The questionnaire_delete_survey function does not send the questionnaire array.
306
    if ($questionnaire != '') {
307
        $cm = get_coursemodule_from_instance("questionnaire", $questionnaire->id, $questionnaire->course->id);
308
    }
309
 
310
    // Delete all of the response data for a response.
311
    $DB->delete_records('questionnaire_response_bool', array('response_id' => $rid));
312
    $DB->delete_records('questionnaire_response_date', array('response_id' => $rid));
313
    $DB->delete_records('questionnaire_resp_multiple', array('response_id' => $rid));
314
    $DB->delete_records('questionnaire_response_other', array('response_id' => $rid));
315
    $DB->delete_records('questionnaire_response_rank', array('response_id' => $rid));
316
    $DB->delete_records('questionnaire_resp_single', array('response_id' => $rid));
317
    $DB->delete_records('questionnaire_response_text', array('response_id' => $rid));
318
 
319
    $status = $status && $DB->delete_records('questionnaire_response', array('id' => $rid));
320
 
321
    if ($status && $cm) {
322
        // Update completion state if necessary.
323
        $completion = new completion_info($questionnaire->course);
324
        if ($completion->is_enabled($cm) == COMPLETION_TRACKING_AUTOMATIC && $questionnaire->completionsubmit) {
325
            $completion->update_state($cm, COMPLETION_INCOMPLETE, $response->userid);
326
        }
327
    }
328
 
329
    return $status;
330
}
331
 
332
/**
333
 * Delete all responses for the questionnaire.
334
 * @param int $qid
335
 * @return bool
336
 */
337
function questionnaire_delete_responses($qid) {
338
    global $DB;
339
 
340
    // Delete all of the response data for a question.
341
    $DB->delete_records('questionnaire_response_bool', ['question_id' => $qid]);
342
    $DB->delete_records('questionnaire_response_date', ['question_id' => $qid]);
343
    $DB->delete_records('questionnaire_resp_multiple', ['question_id' => $qid]);
344
    $DB->delete_records('questionnaire_response_other', ['question_id' => $qid]);
345
    $DB->delete_records('questionnaire_response_rank', ['question_id' => $qid]);
346
    $DB->delete_records('questionnaire_resp_single', ['question_id' => $qid]);
347
    $DB->delete_records('questionnaire_response_text', ['question_id' => $qid]);
348
 
349
    return true;
350
}
351
 
352
/**
353
 * Delete all dependencies for the questionnaire.
354
 * @param int $qid
355
 * @return bool
356
 */
357
function questionnaire_delete_dependencies($qid) {
358
    global $DB;
359
 
360
    // Delete all dependencies for this question.
361
    $DB->delete_records('questionnaire_dependency', ['questionid' => $qid]);
362
    $DB->delete_records('questionnaire_dependency', ['dependquestionid' => $qid]);
363
 
364
    return true;
365
}
366
 
367
/**
368
 * Get a survey selection records.
369
 * @param int $courseid
370
 * @param string $type
371
 * @return array|false
372
 */
373
function questionnaire_get_survey_list($courseid=0, $type='') {
374
    global $DB;
375
 
376
    if ($courseid == 0) {
377
        if (isadmin()) {
378
            $sql = "SELECT id,name,courseid,realm,status " .
379
                   "{questionnaire_survey} " .
380
                   "ORDER BY realm,name ";
381
            $params = null;
382
        } else {
383
            return false;
384
        }
385
    } else {
386
        if ($type == 'public') {
387
            $sql = "SELECT s.id,s.name,s.courseid,s.realm,s.status,s.title,q.id as qid,q.name as qname " .
388
                   "FROM {questionnaire} q " .
389
                   "INNER JOIN {questionnaire_survey} s ON s.id = q.sid AND s.courseid = q.course " .
390
                   "WHERE realm = ? " .
391
                   "ORDER BY realm,name ";
392
            $params = [$type];
393
        } else if ($type == 'template') {
394
            $sql = "SELECT s.id,s.name,s.courseid,s.realm,s.status,s.title,q.id as qid,q.name as qname " .
395
                   "FROM {questionnaire} q " .
396
                   "INNER JOIN {questionnaire_survey} s ON s.id = q.sid AND s.courseid = q.course " .
397
                   "WHERE (realm = ?) " .
398
                   "ORDER BY realm,name ";
399
            $params = [$type];
400
        } else if ($type == 'private') {
401
            $sql = "SELECT s.id,s.name,s.courseid,s.realm,s.status,q.id as qid,q.name as qname " .
402
                "FROM {questionnaire} q " .
403
                "INNER JOIN {questionnaire_survey} s ON s.id = q.sid " .
404
                "WHERE s.courseid = ? and realm = ? " .
405
                "ORDER BY realm,name ";
406
            $params = [$courseid, $type];
407
 
408
        } else {
409
            // Current get_survey_list is called from function questionnaire_reset_userdata so we need to get a
410
            // complete list of all questionnaires in current course to reset them.
411
            $sql = "SELECT s.id,s.name,s.courseid,s.realm,s.status,q.id as qid,q.name as qname " .
412
                   "FROM {questionnaire} q " .
413
                    "INNER JOIN {questionnaire_survey} s ON s.id = q.sid AND s.courseid = q.course " .
414
                   "WHERE s.courseid = ? " .
415
                   "ORDER BY realm,name ";
416
            $params = [$courseid];
417
        }
418
    }
419
    return $DB->get_records_sql($sql, $params) ?? [];
420
}
421
 
422
/**
423
 * Get survey selection list.
424
 * @param int $courseid
425
 * @param string $type
426
 * @return array
427
 */
428
function questionnaire_get_survey_select($courseid=0, $type='') {
429
    global $OUTPUT, $DB;
430
 
431
    $surveylist = array();
432
 
433
    if ($surveys = questionnaire_get_survey_list($courseid, $type)) {
434
        $strpreview = get_string('preview_questionnaire', 'questionnaire');
435
        foreach ($surveys as $survey) {
436
            $originalcourse = $DB->get_record('course', ['id' => $survey->courseid]);
437
            if (!$originalcourse) {
438
                // This should not happen, but we found a case where a public survey
439
                // still existed in a course that had been deleted, and so this
440
                // code lead to a notice, and a broken link. Since that is useless
441
                // we just skip surveys like this.
442
                continue;
443
            }
444
 
445
            // Prevent creating a copy of a public questionnaire IN THE SAME COURSE as the original.
446
            if (($type == 'public') && ($survey->courseid == $courseid)) {
447
                continue;
448
            } else {
449
                $args = "sid={$survey->id}&popup=1";
450
                if (!empty($survey->qid)) {
451
                    $args .= "&qid={$survey->qid}";
452
                }
453
                $link = new moodle_url("/mod/questionnaire/preview.php?{$args}");
454
                $action = new popup_action('click', $link);
455
                $label = $OUTPUT->action_link($link, $survey->qname.' ['.$originalcourse->fullname.']',
456
                    $action, array('title' => $strpreview));
457
                $surveylist[$type.'-'.$survey->id] = $label;
458
            }
459
        }
460
    }
461
    return $surveylist;
462
}
463
 
464
/**
465
 * Return the language string for the specified question type.
466
 * @param int $id
467
 * @return lang_string|mixed|string
468
 * @throws coding_exception
469
 */
470
function questionnaire_get_type ($id) {
471
    switch ($id) {
472
        case 1:
473
            return get_string('yesno', 'questionnaire');
474
        case 2:
475
            return get_string('textbox', 'questionnaire');
476
        case 3:
477
            return get_string('essaybox', 'questionnaire');
478
        case 4:
479
            return get_string('radiobuttons', 'questionnaire');
480
        case 5:
481
            return get_string('checkboxes', 'questionnaire');
482
        case 6:
483
            return get_string('dropdown', 'questionnaire');
484
        case 8:
485
            return get_string('ratescale', 'questionnaire');
486
        case 9:
487
            return get_string('date', 'questionnaire');
488
        case 10:
489
            return get_string('numeric', 'questionnaire');
490
        case 11:
491
            return get_string('slider', 'questionnaire');
492
        case 100:
493
            return get_string('sectiontext', 'questionnaire');
494
        case 99:
495
            return get_string('sectionbreak', 'questionnaire');
496
        default:
497
        return $id;
498
    }
499
}
500
 
501
/**
502
 * This creates new events given as opendate and closedate by $questionnaire.
503
 * @param object $questionnaire
504
 * @return void
505
 */
506
function questionnaire_set_events($questionnaire) {
507
    // Adding the questionnaire to the eventtable.
508
    global $DB;
509
    if ($events = $DB->get_records('event', array('modulename' => 'questionnaire', 'instance' => $questionnaire->id))) {
510
        foreach ($events as $event) {
511
            $event = calendar_event::load($event);
512
            $event->delete();
513
        }
514
    }
515
 
516
    // The open-event.
517
    $event = new stdClass;
518
    $event->description = $questionnaire->name;
519
    $event->courseid = $questionnaire->course;
520
    $event->groupid = 0;
521
    $event->userid = 0;
522
    $event->modulename = 'questionnaire';
523
    $event->instance = $questionnaire->id;
524
    $event->eventtype = 'open';
525
    $event->type = CALENDAR_EVENT_TYPE_ACTION;
526
    $event->timestart = $questionnaire->opendate;
527
    $event->visible = instance_is_visible('questionnaire', $questionnaire);
528
    $event->timeduration = ($questionnaire->closedate - $questionnaire->opendate);
529
 
530
    if ($questionnaire->closedate && $questionnaire->opendate && ($event->timeduration <= QUESTIONNAIRE_MAX_EVENT_LENGTH)) {
531
        // Single event for the whole questionnaire.
532
        $event->name = $questionnaire->name;
533
        $event->timesort = $questionnaire->opendate;
534
        calendar_event::create($event);
535
    } else {
536
        // Separate start and end events.
537
        $event->timeduration = 0;
538
        if ($questionnaire->opendate) {
539
            $event->name = $questionnaire->name.' ('.get_string('questionnaireopens', 'questionnaire').')';
540
            $event->timesort = $questionnaire->opendate;
541
            calendar_event::create($event);
542
            unset($event->id); // So we can use the same object for the close event.
543
        }
544
        if ($questionnaire->closedate) {
545
            $event->name = $questionnaire->name.' ('.get_string('questionnairecloses', 'questionnaire').')';
546
            $event->timestart = $questionnaire->closedate;
547
            $event->timesort = $questionnaire->closedate;
548
            $event->eventtype = 'close';
549
            calendar_event::create($event);
550
        }
551
    }
552
}
553
 
554
/**
555
 * Get users who have not completed the questionnaire
556
 *
557
 * @param object $cm
558
 * @param int $sid
559
 * @param bool $group single groupid
560
 * @param string $sort
561
 * @param bool $startpage
562
 * @param bool $pagecount
563
 * @return object the userrecords
564
 * @throws coding_exception
565
 * @throws dml_exception
566
 */
567
function questionnaire_get_incomplete_users($cm, $sid,
568
                $group = false,
569
                $sort = '',
570
                $startpage = false,
571
                $pagecount = false) {
572
 
573
    global $DB;
574
 
575
    $context = context_module::instance($cm->id);
576
 
577
    // First get all users who can complete this questionnaire.
578
    $cap = 'mod/questionnaire:submit';
579
    $fields = 'u.id, u.username';
580
    if (!$allusers = get_enrolled_users($context, $cap, $group, $fields, $sort)) {
581
        return false;
582
    }
583
    $allusers = array_keys($allusers);
584
 
585
    // Nnow get all completed questionnaires.
586
    $params = array('questionnaireid' => $cm->instance, 'complete' => 'y');
587
    $sql = "SELECT userid FROM {questionnaire_response} " .
588
           "WHERE questionnaireid = :questionnaireid AND complete = :complete " .
589
           "GROUP BY userid ";
590
 
591
    if (!$completedusers = $DB->get_records_sql($sql, $params)) {
592
        return $allusers;
593
    }
594
    $completedusers = array_keys($completedusers);
595
    // Now strike all completedusers from allusers.
596
    $allusers = array_diff($allusers, $completedusers);
597
    // For paging I use array_slice().
598
    if (($startpage !== false) && ($pagecount !== false)) {
599
        $allusers = array_slice($allusers, $startpage, $pagecount);
600
    }
601
    return $allusers;
602
}
603
 
604
/**
605
 * Called by HTML editor in showrespondents and Essay question. Based on question/essay/renderer.
606
 * Pending general solution to using the HTML editor outside of moodleforms in Moodle pages.
607
 * @param int $context
608
 * @return array
609
 */
610
function questionnaire_get_editor_options($context) {
611
    return array(
612
                    'subdirs' => 0,
613
                    'maxbytes' => 0,
614
                    'maxfiles' => -1,
615
                    'context' => $context,
616
                    'noclean' => 0,
617
                    'trusttext' => 0
618
    );
619
}
620
 
621
/**
622
 * Get the parent of a child question.
623
 * @param stdClass $question
624
 * @return array
625
 */
626
function questionnaire_get_parent ($question) {
627
    global $DB;
628
    $qid = $question->id;
629
    $parent = array();
630
    $dependquestion = $DB->get_record('questionnaire_question', ['id' => $question->dependquestionid],
631
        'id, position, name, type_id');
632
    if (is_object($dependquestion)) {
633
        $qdependchoice = '';
634
        switch ($dependquestion->type_id) {
635
            case QUESRADIO:
636
            case QUESDROP:
637
            case QUESCHECK:
638
                $dependchoice = $DB->get_record('questionnaire_quest_choice', ['id' => $question->dependchoiceid], 'id,content');
639
                $qdependchoice = $dependchoice->id;
640
                $dependchoice = $dependchoice->content;
641
 
642
                $contents = questionnaire_choice_values($dependchoice);
643
                if ($contents->modname) {
644
                    $dependchoice = $contents->modname;
645
                }
646
                break;
647
            case QUESYESNO:
648
                switch ($question->dependchoiceid) {
649
                    case 0:
650
                        $dependchoice = get_string('yes');
651
                        $qdependchoice = 'y';
652
                        break;
653
                    case 1:
654
                        $dependchoice = get_string('no');
655
                        $qdependchoice = 'n';
656
                        break;
657
                }
658
                break;
659
        }
660
        // Qdependquestion, parenttype and qdependchoice fields to be used in preview mode.
661
        $parent[$qid]['qdependquestion'] = 'q'.$dependquestion->id;
662
        $parent[$qid]['qdependchoice'] = $qdependchoice;
663
        $parent[$qid]['parenttype'] = $dependquestion->type_id;
664
        // Other fields to be used in Questions edit mode.
665
        $parent[$qid]['position'] = $question->position;
666
        $parent[$qid]['name'] = $question->name;
667
        $parent[$qid]['content'] = $question->content;
668
        $parent[$qid]['parentposition'] = $dependquestion->position;
669
        $parent[$qid]['parent'] = format_string($dependquestion->name) . '->' . format_string ($dependchoice);
670
    }
671
    return $parent;
672
}
673
 
674
/**
675
 * Get parent position of all child questions in current questionnaire.
676
 * Use the parent with the largest position value.
677
 *
678
 * @param array $questions
679
 * @return array An array with Child-ID->Parentposition.
680
 */
681
function questionnaire_get_parent_positions ($questions) {
682
    $parentpositions = array();
683
    foreach ($questions as $question) {
684
        foreach ($question->dependencies as $dependency) {
685
            $dependquestion = $dependency->dependquestionid;
686
            if (isset($dependquestion) && $dependquestion != 0) {
687
                $childid = $question->id;
688
                $parentpos = $questions[$dependquestion]->position;
689
 
690
                if (!isset($parentpositions[$childid])) {
691
                    $parentpositions[$childid] = $parentpos;
692
                }
693
                if (isset ($parentpositions[$childid]) && $parentpos > $parentpositions[$childid]) {
694
                    $parentpositions[$childid] = $parentpos;
695
                }
696
            }
697
        }
698
    }
699
    return $parentpositions;
700
}
701
 
702
/**
703
 * Get child position of all parent questions in current questionnaire.
704
 * Use the child with the smallest position value.
705
 *
706
 * @param array $questions
707
 * @return array An array with Parent-ID->Childposition.
708
 */
709
function questionnaire_get_child_positions ($questions) {
710
    $childpositions = array();
711
    foreach ($questions as $question) {
712
        foreach ($question->dependencies as $dependency) {
713
            $dependquestion = $dependency->dependquestionid;
714
            if (isset($dependquestion) && $dependquestion != 0) {
715
                $parentid = $questions[$dependquestion]->id; // Equals $dependquestion?.
716
                $childpos = $question->position;
717
 
718
                if (!isset($childpositions[$parentid])) {
719
                    $childpositions[$parentid] = $childpos;
720
                }
721
 
722
                if (isset ($childpositions[$parentid]) && $childpos < $childpositions[$parentid]) {
723
                    $childpositions[$parentid] = $childpos;
724
                }
725
            }
726
        }
727
    }
728
    return $childpositions;
729
}
730
 
731
/**
732
 * Check that the needed page breaks are present to separate child questions.
733
 * @param stdClass $questionnaire
734
 * @return false|lang_string|string
735
 */
736
function questionnaire_check_page_breaks($questionnaire) {
737
    global $DB;
738
    $msg = '';
739
    // Store the new page breaks ids.
740
    $newpbids = array();
741
    $delpb = 0;
742
    $sid = $questionnaire->survey->id;
743
    $positions = array();
744
    if ($questions = $DB->get_records('questionnaire_question', ['surveyid' => $sid, 'deleted' => 'n'], 'position')) {
745
        foreach ($questions as $key => $qu) {
746
            $newqu = new stdClass();
747
            $newqu->question_id = $key;
748
            $newqu->type_id = $qu->type_id;
749
            $newqu->qname = $qu->name;
750
            $newqu->qpos = $qu->position;
751
 
752
            $dependencies = $DB->get_records('questionnaire_dependency', ['questionid' => $key, 'surveyid' => $sid],
753
                    'id ASC', 'id, dependquestionid, dependchoiceid, dependlogic');
754
            $newqu->dependencies = $dependencies ?? [];
755
            $positions[] = (array)$newqu;
756
        }
757
    }
758
    $count = count($positions);
759
 
760
    for ($i = $count - 1; $i >= 0; $i--) {
761
        $qu = $positions[$i];
762
        $questionnb = $i;
763
        $prevqu = null;
764
        $prevtypeid = null;
765
        if ($i > 0) {
766
            $prevqu = $positions[$i - 1];
767
            $prevtypeid = $prevqu['type_id'];
768
        }
769
        if ($qu['type_id'] == QUESPAGEBREAK) {
770
            $questionnb--;
771
            // If more than one consecutive page breaks, remove extra one(s).
772
            // Remove that extra page break in 1st position.
773
            if ($prevtypeid == QUESPAGEBREAK || $i == $count - 1 || $qu['qpos'] == 1) {
774
                $qid = $qu['question_id'];
775
                $delpb ++;
776
                $msg .= get_string("checkbreaksremoved", "questionnaire", $delpb).'<br />';
777
                // Need to reload questions.
778
                if ($questions = $DB->get_records('questionnaire_question', ['surveyid' => $sid, 'deleted' => 'n'],  'id')) {
779
                    $DB->set_field('questionnaire_question', 'deleted', 'y', ['id' => $qid, 'surveyid' => $sid]);
780
                    $select = 'surveyid = ' . $sid . ' AND deleted = \'n\' AND position > ' .
781
                            $questions[$qid]->position;
782
                    if ($records = $DB->get_records_select('questionnaire_question', $select, null, 'position ASC')) {
783
                        foreach ($records as $record) {
784
                            $DB->set_field('questionnaire_question', 'position', $record->position - 1, ['id' => $record->id]);
785
                        }
786
                    }
787
                }
788
            }
789
        }
790
        // Add pagebreak between question child and not dependent question that follows.
791
        if ($qu['type_id'] != QUESPAGEBREAK) {
792
            if ($prevqu) {
793
                $prevdependencies = $prevqu['dependencies'];
794
                $outerdependencies = count($qu['dependencies']) >= count($prevdependencies) ?
795
                    $qu['dependencies'] : $prevdependencies;
796
                $innerdependencies = count($qu['dependencies']) < count($prevdependencies) ?
797
                    $qu['dependencies'] : $prevdependencies;
798
 
799
                $okeys = [];
800
                $ikeys = [];
801
                foreach ($outerdependencies as $okey => $outerdependency) {
802
                    foreach ($innerdependencies as $ikey => $innerdependency) {
803
                        if ($outerdependency->dependquestionid === $innerdependency->dependquestionid &&
804
                                $outerdependency->dependchoiceid === $innerdependency->dependchoiceid &&
805
                                $outerdependency->dependlogic === $innerdependency->dependlogic) {
806
                            $okeys[] = $okey;
807
                            $ikeys[] = $ikey;
808
                        }
809
                    }
810
                }
811
 
812
                foreach ($okeys as $key) {
813
                    if (key_exists($key, $outerdependencies)) {
814
                        unset($outerdependencies[$key]);
815
                    }
816
                }
817
                foreach ($ikeys as $key) {
818
                    if (key_exists($key, $innerdependencies)) {
819
                        unset($innerdependencies[$key]);
820
                    }
821
                }
822
 
823
                $diffdependencies = count($outerdependencies) + count($innerdependencies);
824
 
825
                if (($prevtypeid != QUESPAGEBREAK && $diffdependencies != 0)
826
                        || (!isset($qu['dependencies']) && isset($prevdependencies))) {
827
                    $sql = 'SELECT MAX(position) as maxpos FROM {questionnaire_question} ' .
828
                        'WHERE surveyid = ' . $questionnaire->survey->id . ' AND deleted = \'n\'';
829
                    if ($record = $DB->get_record_sql($sql)) {
830
                        $pos = $record->maxpos + 1;
831
                    } else {
832
                        $pos = 1;
833
                    }
834
                    $question = new stdClass();
835
                    $question->surveyid = $questionnaire->survey->id;
836
                    $question->type_id = QUESPAGEBREAK;
837
                    $question->position = $pos;
838
                    $question->content = 'break';
839
 
840
                    if (!($newqid = $DB->insert_record('questionnaire_question', $question))) {
841
                        return (false);
842
                    }
843
                    $newpbids[] = $newqid;
844
                    $questionnaire = new questionnaire($course, $cm, $questionnaire->id, null);
845
                    $questionnaire->move_question($newqid, $qu['qpos']);
846
                }
847
            }
848
        }
849
    }
850
    if (empty($newpbids) && !$msg) {
851
        $msg = get_string('checkbreaksok', 'questionnaire');
852
    } else if ($newpbids) {
853
        $msg .= get_string('checkbreaksadded', 'questionnaire').'&nbsp;';
854
        $newpbids = array_reverse ($newpbids);
855
        $questionnaire = new questionnaire($course, $cm, $questionnaire->id, null);
856
        foreach ($newpbids as $newpbid) {
857
            $msg .= $questionnaire->questions[$newpbid]->position.'&nbsp;';
858
        }
859
    }
860
    return($msg);
861
}
862
 
863
/**
864
 * Code snippet used to set up the questionform.
865
 * @param stdClass $questionnaire
866
 * @param int $qid
867
 * @param int $qtype
868
 * @return mixed|\mod_questionnaire\question\question
869
 */
870
function questionnaire_prep_for_questionform($questionnaire, $qid, $qtype) {
871
    $context = context_module::instance($questionnaire->cm->id);
872
    if ($qid != 0) {
873
        $question = clone($questionnaire->questions[$qid]);
874
        $question->qid = $question->id;
875
        $question->sid = $questionnaire->survey->id;
876
        $question->id = $questionnaire->cm->id;
877
        $draftideditor = file_get_submitted_draft_itemid('question');
878
        $content = file_prepare_draft_area($draftideditor, $context->id, 'mod_questionnaire', 'question',
879
                                           $qid, array('subdirs' => true), $question->content);
880
        $question->content = array('text' => $content, 'format' => FORMAT_HTML, 'itemid' => $draftideditor);
881
 
882
        if (isset($question->dependencies)) {
883
            foreach ($question->dependencies as $dependencies) {
884
                if ($dependencies->dependandor === "and") {
885
                    $question->dependquestions_and[] = $dependencies->dependquestionid.','.$dependencies->dependchoiceid;
886
                    $question->dependlogic_and[] = $dependencies->dependlogic;
887
                } else if ($dependencies->dependandor === "or") {
888
                    $question->dependquestions_or[] = $dependencies->dependquestionid.','.$dependencies->dependchoiceid;
889
                    $question->dependlogic_or[] = $dependencies->dependlogic;
890
                }
891
            }
892
        }
893
    } else {
894
        $question = \mod_questionnaire\question\question::question_builder($qtype);
895
        $question->sid = $questionnaire->survey->id;
896
        $question->id = $questionnaire->cm->id;
897
        $question->type_id = $qtype;
898
        $question->type = '';
899
        $draftideditor = file_get_submitted_draft_itemid('question');
900
        $content = file_prepare_draft_area($draftideditor, $context->id, 'mod_questionnaire', 'question',
901
                                           null, array('subdirs' => true), '');
902
        $question->content = array('text' => $content, 'format' => FORMAT_HTML, 'itemid' => $draftideditor);
903
    }
904
    return $question;
905
}
906
 
907
/**
908
 * Get the standard page contructs and check for validity.
909
 * @param int $id The coursemodule id.
910
 * @param int $a  The module instance id.
911
 * @return array An array with the $cm, $course, and $questionnaire records in that order.
912
 */
913
function questionnaire_get_standard_page_items($id = null, $a = null) {
914
    global $DB;
915
 
916
    if ($id) {
917
        if (! $cm = get_coursemodule_from_id('questionnaire', $id)) {
918
            throw new \moodle_exception('invalidcoursemodule', 'mod_questionnaire');
919
        }
920
 
921
        if (! $course = $DB->get_record("course", array("id" => $cm->course))) {
922
            throw new \moodle_exception('coursemisconf', 'mod_questionnaire');
923
        }
924
 
925
        if (! $questionnaire = $DB->get_record("questionnaire", array("id" => $cm->instance))) {
926
            throw new \moodle_exception('invalidcoursemodule', 'mod_questionnaire');
927
        }
928
 
929
    } else {
930
        if (! $questionnaire = $DB->get_record("questionnaire", array("id" => $a))) {
931
            throw new \moodle_exception('invalidcoursemodule', 'mod_questionnaire');
932
        }
933
        if (! $course = $DB->get_record("course", array("id" => $questionnaire->course))) {
934
            throw new \moodle_exception('coursemisconf', 'mod_questionnaire');
935
        }
936
        if (! $cm = get_coursemodule_from_instance("questionnaire", $questionnaire->id, $course->id)) {
937
            throw new \moodle_exception('invalidcoursemodule', 'mod_questionnaire');
938
        }
939
    }
940
 
941
    return (array($cm, $course, $questionnaire));
942
}