Proyectos de Subversion Moodle

Rev

Rev 1 | Mostrar el archivo completo | | | Autoría | Ultima modificación | Ver Log |

Rev 1 Rev 1441
Línea 126... Línea 126...
126
        }
126
        }
127
        $attempt = $lastattempt;
127
        $attempt = $lastattempt;
128
    }
128
    }
Línea 129... Línea 129...
129
 
129
 
130
    $attempt->attempt = $attemptnumber;
-
 
131
    $attempt->timestart = $timenow;
130
    $attempt->attempt = $attemptnumber;
132
    $attempt->timefinish = 0;
131
    $attempt->timefinish = 0;
133
    $attempt->timemodified = $timenow;
132
    $attempt->timemodified = $timenow;
134
    $attempt->timemodifiedoffline = 0;
-
 
135
    $attempt->state = quiz_attempt::IN_PROGRESS;
133
    $attempt->timemodifiedoffline = 0;
136
    $attempt->currentpage = 0;
134
    $attempt->currentpage = 0;
137
    $attempt->sumgrades = null;
135
    $attempt->sumgrades = null;
-
 
136
    $attempt->gradednotificationsenttime = null;
Línea 138... Línea 137...
138
    $attempt->gradednotificationsenttime = null;
137
    $attempt->timecheckstate = null;
139
 
138
 
140
    // If this is a preview, mark it as such.
139
    // If this is a preview, mark it as such.
141
    if ($ispreview) {
140
    if ($ispreview) {
Línea 142... Línea -...
142
        $attempt->preview = 1;
-
 
143
    }
-
 
144
 
-
 
145
    $timeclose = $quizobj->get_access_manager($timenow)->get_end_time($attempt);
-
 
146
    if ($timeclose === false || $ispreview) {
-
 
147
        $attempt->timecheckstate = null;
-
 
148
    } else {
-
 
149
        $attempt->timecheckstate = $timeclose;
-
 
150
    }
-
 
151
 
141
        $attempt->preview = 1;
152
    di::get(hook\manager::class)->dispatch(new attempt_state_changed(null, $attempt));
142
    }
153
 
143
 
154
    return $attempt;
144
    return $attempt;
155
}
145
}
Línea 264... Línea 254...
264
            $forcedvariantsbyslot, $quba);
254
            $forcedvariantsbyslot, $quba);
265
        $variantstrategy = new question_variant_forced_choices_selection_strategy(
255
        $variantstrategy = new question_variant_forced_choices_selection_strategy(
266
            $forcedvariantsbyseed, $variantstrategy);
256
            $forcedvariantsbyseed, $variantstrategy);
267
    }
257
    }
Línea 268... Línea 258...
268
 
258
 
Línea 269... Línea 259...
269
    $quba->start_all_questions($variantstrategy, $timenow, $attempt->userid);
259
    $quba->start_all_questions($variantstrategy, question_attempt_step::TIMECREATED_ON_FIRST_RENDER, $attempt->userid);
270
 
260
 
271
    // Work out the attempt layout.
261
    // Work out the attempt layout.
272
    $sections = $quizobj->get_sections();
262
    $sections = $quizobj->get_sections();
Línea 352... Línea 342...
352
    $attempt->layout = implode(',', $newlayout);
342
    $attempt->layout = implode(',', $newlayout);
353
    return $attempt;
343
    return $attempt;
354
}
344
}
Línea 355... Línea 345...
355
 
345
 
