Differences Between: [Versions 311 and 400] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403]
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 $feedbackcompletedtmp = $this->get_current_completed_tmp(); 557 558 if (feedback_check_is_switchrole()) { 559 // We do not actually save anything if the role is switched, just delete temporary values. 560 $this->delete_completedtmp(); 561 return; 562 } 563 564 // Save values. 565 $completedid = feedback_save_tmp_values($feedbackcompletedtmp, $feedbackcompleted); 566 $this->completed = $DB->get_record('feedback_completed', array('id' => $completedid)); 567 568 // Send email. 569 if ($this->feedback->anonymous == FEEDBACK_ANONYMOUS_NO) { 570 feedback_send_email($this->cm, $this->feedback, $this->cm->get_course(), $this->userid, $this->completed); 571 } else { 572 feedback_send_email_anonym($this->cm, $this->feedback, $this->cm->get_course()); 573 } 574 575 unset($SESSION->feedback->is_started); 576 577 // Update completion state. 578 $completion = new completion_info($this->cm->get_course()); 579 if ((isloggedin() || $USER->id != $this->userid) && $completion->is_enabled($this->cm) && 580 $this->cm->completion == COMPLETION_TRACKING_AUTOMATIC && $this->feedback->completionsubmit) { 581 $completion->update_state($this->cm, COMPLETION_COMPLETE, $this->userid); 582 } 583 } 584 585 /** 586 * Deletes the temporary completed and all related temporary values 587 */ 588 protected function delete_completedtmp() { 589 global $DB; 590 591 if ($completedtmp = $this->get_current_completed_tmp()) { 592 $DB->delete_records('feedback_valuetmp', ['completed' => $completedtmp->id]); 593 $DB->delete_records('feedback_completedtmp', ['id' => $completedtmp->id]); 594 $this->completedtmp = null; 595 } 596 } 597 598 /** 599 * Retrieves the last completion record for the current user 600 * 601 * @return stdClass record from feedback_completed or false if not found 602 */ 603 public function find_last_completed() { 604 global $DB, $USER; 605 if ((!isloggedin() && $USER->id == $this->userid) || isguestuser($this->userid)) { 606 // Not possible to retrieve completed feedback for guests. 607 return false; 608 } 609 if ($this->is_anonymous()) { 610 // Not possible to retrieve completed anonymous feedback. 611 return false; 612 } 613 $params = array('feedback' => $this->feedback->id, 614 'userid' => $this->userid, 615 'anonymous_response' => FEEDBACK_ANONYMOUS_NO 616 ); 617 if ($this->get_courseid()) { 618 $params['courseid'] = $this->get_courseid(); 619 } 620 $this->completed = $DB->get_record('feedback_completed', $params); 621 return $this->completed; 622 } 623 624 /** 625 * Checks if user has capability to submit the feedback 626 * 627 * There is an exception for fully anonymous feedbacks when guests can complete 628 * feedback without the proper capability. 629 * 630 * This should be followed by checking {@link can_submit()} because even if 631 * user has capablity to complete, they may have already submitted feedback 632 * and can not re-submit 633 * 634 * @return bool 635 */ 636 public function can_complete() { 637 global $CFG, $USER; 638 639 $context = context_module::instance($this->cm->id); 640 if (has_capability('mod/feedback:complete', $context, $this->userid)) { 641 return true; 642 } 643 644 if (!empty($CFG->feedback_allowfullanonymous) 645 AND $this->feedback->course == SITEID 646 AND $this->feedback->anonymous == FEEDBACK_ANONYMOUS_YES 647 AND ((!isloggedin() && $USER->id == $this->userid) || isguestuser($this->userid))) { 648 // Guests are allowed to complete fully anonymous feedback without having 'mod/feedback:complete' capability. 649 return true; 650 } 651 652 return false; 653 } 654 655 /** 656 * Checks if user is prevented from re-submission. 657 * 658 * This must be called after {@link can_complete()} 659 * 660 * @return bool 661 */ 662 public function can_submit() { 663 if ($this->get_feedback()->multiple_submit == 0 ) { 664 if ($this->is_already_submitted()) { 665 return false; 666 } 667 } 668 return true; 669 } 670 671 /** 672 * Trigger module viewed event. 673 * 674 * @since Moodle 3.3 675 */ 676 public function trigger_module_viewed() { 677 $event = \mod_feedback\event\course_module_viewed::create_from_record($this->feedback, $this->cm, $this->cm->get_course()); 678 $event->trigger(); 679 } 680 681 /** 682 * Mark activity viewed for completion-tracking. 683 * 684 * @since Moodle 3.3 685 */ 686 public function set_module_viewed() { 687 global $CFG; 688 require_once($CFG->libdir . '/completionlib.php'); 689 690 $completion = new completion_info($this->cm->get_course()); 691 $completion->set_module_viewed($this->cm, $this->userid); 692 } 693 694 /** 695 * Process a page jump via the mod_feedback_complete_form. 696 * 697 * This function initializes the form and process the submission. 698 * 699 * @param int $gopage the current page 700 * @param int $gopreviouspage if the user chose to go to the previous page 701 * @return string the url to redirect the user (if any) 702 * @since Moodle 3.3 703 */ 704 public function process_page($gopage, $gopreviouspage = false) { 705 global $CFG, $PAGE, $SESSION; 706 707 $urltogo = null; 708 709 // Save the form for later during the request. 710 $this->create_completed_tmp_from_last_completed(); 711 $this->form = new mod_feedback_complete_form(mod_feedback_complete_form::MODE_COMPLETE, 712 $this, 'feedback_complete_form', array('gopage' => $gopage)); 713 714 if ($this->form->is_cancelled()) { 715 // Form was cancelled - return to the course page. 716 $urltogo = course_get_url($this->courseid ?: $this->feedback->course); 717 } else if ($this->form->is_submitted() && 718 ($this->form->is_validated() || $gopreviouspage)) { 719 // Form was submitted (skip validation for "Previous page" button). 720 $data = $this->form->get_submitted_data(); 721 if (!isset($SESSION->feedback->is_started) OR !$SESSION->feedback->is_started == true) { 722 print_error('error', '', $CFG->wwwroot.'/course/view.php?id='.$this->courseid); 723 } 724 $this->save_response_tmp($data); 725 if (!empty($data->savevalues) || !empty($data->gonextpage)) { 726 if (($nextpage = $this->get_next_page($gopage)) !== null) { 727 if ($PAGE->has_set_url()) { 728 $urltogo = new moodle_url($PAGE->url, array('gopage' => $nextpage)); 729 } 730 $this->jumpto = $nextpage; 731 } else { 732 $this->save_response(); 733 if (!$this->get_feedback()->page_after_submit) { 734 \core\notification::success(get_string('entries_saved', 'feedback')); 735 } 736 $this->justcompleted = true; 737 } 738 } else if (!empty($gopreviouspage)) { 739 $prevpage = intval($this->get_previous_page($gopage)); 740 if ($PAGE->has_set_url()) { 741 $urltogo = new moodle_url($PAGE->url, array('gopage' => $prevpage)); 742 } 743 $this->jumpto = $prevpage; 744 } 745 } 746 return $urltogo; 747 } 748 749 /** 750 * Render the form with the questions. 751 * 752 * @return string the form rendered 753 * @since Moodle 3.3 754 */ 755 public function render_items() { 756 global $SESSION; 757 758 // Print the items. 759 $SESSION->feedback->is_started = true; 760 return $this->form->render(); 761 } 762 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body