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
 * Contains class mod_feedback_completion
19
 *
20
 * @package   mod_feedback
21
 * @copyright 2016 Marina Glancy
22
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23
 */
24
 
25
defined('MOODLE_INTERNAL') || die();
26
 
27
/**
28
 * Collects information and methods about feedback completion (either complete.php or show_entries.php)
29
 *
30
 * @package   mod_feedback
31
 * @copyright 2016 Marina Glancy
32
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
33
 */
34
class mod_feedback_completion extends mod_feedback_structure {
35
    /** @var stdClass */
36
    protected $completed;
37
    /** @var stdClass */
38
    protected $completedtmp = null;
39
    /** @var stdClass[] */
40
    protected $valuestmp = null;
41
    /** @var stdClass[] */
42
    protected $values = null;
43
    /** @var bool */
44
    protected $iscompleted = false;
45
    /** @var mod_feedback_complete_form the form used for completing the feedback */
46
    protected $form = null;
47
    /** @var bool true when the feedback has been completed during the request */
48
    protected $justcompleted = false;
49
    /** @var int the next page the user should jump after processing the form */
50
    protected $jumpto = null;
51
 
52
 
53
    /**
54
     * Constructor
55
     *
56
     * @param stdClass $feedback feedback object
57
     * @param cm_info $cm course module object corresponding to the $feedback
58
     *     (at least one of $feedback or $cm is required)
59
     * @param int $courseid current course (for site feedbacks only)
60
     * @param bool $iscompleted has feedback been already completed? If yes either completedid or userid must be specified.
61
     * @param int $completedid id in the table feedback_completed, may be omitted if userid is specified
62
     *     but it is highly recommended because the same user may have multiple responses to the same feedback
63
     *     for different courses
64
     * @param int $nonanonymouseuserid - Return only anonymous results or specified user's results.
65
     *     If null only anonymous replies will be returned and the $completedid is mandatory.
66
     *     If specified only non-anonymous replies of $nonanonymouseuserid will be returned.
67
     * @param int $userid User id to use for all capability checks, etc. Set to 0 for current user (default).
68
     */
69
    public function __construct($feedback, $cm, $courseid, $iscompleted = false, $completedid = null,
70
                                $nonanonymouseuserid = null, $userid = 0) {
71
        global $DB;
72
 
73
        parent::__construct($feedback, $cm, $courseid, 0, $userid);
74
        // Make sure courseid is always set for site feedback.
75
        if ($this->feedback->course == SITEID && !$this->courseid) {
76
            $this->courseid = SITEID;
77
        }
78
        if ($iscompleted) {
79
            // Retrieve information about the completion.
80
            $this->iscompleted = true;
81
            $params = array('feedback' => $this->feedback->id);
82
            if (!$nonanonymouseuserid && !$completedid) {
83
                throw new coding_exception('Either $completedid or $nonanonymouseuserid must be specified for completed feedbacks');
84
            }
85
            if ($completedid) {
86
                $params['id'] = $completedid;
87
            }
88
            if ($nonanonymouseuserid) {
89
                // We must respect the anonymousity of the reply that the user saw when they were completing the feedback,
90
                // not the current state that may have been changed later by the teacher.
91
                $params['anonymous_response'] = FEEDBACK_ANONYMOUS_NO;
92
                $params['userid'] = $nonanonymouseuserid;
93
            }
94
            $this->completed = $DB->get_record('feedback_completed', $params, '*', MUST_EXIST);
95
            $this->courseid = $this->completed->courseid;
96
        }
97
    }
98
 
99
    /**
100
     * Returns a record from 'feedback_completed' table
101
     * @return stdClass
102
     */
103
    public function get_completed() {
104
        return $this->completed;
105
    }
106
 
107
    /**
108
     * Check if the feedback was just completed.
109
     *
110
     * @return bool true if the feedback was just completed.
111
     * @since  Moodle 3.3
112
     */
113
    public function just_completed() {
114
        return $this->justcompleted;
115
    }
116
 
117
    /**
118
     * Return the jumpto property.
119
     *
120
     * @return int the next page to jump.
121
     * @since  Moodle 3.3
122
     */
123
    public function get_jumpto() {
124
        return $this->jumpto;
125
    }
126
 
127
    /**
128
     * Returns the temporary completion record for the current user or guest session
129
     *
130
     * @return stdClass|false record from feedback_completedtmp or false if not found
131
     */
132
    public function get_current_completed_tmp() {
133
        global $DB, $USER;
134
        if ($this->completedtmp === null) {
135
            $params = array('feedback' => $this->get_feedback()->id);
136
            if ($courseid = $this->get_courseid()) {
137
                $params['courseid'] = $courseid;
138
            }
139
            if ((isloggedin() || $USER->id != $this->userid) && !isguestuser($this->userid)) {
140
                $params['userid'] = $this->userid;
141
            } else {
142
                $params['guestid'] = sesskey();
143
            }
144
            $this->completedtmp = $DB->get_record('feedback_completedtmp', $params);
145
        }
146
        return $this->completedtmp;
147
    }
148
 
149
    /**
150
     * Can the current user see the item, if dependency is met?
151
     *
152
     * @param stdClass $item
153
     * @return bool whether user can see item or not,
154
     *     true if there is no dependency or dependency is met,
155
     *     false if dependent question is visible or broken
156
     *        and further it is either not answered or the dependency is not met,
157
     *     null if dependency is broken.
158
     */
159
    protected function can_see_item($item) {
160
        if (empty($item->dependitem)) {
161
            return true;
162
        }
163
        if ($this->dependency_has_error($item)) {
164
            return null;
165
        }
166
        $allitems = $this->get_items();
167
        $ditem = $allitems[$item->dependitem];
168
        $itemobj = feedback_get_item_class($ditem->typ);
169
        if ($this->iscompleted) {
170
            $value = $this->get_values($ditem);
171
        } else {
172
            $value = $this->get_values_tmp($ditem);
173
        }
174
        if ($value === null) {
175
            // Cyclic dependencies are no problem here, since they will throw an dependency error above.
176
            if ($this->can_see_item($ditem) === false) {
177
                return false;
178
            }
179
            return null;
180
        }
181
        $check = $itemobj->compare_value($ditem, $value, $item->dependvalue) ? true : false;
182
        if ($check) {
183
            return $this->can_see_item($ditem);
184
        }
185
        return false;
186
    }
187
 
188
    /**
189
     * Dependency condition has an error
190
     * @param stdClass $item
191
     * @return bool
192
     */
193
    protected function dependency_has_error($item) {
194
        if (empty($item->dependitem)) {
195
            // No dependency - no error.
196
            return false;
197
        }
198
        $allitems = $this->get_items();
199
        if (!array_key_exists($item->dependitem, $allitems)) {
200
            // Looks like dependent item has been removed.
201
            return true;
202
        }
203
        $itemids = array_keys($allitems);
204
        $index1 = array_search($item->dependitem, $itemids);
205
        $index2 = array_search($item->id, $itemids);
206
        if ($index1 >= $index2) {
207
            // Dependent item is after the current item in the feedback.
208
            return true;
209
        }
210
        for ($i = $index1 + 1; $i < $index2; $i++) {
211
            if ($allitems[$itemids[$i]]->typ === 'pagebreak') {
212
                return false;
213
            }
214
        }
215
        // There are no page breaks between dependent items.
216
        return true;
217
    }
218
 
219
    /**
220
     * Returns a value stored for this item in the feedback (temporary or not, depending on the mode)
221
     * @param stdClass $item
222
     * @return string
223
     */
224
    public function get_item_value($item) {
225
        if ($this->iscompleted) {
226
            return $this->get_values($item);
227
        } else {
228
            return $this->get_values_tmp($item);
229
        }
230
    }
231
 
232
    /**
233
     * Retrieves responses from an unfinished attempt.
234
     *
235
     * @return array the responses (from the feedback_valuetmp table)
236
     * @since  Moodle 3.3
237
     */
238
    public function get_unfinished_responses() {
239
        global $DB;
240
        $responses = array();
241
 
242
        $completedtmp = $this->get_current_completed_tmp();
243
        if ($completedtmp) {
244
            $responses = $DB->get_records('feedback_valuetmp', ['completed' => $completedtmp->id]);
245
        }
246
        return $responses;
247
    }
248
 
249
    /**
250
     * Returns all temporary values for this feedback or just a value for an item
251
     * @param stdClass $item
252
     * @return array
253
     */
254
    protected function get_values_tmp($item = null) {
255
        global $DB;
256
        if ($this->valuestmp === null) {
257
            $this->valuestmp = array();
258
            $responses = $this->get_unfinished_responses();
259
            foreach ($responses as $r) {
260
                $this->valuestmp[$r->item] = $r->value;
261
            }
262
        }
263
        if ($item) {
264
            return array_key_exists($item->id, $this->valuestmp) ? $this->valuestmp[$item->id] : null;
265
        }
266
        return $this->valuestmp;
267
    }
268
 
269
    /**
270
     * Retrieves responses from an finished attempt.
271
     *
272
     * @return array the responses (from the feedback_value table)
273
     * @since  Moodle 3.3
274
     */
275
    public function get_finished_responses() {
276
        global $DB;
277
        $responses = array();
278
 
279
        if ($this->completed) {
280
            $responses = $DB->get_records('feedback_value', ['completed' => $this->completed->id]);
281
        }
282
        return $responses;
283
    }
284
 
285
    /**
286
     * Returns all completed values for this feedback or just a value for an item
287
     * @param stdClass $item
288
     * @return array
289
     */
290
    protected function get_values($item = null) {
291
        global $DB;
292
        if ($this->values === null) {
293
            $this->values = array();
294
            $responses = $this->get_finished_responses();
295
            foreach ($responses as $r) {
296
                $this->values[$r->item] = $r->value;
297
            }
298
        }
299
        if ($item) {
300
            return array_key_exists($item->id, $this->values) ? $this->values[$item->id] : null;
301
        }
302
        return $this->values;
303
    }
304
 
305
    /**
306
     * Splits the feedback items into pages
307
     *
308
     * Items that we definitely know at this stage as not applicable are excluded.
309
     * Items that are dependent on something that has not yet been answered are
310
     * still present, as well as items with broken dependencies.
311
     *
312
     * @return array array of arrays of items
313
     */
314
    public function get_pages() {
315
        $pages = [[]]; // The first page always exists.
316
        $items = $this->get_items();
317
        foreach ($items as $item) {
318
            if ($item->typ === 'pagebreak') {
319
                $pages[] = [];
320
            } else if ($this->can_see_item($item) !== false) {
321
                $pages[count($pages) - 1][] = $item;
322
            }
323
        }
324
        return $pages;
325
    }
326
 
327
    /**
328
     * Returns the last page that has items with the value (i.e. not label) which have been answered
329
     * as well as the first page that has items with the values that have not been answered.
330
     *
331
     * Either of the two return values may be null if there are no answered page or there are no
332
     * unanswered pages left respectively.
333
     *
334
     * Two pages may not be directly following each other because there may be empty pages
335
     * or pages with information texts only between them
336
     *
337
     * @return array array of two elements [$lastcompleted, $firstincompleted]
338
     */
339
    protected function get_last_completed_page() {
340
        $completed = [];
341
        $incompleted = [];
342
        $pages = $this->get_pages();
343
        foreach ($pages as $pageidx => $pageitems) {
344
            foreach ($pageitems as $item) {
345
                if ($item->hasvalue) {
346
                    if ($this->get_values_tmp($item) !== null) {
347
                        $completed[$pageidx] = true;
348
                    } else {
349
                        $incompleted[$pageidx] = true;
350
                    }
351
                }
352
            }
353
        }
354
        $completed = array_keys($completed);
355
        $incompleted = array_keys($incompleted);
356
        // If some page has both completed and incompleted items it is considered incompleted.
357
        $completed = array_diff($completed, $incompleted);
358
        // If the completed page follows an incompleted page, it does not count.
359
        $firstincompleted = $incompleted ? min($incompleted) : null;
360
        if ($firstincompleted !== null) {
361
            $completed = array_filter($completed, function($a) use ($firstincompleted) {
362
                return $a < $firstincompleted;
363
            });
364
        }
365
        $lastcompleted = $completed ? max($completed) : null;
366
        return [$lastcompleted, $firstincompleted];
367
    }
368
 
369
    /**
370
     * Get the next page for the feedback
371
     *
372
     * This is normally $gopage+1 but may be bigger if there are empty pages or
373
     * pages without visible questions.
374
     *
375
     * This method can only be called when questions on the current page are
376
     * already answered, otherwise it may be inaccurate.
377
     *
378
     * @param int $gopage current page
379
     * @param bool $strictcheck when gopage is the user-input value, make sure we do not jump over unanswered questions
380
     * @return int|null the index of the next page or null if this is the last page
381
     */
382
    public function get_next_page($gopage, $strictcheck = true) {
383
        if ($strictcheck) {
384
            list($lastcompleted, $firstincompleted) = $this->get_last_completed_page();
385
            if ($firstincompleted !== null && $firstincompleted <= $gopage) {
386
                return $firstincompleted;
387
            }
388
        }
389
        $pages = $this->get_pages();
390
        for ($pageidx = $gopage + 1; $pageidx < count($pages); $pageidx++) {
391
            if (!empty($pages[$pageidx])) {
392
                return $pageidx;
393
            }
394
        }
395
        // No further pages in the feedback have any visible items.
396
        return null;
397
    }
398
 
399
    /**
400
     * Get the previous page for the feedback
401
     *
402
     * This is normally $gopage-1 but may be smaller if there are empty pages or
403
     * pages without visible questions.
404
     *
405
     * @param int $gopage current page
406
     * @param bool $strictcheck when gopage is the user-input value, make sure we do not jump over unanswered questions
407
     * @return int|null the index of the next page or null if this is the first page with items
408
     */
409
    public function get_previous_page($gopage, $strictcheck = true) {
410
        if (!$gopage) {
411
            // If we are already on the first (0) page, there is definitely no previous page.
412
            return null;
413
        }
414
        $pages = $this->get_pages();
415
        $rv = null;
416
        // Iterate through previous pages and find the closest one that has any items on it.
417
        for ($pageidx = $gopage - 1; $pageidx >= 0; $pageidx--) {
418
            if (!empty($pages[$pageidx])) {
419
                $rv = $pageidx;
420
                break;
421
            }
422
        }
423
        if ($rv === null) {
424
            // We are on the very first page that has items.
425
            return null;
426
        }
427
        if ($rv > 0 && $strictcheck) {
428
            // Check if this page is actually not past than first incompleted page.
429
            list($lastcompleted, $firstincompleted) = $this->get_last_completed_page();
430
            if ($firstincompleted !== null && $firstincompleted < $rv) {
431
                return $firstincompleted;
432
            }
433
        }
434
        return $rv;
435
    }
436
 
437
    /**
438
     * Page index to resume the feedback
439
     *
440
     * When user abandones answering feedback and then comes back to it we should send him
441
     * to the first page after the last page he fully completed.
442
     * @return int
443
     */
444
    public function get_resume_page() {
445
        list($lastcompleted, $firstincompleted) = $this->get_last_completed_page();
446
        return $lastcompleted === null ? 0 : $this->get_next_page($lastcompleted, false);
447
    }
448
 
449
    /**
450
     * Creates a new record in the 'feedback_completedtmp' table for the current user/guest session
451
     *
452
     * @return stdClass record from feedback_completedtmp or false if not found
453
     */
454
    protected function create_current_completed_tmp() {
455
        global $DB, $USER;
456
        $record = (object)['feedback' => $this->feedback->id];
457
        if ($this->get_courseid()) {
458
            $record->courseid = $this->get_courseid();
459
        }
460
        if ((isloggedin() || $USER->id != $this->userid) && !isguestuser($this->userid)) {
461
            $record->userid = $this->userid;
462
        } else {
463
            $record->guestid = sesskey();
464
        }
465
        $record->timemodified = time();
466
        $record->anonymous_response = $this->feedback->anonymous;
467
        $id = $DB->insert_record('feedback_completedtmp', $record);
468
        $this->completedtmp = $DB->get_record('feedback_completedtmp', ['id' => $id]);
469
        $this->valuestmp = null;
470
        return $this->completedtmp;
471
    }
472
 
473
    /**
474
     * If user has already completed the feedback, create the temproray values from last completed attempt
475
     *
476
     * @return stdClass record from feedback_completedtmp or false if not found
477
     */
478
    public function create_completed_tmp_from_last_completed() {
479
        if (!$this->get_current_completed_tmp()) {
480
            $lastcompleted = $this->find_last_completed();
481
            if ($lastcompleted) {
482
                $this->completedtmp = feedback_set_tmp_values($lastcompleted);
483
            }
484
        }
485
        return $this->completedtmp;
486
    }
487
 
488
    /**
489
     * Saves unfinished response to the temporary table
490
     *
491
     * This is called when user proceeds to the next/previous page in the complete form
492
     * and also right after the form submit.
493
     * After the form submit the {@link save_response()} is called to
494
     * move response from temporary table to completion table.
495
     *
496
     * @param stdClass $data data from the form mod_feedback_complete_form
497
     */
498
    public function save_response_tmp($data) {
499
        global $DB;
500
        if (!$completedtmp = $this->get_current_completed_tmp()) {
501
            $completedtmp = $this->create_current_completed_tmp();
502
        } else {
503
            $currentime = time();
504
            $DB->update_record('feedback_completedtmp',
505
                    ['id' => $completedtmp->id, 'timemodified' => $currentime]);
506
            $completedtmp->timemodified = $currentime;
507
        }
508
 
509
        // Find all existing values.
510
        $existingvalues = $DB->get_records_menu('feedback_valuetmp',
511
                ['completed' => $completedtmp->id], '', 'item, id');
512
 
513
        // Loop through all feedback items and save the ones that are present in $data.
514
        $allitems = $this->get_items();
515
        foreach ($allitems as $item) {
516
            if (!$item->hasvalue) {
517
                continue;
518
            }
519
            $keyname = $item->typ . '_' . $item->id;
520
            if (!isset($data->$keyname)) {
521
                // This item is either on another page or dependency was not met - nothing to save.
522
                continue;
523
            }
524
 
525
            $newvalue = ['item' => $item->id, 'completed' => $completedtmp->id, 'course_id' => $completedtmp->courseid];
526
 
527
            // Convert the value to string that can be stored in 'feedback_valuetmp' or 'feedback_value'.
528
            $itemobj = feedback_get_item_class($item->typ);
529
            $newvalue['value'] = $itemobj->create_value($data->$keyname);
530
 
531
            // Update or insert the value in the 'feedback_valuetmp' table.
532
            if (array_key_exists($item->id, $existingvalues)) {
533
                $newvalue['id'] = $existingvalues[$item->id];
534
                $DB->update_record('feedback_valuetmp', $newvalue);
535
            } else {
536
                $DB->insert_record('feedback_valuetmp', $newvalue);
537
            }
538
        }
539
 
540
        // Reset valuestmp cache.
541
        $this->valuestmp = null;
542
    }
543
 
544
    /**
545
     * Saves the response
546
     *
547
     * The form data has already been stored in the temporary table in
548
     * {@link save_response_tmp()}. This function copies the values
549
     * from the temporary table to the completion table.
550
     * It is also responsible for sending email notifications when applicable.
551
     */
552
    public function save_response() {
553
        global $SESSION, $DB, $USER;
554
 
555
        $feedbackcompleted = $this->find_last_completed();
556
        // If no record is found, change false to null for safe use in feedback_save_tmp_values.
557
        $feedbackcompleted = !$feedbackcompleted ? null : $feedbackcompleted;
558
        $feedbackcompletedtmp = $this->get_current_completed_tmp();
559
 
560
        if (feedback_check_is_switchrole()) {
561
            // We do not actually save anything if the role is switched, just delete temporary values.
562
            $this->delete_completedtmp();
563
            return;
564
        }
565
 
566
        // Save values.
567
        $completedid = feedback_save_tmp_values($feedbackcompletedtmp, $feedbackcompleted);
568
        $this->completed = $DB->get_record('feedback_completed', array('id' => $completedid));
569
 
570
        // Send email.
571
        if ($this->feedback->anonymous == FEEDBACK_ANONYMOUS_NO) {
572
            feedback_send_email($this->cm, $this->feedback, $this->cm->get_course(), $this->userid, $this->completed);
573
        } else {
574
            feedback_send_email_anonym($this->cm, $this->feedback, $this->cm->get_course());
575
        }
576
 
577
        unset($SESSION->feedback->is_started);
578
 
579
        // Update completion state.
580
        $completion = new completion_info($this->cm->get_course());
581
        if ((isloggedin() || $USER->id != $this->userid) && $completion->is_enabled($this->cm) &&
582
                $this->cm->completion == COMPLETION_TRACKING_AUTOMATIC && $this->feedback->completionsubmit) {
583
            $completion->update_state($this->cm, COMPLETION_COMPLETE, $this->userid);
584
        }
585
    }
586
 
587
    /**
588
     * Deletes the temporary completed and all related temporary values
589
     */
590
    protected function delete_completedtmp() {
591
        global $DB;
592
 
593
        if ($completedtmp = $this->get_current_completed_tmp()) {
594
            $DB->delete_records('feedback_valuetmp', ['completed' => $completedtmp->id]);
595
            $DB->delete_records('feedback_completedtmp', ['id' => $completedtmp->id]);
596
            $this->completedtmp = null;
597
        }
598
    }
599
 
600
    /**
601
     * Retrieves the last completion record for the current user
602
     *
603
     * @return stdClass record from feedback_completed or false if not found
604
     */
605
    public function find_last_completed() {
606
        global $DB, $USER;
607
        if ((!isloggedin() && $USER->id == $this->userid) || isguestuser($this->userid)) {
608
            // Not possible to retrieve completed feedback for guests.
609
            return false;
610
        }
611
        if ($this->is_anonymous()) {
612
            // Not possible to retrieve completed anonymous feedback.
613
            return false;
614
        }
615
        $params = array('feedback' => $this->feedback->id,
616
            'userid' => $this->userid,
617
            'anonymous_response' => FEEDBACK_ANONYMOUS_NO
618
        );
619
        if ($this->get_courseid()) {
620
            $params['courseid'] = $this->get_courseid();
621
        }
622
        $this->completed = $DB->get_record('feedback_completed', $params);
623
        return $this->completed;
624
    }
625
 
626
    /**
627
     * Checks if user has capability to submit the feedback
628
     *
629
     * There is an exception for fully anonymous feedbacks when guests can complete
630
     * feedback without the proper capability.
631
     *
632
     * This should be followed by checking {@link can_submit()} because even if
633
     * user has capablity to complete, they may have already submitted feedback
634
     * and can not re-submit
635
     *
636
     * @return bool
637
     */
638
    public function can_complete() {
639
        global $CFG, $USER;
640
 
641
        $context = context_module::instance($this->cm->id);
642
        if (has_capability('mod/feedback:complete', $context, $this->userid)) {
643
            return true;
644
        }
645
 
646
        if (!empty($CFG->feedback_allowfullanonymous)
647
                    AND $this->feedback->course == SITEID
648
                    AND $this->feedback->anonymous == FEEDBACK_ANONYMOUS_YES
649
                    AND ((!isloggedin() && $USER->id == $this->userid) || isguestuser($this->userid))) {
650
            // Guests are allowed to complete fully anonymous feedback without having 'mod/feedback:complete' capability.
651
            return true;
652
        }
653
 
654
        return false;
655
    }
656
 
657
    /**
658
     * Checks if user is prevented from re-submission.
659
     *
660
     * This must be called after {@link can_complete()}
661
     *
662
     * @return bool
663
     */
664
    public function can_submit() {
665
        if ($this->get_feedback()->multiple_submit == 0 ) {
666
            if ($this->is_already_submitted()) {
667
                return false;
668
            }
669
        }
670
        return true;
671
    }
672
 
673
    /**
674
     * Trigger module viewed event.
675
     *
676
     * @since Moodle 3.3
677
     */
678
    public function trigger_module_viewed() {
679
        $event = \mod_feedback\event\course_module_viewed::create_from_record($this->feedback, $this->cm, $this->cm->get_course());
680
        $event->trigger();
681
    }
682
 
683
    /**
684
     * Mark activity viewed for completion-tracking.
685
     *
686
     * @since Moodle 3.3
687
     */
688
    public function set_module_viewed() {
689
        global $CFG;
690
        require_once($CFG->libdir . '/completionlib.php');
691
 
692
        $completion = new completion_info($this->cm->get_course());
693
        $completion->set_module_viewed($this->cm, $this->userid);
694
    }
695
 
696
    /**
697
     * Process a page jump via the mod_feedback_complete_form.
698
     *
699
     * This function initializes the form and process the submission.
700
     *
701
     * @param  int $gopage         the current page
702
     * @param  int $gopreviouspage if the user chose to go to the previous page
703
     * @return string the url to redirect the user (if any)
704
     * @since  Moodle 3.3
705
     */
706
    public function process_page($gopage, $gopreviouspage = false) {
707
        global $CFG, $PAGE, $SESSION;
708
 
709
        $urltogo = null;
710
 
711
        // Save the form for later during the request.
712
        $this->create_completed_tmp_from_last_completed();
713
        $this->form = new mod_feedback_complete_form(mod_feedback_complete_form::MODE_COMPLETE,
714
            $this, 'feedback_complete_form', array('gopage' => $gopage));
715
 
716
        if ($this->form->is_cancelled()) {
717
            // Form was cancelled - return to the course page.
718
            $urltogo = new moodle_url('/mod/feedback/view.php', ['id' => $this->get_cm()->id]);
719
        } else if ($this->form->is_submitted() &&
720
                ($this->form->is_validated() || $gopreviouspage)) {
721
            // Form was submitted (skip validation for "Previous page" button).
722
            $data = $this->form->get_submitted_data();
723
            if (!isset($SESSION->feedback->is_started) OR !$SESSION->feedback->is_started == true) {
724
                throw new \moodle_exception('error', '', $CFG->wwwroot.'/course/view.php?id='.$this->courseid);
725
            }
726
            $this->save_response_tmp($data);
727
            if (!empty($data->savevalues) || !empty($data->gonextpage)) {
728
                if (($nextpage = $this->get_next_page($gopage)) !== null) {
729
                    if ($PAGE->has_set_url()) {
730
                        $urltogo = new moodle_url($PAGE->url, array('gopage' => $nextpage));
731
                    }
732
                    $this->jumpto = $nextpage;
733
                } else {
734
                    $this->save_response();
735
                    if (!$this->get_feedback()->page_after_submit) {
736
                        \core\notification::success(get_string('entries_saved', 'feedback'));
737
                    }
738
                    $this->justcompleted = true;
739
                }
740
            } else if (!empty($gopreviouspage)) {
741
                $prevpage = intval($this->get_previous_page($gopage));
742
                if ($PAGE->has_set_url()) {
743
                    $urltogo = new moodle_url($PAGE->url, array('gopage' => $prevpage));
744
                }
745
                $this->jumpto = $prevpage;
746
            }
747
        }
748
        return $urltogo;
749
    }
750
 
751
    /**
752
     * Render the form with the questions.
753
     *
754
     * @return string the form rendered
755
     * @since Moodle 3.3
756
     */
757
    public function render_items() {
758
        global $SESSION;
759
 
760
        // Print the items.
761
        $SESSION->feedback->is_started = true;
762
        return $this->form->render();
763
    }
764
}