356
/**
346
/**
-
 
347
 * Create or update the quiz attempt record, and the question usage.
-
 
348
 *
-
 
349
 * If the attempt already exists in the database with the NOT_STARTED state, it will be transitioned
-
 
350
 * to IN_PROGRESS and the timestart updated. If it does not already exist, a new record will be created
357
 * The save started question usage and quiz attempt in db and log the started attempt.
351
 * already in the IN_PROGRESS state.
358
 *
352
 *
359
 * @param quiz_settings $quizobj
353
 * @param quiz_settings $quizobj
360
 * @param question_usage_by_activity $quba
354
 * @param question_usage_by_activity $quba
-
 
355
 * @param stdClass                     $attempt
361
 * @param stdClass                     $attempt
356
 * @param ?int $timenow The time to use for the attempt's timestart property. Defaults to time().
362
 * @return stdClass                    attempt object with uniqueid and id set.
357
 * @return stdClass                    attempt object with uniqueid and id set.
363
 */
358
 */
-
 
359
function quiz_attempt_save_started(
-
 
360
    quiz_settings $quizobj,
-
 
361
    question_usage_by_activity $quba,
-
 
362
    \stdClass $attempt,
-
 
363
    ?int $timenow = null,
364
function quiz_attempt_save_started($quizobj, $quba, $attempt) {
364
): stdClass {
-
 
365
    global $DB;
-
 
366
 
-
 
367
    $attempt->timestart = $timenow ?? time();
-
 
368
 
-
 
369
    $timeclose = $quizobj->get_access_manager($attempt->timestart)->get_end_time($attempt);
-
 
370
    if ($timeclose === false || $attempt->preview) {
-
 
371
        $attempt->timecheckstate = null;
-
 
372
    } else {
-
 
373
        $attempt->timecheckstate = $timeclose;
-
 
374
    }
-
 
375
 
-
 
376
    $originalattempt = null;
-
 
377
    if (isset($attempt->id) && $attempt->state === quiz_attempt::NOT_STARTED) {
-
 
378
        $originalattempt = clone $attempt;
-
 
379
        // In case questions have been edited since attempts were pre-created, update questions now.
-
 
380
        quiz_attempt::create($attempt->id)->update_questions_to_new_version_if_changed();
-
 
381
        // Update the attempt's state.
-
 
382
        $attempt->state = quiz_attempt::IN_PROGRESS;
-
 
383
        $DB->update_record('quiz_attempts', $attempt);
365
    global $DB;
384
    } else {
366
    // Save the attempt in the database.
385
        // Save the attempt in the database.
367
    question_engine::save_questions_usage_by_activity($quba);
386
        question_engine::save_questions_usage_by_activity($quba);
-
 
387
        $attempt->uniqueid = $quba->get_id();
368
    $attempt->uniqueid = $quba->get_id();
388
        $attempt->state = quiz_attempt::IN_PROGRESS;
-
 
389
        $attempt->id = $DB->insert_record('quiz_attempts', $attempt);
Línea 369... Línea 390...
369
    $attempt->id = $DB->insert_record('quiz_attempts', $attempt);
390
    }
370
 
391
 
371
    // Params used by the events below.
392
    // Params used by the events below.
372
    $params = [
393
    $params = [
Línea 389... Línea 410...
389
    // Trigger the event.
410
    // Trigger the event.
390
    $event->add_record_snapshot('quiz', $quizobj->get_quiz());
411
    $event->add_record_snapshot('quiz', $quizobj->get_quiz());
391
    $event->add_record_snapshot('quiz_attempts', $attempt);
412
    $event->add_record_snapshot('quiz_attempts', $attempt);
392
    $event->trigger();
413
    $event->trigger();
Línea -... Línea 414...
-
 
414
 
-
 
415
    di::get(hook\manager::class)->dispatch(new attempt_state_changed($originalattempt, $attempt));
-
 
416
 
-
 
417
    return $attempt;
-
 
418
}
-
 
419
 
-
 
420
/**
-
 
421
 * Create the quiz attempt record, and the question usage.
-
 
422
 *
-
 
423
 * This saves an attempt in the NOT_STARTED state, and is designed for use when pre-creating attempts
-
 
424
 * ahead of the quiz start time to spread out the processing load.
-
 
425
 *
-
 
426
 * @param question_usage_by_activity $quba
-
 
427
 * @param stdClass $attempt
-
 
428
 * @return stdClass attempt object with uniqueid and id set.
-
 
429
 */
-
 
430
function quiz_attempt_save_not_started(question_usage_by_activity $quba, stdClass $attempt): stdClass {
-
 
431
    global $DB;
-
 
432
    // Save the attempt in the database.
-
 
433
    question_engine::save_questions_usage_by_activity($quba);
-
 
434
    $attempt->uniqueid = $quba->get_id();
-
 
435
    $attempt->state = quiz_attempt::NOT_STARTED;
-
 
436
    $attempt->id = $DB->insert_record('quiz_attempts', $attempt);
393
 
437
    di::get(hook\manager::class)->dispatch(new attempt_state_changed(null, $attempt));
394
    return $attempt;
438
    return $attempt;
Línea 395... Línea 439...
395
}
439
}
396
 
440
 
Línea 798... Línea 842...
798
 
842
 
799
   /*
843
   /*
800
    * Each database handles updates with inner joins differently:
844
    * Each database handles updates with inner joins differently:
801
    *  - mysql does not allow a FROM clause
845
    *  - mysql does not allow a FROM clause
802
    *  - postgres and mssql allow FROM but handle table aliases differently
-
 
803
    *  - oracle requires a subquery
846
    *  - postgres and mssql allow FROM but handle table aliases differently
804
    *
847
    *
805
    * Different code for each database.
848
    * Different code for each database.
Línea 806... Línea 849...
806
    */
849
    */
Línea 825... Línea 868...
825
                        FROM {quiz_attempts} quiza
868
                        FROM {quiz_attempts} quiza
826
                        JOIN {quiz} quiz ON quiz.id = quiza.quiz
869
                        JOIN {quiz} quiz ON quiz.id = quiza.quiz
827
                        JOIN ( $quizausersql ) quizauser ON quizauser.id = quiza.id
870
                        JOIN ( $quizausersql ) quizauser ON quizauser.id = quiza.id
828
                       WHERE $attemptselect";
871
                       WHERE $attemptselect";
829
    } else {
872
    } else {
830
        // oracle, sqlite and others
-
 
831
        $updatesql = "UPDATE {quiz_attempts} quiza
-
 
832
                         SET timecheckstate = (
-
 
833
                           SELECT $timecheckstatesql
-
 
834
                             FROM {quiz} quiz, ( $quizausersql ) quizauser
873
        throw new \core\exception\coding_exception("Unsupported database family: {$dbfamily}");
835
                            WHERE quiz.id = quiza.quiz
-
 
836
                              AND quizauser.id = quiza.id
-
 
837
                         )
-
 
838
                         WHERE $attemptselect";
-
 
839
    }
874
    }
Línea 840... Línea 875...
840
 
875
 
841
    $DB->execute($updatesql, $params);
876
    $DB->execute($updatesql, $params);
Línea 978... Línea 1013...
978
 * @param string $state one of the state constants like {@see quiz_attempt::IN_PROGRESS}.
1013
 * @param string $state one of the state constants like {@see quiz_attempt::IN_PROGRESS}.
979
 * @return string The lang string to describe that state.
1014
 * @return string The lang string to describe that state.
980
 */
1015
 */
981
function quiz_attempt_state_name($state) {
1016
function quiz_attempt_state_name($state) {
982
    switch ($state) {
1017
    switch ($state) {
-
 
1018
        case quiz_attempt::NOT_STARTED:
-
 
1019
            return get_string('statenotstarted', 'quiz');
983
        case quiz_attempt::IN_PROGRESS:
1020
        case quiz_attempt::IN_PROGRESS:
984
            return get_string('stateinprogress', 'quiz');
1021
            return get_string('stateinprogress', 'quiz');
985
        case quiz_attempt::OVERDUE:
1022
        case quiz_attempt::OVERDUE:
986
            return get_string('stateoverdue', 'quiz');
1023
            return get_string('stateoverdue', 'quiz');
-
 
1024
        case quiz_attempt::SUBMITTED:
-
 
1025
            return get_string('statesubmitted', 'quiz');
987
        case quiz_attempt::FINISHED:
1026
        case quiz_attempt::FINISHED:
988
            return get_string('statefinished', 'quiz');
1027
            return get_string('statefinished', 'quiz');
989
        case quiz_attempt::ABANDONED:
1028
        case quiz_attempt::ABANDONED:
990
            return get_string('stateabandoned', 'quiz');
1029
            return get_string('stateabandoned', 'quiz');
991
        default:
1030
        default:
Línea 1389... Línea 1428...
1389
    }
1428
    }
Línea 1390... Línea 1429...
1390
 
1429
 
1391
    $a = new stdClass();
1430
    $a = new stdClass();
1392
    // Course info.
1431
    // Course info.
1393
    $a->courseid        = $course->id;
1432
    $a->courseid        = $course->id;
1394
    $a->coursename      = $course->fullname;
1433
    $a->coursename      = format_string($course->fullname, true, ['context' => $context]);
1395
    $a->courseshortname = $course->shortname;
1434
    $a->courseshortname = format_string($course->shortname, true, ['context' => $context]);
1396
    // Quiz info.
1435
    // Quiz info.
1397
    $a->quizname        = $quiz->name;
1436
    $a->quizname        = format_string($quiz->name, true, ['context' => $context]);
1398
    $a->quizreporturl   = $CFG->wwwroot . '/mod/quiz/report.php?id=' . $cm->id;
1437
    $a->quizreporturl   = $CFG->wwwroot . '/mod/quiz/report.php?id=' . $cm->id;
1399
    $a->quizreportlink  = '<a href="' . $a->quizreporturl . '">' .
-
 
1400
            format_string($quiz->name) . ' report</a>';
1438
    $a->quizreportlink  = '<a href="' . $a->quizreporturl . '">' . $a->quizname . ' report</a>';
1401
    $a->quizurl         = $CFG->wwwroot . '/mod/quiz/view.php?id=' . $cm->id;
1439
    $a->quizurl         = $CFG->wwwroot . '/mod/quiz/view.php?id=' . $cm->id;
1402
    $a->quizlink        = '<a href="' . $a->quizurl . '">' . format_string($quiz->name) . '</a>';
1440
    $a->quizlink        = '<a href="' . $a->quizurl . '">' . $a->quizname . '</a>';
1403
    $a->quizid          = $quiz->id;
1441
    $a->quizid          = $quiz->id;
1404
    $a->quizcmid        = $cm->id;
1442
    $a->quizcmid        = $cm->id;
1405
    // Attempt info.
1443
    // Attempt info.
1406
    $a->submissiontime  = userdate($attempt->timefinish);
1444
    $a->submissiontime  = userdate($attempt->timefinish);
1407
    $a->timetaken       = format_time($attempt->timefinish - $attempt->timestart);
1445
    $a->timetaken       = format_time($attempt->timefinish - $attempt->timestart);
1408
    $a->quizreviewurl   = $CFG->wwwroot . '/mod/quiz/review.php?attempt=' . $attempt->id;
1446
    $a->quizreviewurl   = $CFG->wwwroot . '/mod/quiz/review.php?attempt=' . $attempt->id;
1409
    $a->quizreviewlink  = '<a href="' . $a->quizreviewurl . '">' .
-
 
1410
            format_string($quiz->name) . ' review</a>';
1447
    $a->quizreviewlink  = '<a href="' . $a->quizreviewurl . '">' . $a->quizname . ' review</a>';
1411
    $a->attemptid       = $attempt->id;
1448
    $a->attemptid       = $attempt->id;
1412
    // Student who sat the quiz info.
1449
    // Student who sat the quiz info.
1413
    $a->studentidnumber = $submitter->idnumber;
1450
    $a->studentidnumber = $submitter->idnumber;
1414
    $a->studentname     = fullname($submitter);
1451
    $a->studentname     = fullname($submitter);
Línea 1635... Línea 1672...
1635
 * @param bool $showquestiontext If true (default), show question text after question name.
1672
 * @param bool $showquestiontext If true (default), show question text after question name.
1636
 *       If false, show only question name.
1673
 *       If false, show only question name.
1637
 * @param bool $showidnumber If true, show the question's idnumber, if any. False by default.
1674
 * @param bool $showidnumber If true, show the question's idnumber, if any. False by default.
1638
 * @param core_tag_tag[]|bool $showtags if array passed, show those tags. Else, if true, get and show tags,
1675
 * @param core_tag_tag[]|bool $showtags if array passed, show those tags. Else, if true, get and show tags,
1639
 *       else, don't show tags (which is the default).
1676
 *       else, don't show tags (which is the default).
-
 
1677
 * @param bool $displaytaglink Indicates whether the tag should be displayed as a link.
1640
 * @return string HTML fragment.
1678
 * @return string HTML fragment.
1641
 */
1679
 */
1642
function quiz_question_tostring($question, $showicon = false, $showquestiontext = true,
1680
function quiz_question_tostring($question, $showicon = false, $showquestiontext = true,
1643
        $showidnumber = false, $showtags = false) {
1681
        $showidnumber = false, $showtags = false, $displaytaglink = true) {
1644
    global $OUTPUT;
1682
    global $OUTPUT;
1645
    $result = '';
1683
    $result = '';
Línea 1646... Línea 1684...
1646
 
1684
 
1647
    // Question name.
1685
    // Question name.
Línea 1665... Línea 1703...
1665
        $tags = core_tag_tag::get_item_tags('core_question', 'question', $question->id);
1703
        $tags = core_tag_tag::get_item_tags('core_question', 'question', $question->id);
1666
    } else {
1704
    } else {
1667
        $tags = [];
1705
        $tags = [];
1668
    }
1706
    }
1669
    if ($tags) {
1707
    if ($tags) {
1670
        $result .= $OUTPUT->tag_list($tags, null, 'd-inline', 0, null, true);
1708
        $result .= $OUTPUT->tag_list($tags, null, 'd-inline', 0, null, true, $displaytaglink);
1671
    }
1709
    }
Línea 1672... Línea 1710...
1672
 
1710
 
1673
    // Question text.
1711
    // Question text.
1674
    if ($showquestiontext) {
1712
    if ($showquestiontext) {
Línea 1722... Línea 1760...
1722
        throw new coding_exception(
1760
        throw new coding_exception(
1723
                'Adding "random" questions via quiz_add_quiz_question() is deprecated. Please use quiz_add_random_questions().'
1761
                'Adding "random" questions via quiz_add_quiz_question() is deprecated. Please use quiz_add_random_questions().'
1724
        );
1762
        );
1725
    }
1763
    }
Línea -... Línea 1764...
-
 
1764
 
-
 
1765
    // If the question type is invalid, we cannot add it to the quiz. It shouldn't be possible to get to this
-
 
1766
    // point without fiddling with the DOM so we can just throw an exception.
-
 
1767
    if (!\question_bank::is_qtype_installed($questiontype)) {
-
 
1768
        throw new coding_exception('Invalid question type: ' . $questiontype);
-
 
1769
    }
1726
 
1770
 
Línea 1727... Línea 1771...
1727
    $trans = $DB->start_delegated_transaction();
1771
    $trans = $DB->start_delegated_transaction();
1728
 
1772
 
1729
    $sql = "SELECT qbe.id
1773
    $sql = "SELECT qbe.id
Línea 1971... Línea 2015...
1971
    // Look for an existing attempt.
2015
    // Look for an existing attempt.
1972
    $attempts = quiz_get_user_attempts($quizobj->get_quizid(), $USER->id, 'all', true);
2016
    $attempts = quiz_get_user_attempts($quizobj->get_quizid(), $USER->id, 'all', true);
1973
    $lastattempt = end($attempts);
2017
    $lastattempt = end($attempts);
Línea 1974... Línea 2018...
1974
 
2018
 
-
 
2019
    $attemptnumber = null;
1975
    $attemptnumber = null;
2020
    if (
1976
    // If an in-progress attempt exists, check password then redirect to it.
2021
        $lastattempt
-
 
2022
        && in_array($lastattempt->state, [quiz_attempt::NOT_STARTED, quiz_attempt::IN_PROGRESS, quiz_attempt::OVERDUE])
1977
    if ($lastattempt && ($lastattempt->state == quiz_attempt::IN_PROGRESS ||
2023
    ) {
1978
            $lastattempt->state == quiz_attempt::OVERDUE)) {
2024
        // If an in-progress or not-started attempt exists, check password then redirect to it.
-
 
2025
        $currentattemptid = $lastattempt->id;
1979
        $currentattemptid = $lastattempt->id;
2026
 
Línea 1980... Línea 2027...
1980
        $messages = $accessmanager->prevent_access();
2027
        $messages = $accessmanager->prevent_access();
1981
 
2028
 
Línea 2049... Línea 2096...
2049
    quiz_delete_previews($quizobj->get_quiz(), $userid);
2096
    quiz_delete_previews($quizobj->get_quiz(), $userid);
Línea 2050... Línea 2097...
2050
 
2097
 
2051
    $quba = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context());
2098
    $quba = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context());
Línea -... Línea 2099...
-
 
2099
    $quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour);
-
 
2100
 
-
 
2101
    $attempt = $DB->get_record(
-
 
2102
        'quiz_attempts',
-
 
2103
        [
-
 
2104
            'quiz' => $quizobj->get_quizid(),
-
 
2105
            'userid' => $userid,
-
 
2106
            'preview' => 0,
-
 
2107
            'state' => quiz_attempt::NOT_STARTED,
-
 
2108
        ],
2052
    $quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour);
2109
    );
2053
 
2110
    if (!$attempt) {
2054
    // Create the new attempt and initialize the question sessions
2111
        // Create the new attempt and initialize the question sessions.
2055
    $timenow = time(); // Update time now, in case the server is running really slowly.
2112
        $timenow = time(); // Update time now, in case the server is running really slowly.
2056
    $attempt = quiz_create_attempt($quizobj, $attemptnumber, $lastattempt, $timenow, $ispreviewuser, $userid);
2113
        $attempt = quiz_create_attempt($quizobj, $attemptnumber, $lastattempt, $timenow, $ispreviewuser, $userid);
2057
 
2114
 
-
 
2115
        if (!($quizobj->get_quiz()->attemptonlast && $lastattempt)) {
-
 
2116
            $attempt = quiz_start_new_attempt(
-
 
2117
                $quizobj,
-
 
2118
                $quba,
-
 
2119
                $attempt,
2058
    if (!($quizobj->get_quiz()->attemptonlast && $lastattempt)) {
2120
                $attemptnumber,
-
 
2121
                $timenow,
-
 
2122
                $forcedrandomquestions,
2059
        $attempt = quiz_start_new_attempt($quizobj, $quba, $attempt, $attemptnumber, $timenow,
2123
                $forcedvariants,
2060
                $forcedrandomquestions, $forcedvariants);
2124
            );
-
 
2125
        } else {
2061
    } else {
2126
            $attempt = quiz_start_attempt_built_on_last($quba, $attempt, $lastattempt);
Línea 2062... Línea 2127...
2062
        $attempt = quiz_start_attempt_built_on_last($quba, $attempt, $lastattempt);
2127
        }
Línea 2063... Línea 2128...
2063
    }
2128
    }