<?php
/**
 * This script lists student attempts
 *
 * @author Martin Dougiamas, Tim Hunt and others.
 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
 * @package quiz
 */

require_once($CFG->libdir.'/tablelib.php');
require_once($CFG->dirroot.'/mod/quiz/report/overview/overviewsettings_form.php');
require_once($CFG->dirroot.'/mod/quiz/report/overview/overview_table.php');

class quiz_overview_report extends quiz_default_report {

    /**
     * Display the report.
     */
    function display($quiz, $cm, $course) {
        global $CFG, $COURSE, $DB, $OUTPUT;

        $this->context = get_context_instance(CONTEXT_MODULE, $cm->id);

        // Work out some display options - whether there is feedback, and whether scores should be shown.
        $hasfeedback = quiz_has_feedback($quiz);
        $fakeattempt = new stdClass();
        $fakeattempt->preview = false;
        $fakeattempt->timefinish = $quiz->timeopen;
        $fakeattempt->userid = 0;
        $reviewoptions = quiz_get_reviewoptions($quiz, $fakeattempt, $this->context);
        $showgrades = quiz_has_grades($quiz) && $reviewoptions->scores;

        $download = optional_param('download', '', PARAM_ALPHA);

        /// find out current groups mode
        $currentgroup = groups_get_activity_group($cm, true);
        if (!$students = get_users_by_capability($this->context, array('mod/quiz:reviewmyattempts', 'mod/quiz:attempt'),'u.id,1','','','','','',false)) {
            $students = array();
        } else {
            $students = array_keys($students);
        }

        if (empty($currentgroup)) {
            // all users who can attempt quizzes
            $allowed = $students;
            $groupstudents = array();
        } else {
            // all users who can attempt quizzes and who are in the currently selected group
            if (!$groupstudents = get_users_by_capability($this->context, array('mod/quiz:reviewmyattempts', 'mod/quiz:attempt'),'u.id,1','','','',$currentgroup,'',false)) {
                $groupstudents = array();
            } else {
                $groupstudents = array_keys($groupstudents);
            }
            $allowed = $groupstudents;
        }

        $pageoptions = array();
        $pageoptions['id'] = $cm->id;
        $pageoptions['mode'] = 'overview';

        $reporturl = new moodle_url('/mod/quiz/report.php', $pageoptions);
        $qmsubselect = quiz_report_qm_filter_select($quiz);

        $mform = new mod_quiz_report_overview_settings($reporturl, array('qmsubselect'=> $qmsubselect, 'quiz'=>$quiz,
                                                             'currentgroup'=>$currentgroup, 'context'=>$this->context));
        if ($fromform = $mform->get_data()) {
            $regradeall = false;
            $regradealldry = false;
            $regradealldrydo = false;
            $attemptsmode = $fromform->attemptsmode;
            if ($qmsubselect) {
                //control is not on the form if
                //the grading method is not set
                //to grade one attempt per user eg. for average attempt grade.
                $qmfilter = $fromform->qmfilter;
            } else {
                $qmfilter = 0;
            }
            $regradefilter = $fromform->regradefilter;
            set_user_preference('quiz_report_overview_detailedmarks', $fromform->detailedmarks);
            set_user_preference('quiz_report_pagesize', $fromform->pagesize);
            $detailedmarks = $fromform->detailedmarks;
            $pagesize = $fromform->pagesize;
        } else {
            $regradeall  = optional_param('regradeall', 0, PARAM_BOOL);
            $regradealldry  = optional_param('regradealldry', 0, PARAM_BOOL);
            $regradealldrydo  = optional_param('regradealldrydo', 0, PARAM_BOOL);
            $attemptsmode = optional_param('attemptsmode', null, PARAM_INT);
            if ($qmsubselect) {
                $qmfilter = optional_param('qmfilter', 0, PARAM_INT);
            } else {
                $qmfilter = 0;
            }
            $regradefilter = optional_param('regradefilter', 0, PARAM_INT);

            $detailedmarks = get_user_preferences('quiz_report_overview_detailedmarks', 1);
            $pagesize = get_user_preferences('quiz_report_pagesize', 0);
        }
        if ($currentgroup) {
            //default for when a group is selected
            if ($attemptsmode === null  || $attemptsmode == QUIZ_REPORT_ATTEMPTS_ALL) {
                $attemptsmode = QUIZ_REPORT_ATTEMPTS_STUDENTS_WITH;
            }
        } else if (!$currentgroup && $course->id == SITEID) {
            //force report on front page to show all, unless a group is selected.
            $attemptsmode = QUIZ_REPORT_ATTEMPTS_ALL;
        } else if ($attemptsmode === null) {
            //default
            $attemptsmode = QUIZ_REPORT_ATTEMPTS_ALL;
        }
        if (!$reviewoptions->scores) {
            $detailedmarks = 0;
        }
        if ($pagesize < 1) {
            $pagesize = QUIZ_REPORT_DEFAULT_PAGE_SIZE;
        }
        // We only want to show the checkbox to delete attempts
        // if the user has permissions and if the report mode is showing attempts.
        $candelete = has_capability('mod/quiz:deleteattempts', $this->context)
                && ($attemptsmode != QUIZ_REPORT_ATTEMPTS_STUDENTS_WITH_NO);

        $displayoptions = array();
        $displayoptions['attemptsmode'] = $attemptsmode;
        $displayoptions['qmfilter'] = $qmfilter;
        $displayoptions['regradefilter'] = $regradefilter;

        if ($attemptsmode == QUIZ_REPORT_ATTEMPTS_ALL) {
            $allowed = array();
        }

        if (empty($currentgroup) || $groupstudents) {
            if (optional_param('delete', 0, PARAM_BOOL) && confirm_sesskey()) {
                if ($attemptids = optional_param('attemptid', array(), PARAM_INT)) {
                    require_capability('mod/quiz:deleteattempts', $this->context);
                    $this->delete_selected_attempts($quiz, $cm, $attemptids, $allowed, $groupstudents);
                    redirect($reporturl->out(false, $displayoptions));
                }
            } else if (optional_param('regrade', 0, PARAM_BOOL) && confirm_sesskey()) {
                if ($attemptids = optional_param('attemptid', array(), PARAM_INT)) {
                    $this->regrade_selected_attempts($quiz, $attemptids, $groupstudents);
                    redirect($reporturl->out(false, $displayoptions));
                }
            }
        }

        //work out the sql for this table.
        if ($detailedmarks) {
            $questions = quiz_report_load_questions($quiz);
        } else {
            $questions = array();
        }
        $table = new quiz_report_overview_table($quiz , $qmsubselect, $groupstudents,
                $students, $detailedmarks, $questions, $candelete, $reporturl,
                $displayoptions, $this->context);
        $table->is_downloading($download, get_string('reportoverview','quiz'),
                    "$COURSE->shortname ".format_string($quiz->name,true));
        if (!$table->is_downloading()) {
            // Only print headers if not asked to download data
            $this->print_header_and_tabs($cm, $course, $quiz, "overview");
        }

        if ($regradeall && confirm_sesskey()) {
            $this->regrade_all(false, $quiz, $groupstudents);
        } else if ($regradealldry && confirm_sesskey()) {
            $this->regrade_all(true, $quiz, $groupstudents);
        } else if ($regradealldrydo && confirm_sesskey()) {
            $this->regrade_all_needed($quiz, $groupstudents);
        }
        if ($regradeall || $regradealldry || $regradealldrydo) {
            redirect($reporturl->out(false, $displayoptions), '', 5);
        }

        if ($groupmode = groups_get_activity_groupmode($cm)) {   // Groups are being used
            if (!$table->is_downloading()) {
                groups_print_activity_menu($cm, $reporturl->out(true, $displayoptions));
            }
        }

        $nostudents = false;
        if (!$students) {
            if (!$table->is_downloading()) {
                echo $OUTPUT->notification(get_string('nostudentsyet'));
            }
            $nostudents = true;
        } else if ($currentgroup && !$groupstudents) {
            if (!$table->is_downloading()) {
                echo $OUTPUT->notification(get_string('nostudentsingroup'));
            }
            $nostudents = true;
        }
        if (!$table->is_downloading()) {
            // Print display options
            $mform->set_data($displayoptions +compact('detailedmarks', 'pagesize'));
            $mform->display();

            // Print information on the number of existing attempts
            if ($strattemptnum = quiz_num_attempt_summary($quiz, $cm, true, $currentgroup)) {
                echo '<div class="quizattemptcounts">' . $strattemptnum . '</div>';
            }
        }

        if (!$nostudents || ($attemptsmode == QUIZ_REPORT_ATTEMPTS_ALL)) {

            // Construct the SQL
            $fields = $DB->sql_concat('u.id', '\'#\'', 'COALESCE(qa.attempt, \'0\')').' AS uniqueid, ';
            if ($qmsubselect) {
                $fields .=
                    "(CASE " .
                    "   WHEN $qmsubselect THEN 1" .
                    "   ELSE 0 " .
                    "END) AS gradedattempt, ";
            }

            $fields .='qa.uniqueid AS attemptuniqueid, qa.id AS attempt, ' .
                'u.id AS userid, u.idnumber, u.firstname, u.lastname, u.picture, u.imagealt, u.email, '.
                'qa.sumgrades, qa.timefinish, qa.timestart, qa.timefinish - qa.timestart AS duration ';

            // This part is the same for all cases - join users and quiz_attempts tables
            $from = '{user} u ';
            $from .= 'LEFT JOIN {quiz_attempts} qa ON qa.userid = u.id AND qa.quiz = :quizid';
            $params = array('quizid' => $quiz->id);

            if ($qmsubselect && $qmfilter) {
                $from .= ' AND '.$qmsubselect;
            }
            switch ($attemptsmode) {
                 case QUIZ_REPORT_ATTEMPTS_ALL:
                     // Show all attempts, including students who are no longer in the course
                    $where = 'qa.id IS NOT NULL AND qa.preview = 0';
                     break;
                 case QUIZ_REPORT_ATTEMPTS_STUDENTS_WITH:
                     // Show only students with attempts
                     list($allowed_usql, $allowed_params) = $DB->get_in_or_equal($allowed, SQL_PARAMS_NAMED, 'u0000');
                     $params += $allowed_params;
                    $where = "u.id $allowed_usql AND qa.preview = 0 AND qa.id IS NOT NULL";
                     break;
                 case QUIZ_REPORT_ATTEMPTS_STUDENTS_WITH_NO:
                     // Show only students without attempts
                     list($allowed_usql, $allowed_params) = $DB->get_in_or_equal($allowed, SQL_PARAMS_NAMED, 'u0000');
                     $params += $allowed_params;
                    $where = "u.id $allowed_usql AND qa.id IS NULL";
                     break;
                 case QUIZ_REPORT_ATTEMPTS_ALL_STUDENTS:
                     // Show all students with or without attempts
                     list($allowed_usql, $allowed_params) = $DB->get_in_or_equal($allowed, SQL_PARAMS_NAMED, 'u0000');
                     $params += $allowed_params;
                    $where = "u.id $allowed_usql AND (qa.preview = 0 OR qa.preview IS NULL)";
                     break;
             }

            $table->set_count_sql("SELECT COUNT(1) FROM $from WHERE $where", $params);

            $sqlobject = new stdClass();
            $sqlobject->from = $from;
            $sqlobject->where = $where;
            $sqlobject->params = $params;
            //test to see if there are any regraded attempts to be listed.
            if (quiz_get_regraded_qs($sqlobject, 0, 1)) {
                $regradedattempts = true;
            } else {
                $regradedattempts = false;
            }
            $fields .= ', COALESCE((SELECT MAX(qqr.regraded) FROM {quiz_question_regrade} qqr WHERE qqr.attemptid = qa.uniqueid),-1) AS regraded';
            if ($regradefilter) {
                $where .= ' AND COALESCE((SELECT MAX(qqr.regraded) FROM {quiz_question_regrade} qqr WHERE qqr.attemptid = qa.uniqueid),-1) !=\'-1\'';
            }
            $table->set_sql($fields, $from, $where, $params);

            // Define table columns
            $columns = array();
            $headers = array();
            if (!$table->is_downloading()) { //do not print notices when downloading
                //regrade buttons
                if (has_capability('mod/quiz:regrade', $this->context)) {
                    $countregradeneeded = $this->count_regrade_all_needed($quiz, $groupstudents);
                    if ($currentgroup) {
                        $a= new stdClass();
                        $a->groupname = groups_get_group_name($currentgroup);
                        $a->coursestudents = get_string('participants');
                        $a->countregradeneeded = $countregradeneeded;
                        $regradealldrydolabel = get_string('regradealldrydogroup', 'quiz_overview', $a);
                        $regradealldrylabel = get_string('regradealldrygroup', 'quiz_overview', $a);
                        $regradealllabel = get_string('regradeallgroup', 'quiz_overview', $a);
                    } else {
                        $regradealldrydolabel = get_string('regradealldrydo', 'quiz_overview', $countregradeneeded);
                        $regradealldrylabel = get_string('regradealldry', 'quiz_overview');
                        $regradealllabel = get_string('regradeall', 'quiz_overview');
                    }
                    $displayurl = new moodle_url($reporturl, $displayoptions);
                    echo '<div class="mdl-align">';
                    echo '<form action="'.$displayurl->out_omit_querystring().'">';
                    echo '<div>';
                    echo html_writer::input_hidden_params($displayurl);
                    echo html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'sesskey', 'value' => sesskey())) . "\n";
                    echo '<input type="submit" name="regradeall" value="'.$regradealllabel.'"/>';
                    echo '<input type="submit" name="regradealldry" value="'.$regradealldrylabel.'"/>';
                    if ($countregradeneeded) {
                        echo '<input type="submit" name="regradealldrydo" value="'.$regradealldrydolabel.'"/>';
                    }
                    echo '</div>';
                    echo '</form>';
                    echo '</div>';
                }
                // Print information on the grading method
                if ($strattempthighlight = quiz_report_highlighting_grading_method($quiz, $qmsubselect, $qmfilter)) {
                    echo '<div class="quizattemptcounts">' . $strattempthighlight . '</div>';
                }
            }

            if (!$table->is_downloading() && $candelete) {
                $columns[]= 'checkbox';
                $headers[]= NULL;
            }

            if (!$table->is_downloading() && $CFG->grade_report_showuserimage) {
                $columns[]= 'picture';
                $headers[]= '';
            }
            if (!$table->is_downloading()) {
                $columns[]= 'fullname';
                $headers[]= get_string('name');
             } else {
                $columns[]= 'lastname';
                $headers[]= get_string('lastname');
                $columns[]= 'firstname';
                $headers[]= get_string('firstname');
             }

            if ($CFG->grade_report_showuseridnumber) {
                $columns[]= 'idnumber';
                $headers[]= get_string('idnumber');
            }

            $columns[]= 'timestart';
            $headers[]= get_string('startedon', 'quiz');

            $columns[]= 'timefinish';
            $headers[]= get_string('timecompleted','quiz');

            $columns[]= 'duration';
            $headers[]= get_string('attemptduration', 'quiz');

            if ($detailedmarks) {
                foreach ($questions as $id => $question) {
                    // Ignore questions of zero length
                    $columns[] = 'qsgrade'.$id;
                    $header = '#'.$question->number;
                    if (!$table->is_downloading()) {
                        $header .='<br />';
                    } else {
                        $header .=' ';
                    }
                    $header .='--/'.quiz_rescale_grade($question->maxgrade, $quiz, 'question');
                    $headers[] = $header;
                    $question->formattedname = strip_tags(format_string($question->name));
                }
            }
            if (!$table->is_downloading() && has_capability('mod/quiz:regrade', $this->context) && $regradedattempts) {
                $columns[] = 'regraded';
                $headers[] = get_string('regrade', 'quiz_overview');
            }
            if ($showgrades) {
                $columns[] = 'sumgrades';
                $headers[] = get_string('grade', 'quiz').'/'.quiz_format_grade($quiz, $quiz->grade);
             }

            if ($hasfeedback) {
                $columns[] = 'feedbacktext';
                $headers[] = get_string('feedback', 'quiz');
             }

            $table->define_columns($columns);
            $table->define_headers($headers);
            $table->sortable(true, 'uniqueid');

            // Set up the table
            $table->define_baseurl($reporturl->out(true, $displayoptions));

            $table->collapsible(true);

            $table->no_sorting('feedbacktext');

            $table->column_class('picture', 'picture');
            $table->column_class('lastname', 'bold');
            $table->column_class('firstname', 'bold');
            $table->column_class('fullname', 'bold');
            $table->column_class('sumgrades', 'bold');

            $table->set_attribute('id', 'attempts');

            $table->out($pagesize, true);
        }
        if (!$table->is_downloading() && $showgrades) {
            if ($currentgroup && $groupstudents) {
                list($usql, $params) = $DB->get_in_or_equal($groupstudents);
                $params[] = $quiz->id;
                if ($DB->record_exists_select('quiz_grades', "userid $usql AND quiz = ?", $params)) {
                     $imageurl = "{$CFG->wwwroot}/mod/quiz/report/overview/overviewgraph.php?id={$quiz->id}&amp;groupid=$currentgroup";
                     $graphname = get_string('overviewreportgraphgroup', 'quiz_overview', groups_get_group_name($currentgroup));
                     echo $OUTPUT->heading($graphname);
                     echo '<div class="graph"><img src="'.$imageurl.'" alt="'.$graphname.'" /></div>';
                }
            }
            if ($DB->record_exists('quiz_grades', array('quiz'=> $quiz->id))) {
                 $graphname = get_string('overviewreportgraph', 'quiz_overview');
                 $imageurl = $CFG->wwwroot.'/mod/quiz/report/overview/overviewgraph.php?id='.$quiz->id;
                 echo $OUTPUT->heading($graphname);
                 echo '<div class="graph"><img src="'.$imageurl.'" alt="'.$graphname.'" /></div>';
            }
        }
        return true;
    }
    /**
     * @param bool changedb whether to change contents of state and grades
     * tables.
     */
    function regrade_all($dry, $quiz, $groupstudents) {
        global $DB, $OUTPUT;
        if (!has_capability('mod/quiz:regrade', $this->context)) {
            echo $OUTPUT->notification(get_string('regradenotallowed', 'quiz'));
            return true;
        }
        // Fetch all attempts
        if ($groupstudents) {
            list($usql, $params) = $DB->get_in_or_equal($groupstudents);
            $select = "userid $usql AND ";
        } else {
            $select = '';
            $params = array();
        }
        $select .= "quiz = ? AND preview = 0";
        $params[] = $quiz->id;
        if (!$attempts = $DB->get_records_select('quiz_attempts', $select, $params)) {
            echo $OUTPUT->heading(get_string('noattempts', 'quiz'));
            return true;
        }

        $this->clear_regrade_table($quiz, $groupstudents);

        // Fetch all questions
        $questions = question_load_questions(explode(',',quiz_questions_in_quiz($quiz->questions)), 'qqi.grade AS maxgrade, qqi.id AS instance',
            '{quiz_question_instances} qqi ON qqi.quiz = ' . $quiz->id . ' AND q.id = qqi.question');

        // Print heading
        echo $OUTPUT->heading(get_string('regradingquiz', 'quiz', format_string($quiz->name)));
        $qstodo = count($questions);
        $qsdone = 0;
        if ($qstodo > 1) {
            $qpb = new progress_bar('qregradingbar', 500, true);
            $qpb->update($qsdone, $qstodo, "Question $qsdone of $qstodo");
        }
        $apb = new progress_bar('aregradingbar', 500, true);

        // Loop through all questions and all attempts and regrade while printing progress info
        $attemptstodo = count($attempts);
        foreach ($questions as $question) {
            $attemptsdone = 0;
            $apb->restart();
            echo '<p class="mdl-align"><strong>'.get_string('regradingquestion', 'quiz', $question->name).'</strong></p>';
            @flush();@ob_flush();
            foreach ($attempts as $attempt) {
                set_time_limit(30);
                $changed = regrade_question_in_attempt($question, $attempt, $quiz, true, $dry);

                $attemptsdone++;
                $a = new stdClass();
                $a->done = $attemptsdone;
                $a->todo = $attemptstodo;
                $apb->update($attemptsdone, $attemptstodo, get_string('attemptprogress', 'quiz_overview', $a));
            }
            $qsdone++;
            if (isset($qpb)) {
                $a = new stdClass();
                $a->done = $qsdone;
                $a->todo = $qstodo;
                $qpb->update($qsdone, $qstodo, get_string('qprogress', 'quiz_overview', $a));
            }
            // the following makes sure that the output is sent immediately.
            @flush();@ob_flush();
        }

        if (!$dry) {
            $this->check_overall_grades($quiz, $groupstudents);
        }
    }
    function count_regrade_all_needed($quiz, $groupstudents) {
        global $DB;
        // Fetch all attempts that need regrading
        if ($groupstudents) {
            list($usql, $params) = $DB->get_in_or_equal($groupstudents);
            $where = "qa.userid $usql AND ";
        } else {
            $where = '';
            $params = array();
        }
        $where .= "qa.quiz = ? AND qa.preview = 0 AND qa.uniqueid = qqr.attemptid AND qqr.regraded = 0";
        $params[] = $quiz->id;
        return $DB->get_field_sql('SELECT COUNT(1) FROM {quiz_attempts} qa, {quiz_question_regrade} qqr WHERE '. $where, $params);
    }
    function regrade_all_needed($quiz, $groupstudents) {
        global $DB, $OUTPUT;
        if (!has_capability('mod/quiz:regrade', $this->context)) {
            echo $OUTPUT->notification(get_string('regradenotallowed', 'quiz'));
            return;
        }
        // Fetch all attempts that need regrading
        if ($groupstudents) {
            list($usql, $params) = $DB->get_in_or_equal($groupstudents);
            $where = "qa.userid $usql AND ";
        } else {
            $where = '';
            $params = array();
        }
        $where .= "qa.quiz = ? AND qa.preview = 0 AND qa.uniqueid = qqr.attemptid AND qqr.regraded = 0";
        $params[] = $quiz->id;
        if (!$attempts = $DB->get_records_sql('SELECT qa.*, qqr.questionid FROM {quiz_attempts} qa, {quiz_question_regrade} qqr WHERE '. $where, $params)) {
            echo $OUTPUT->heading(get_string('noattemptstoregrade', 'quiz_overview'));
            return true;
        }
        $this->clear_regrade_table($quiz, $groupstudents);
        // Fetch all questions
        $questions = question_load_questions(explode(',',quiz_questions_in_quiz($quiz->questions)), 'qqi.grade AS maxgrade, qqi.id AS instance',
            '{quiz_question_instances} qqi ON qqi.quiz = ' . $quiz->id . ' AND q.id = qqi.question');

        // Print heading
        echo $OUTPUT->heading(get_string('regradingquiz', 'quiz', format_string($quiz->name)));

        $apb = new progress_bar('aregradingbar', 500, true);

        // Loop through all questions and all attempts and regrade while printing progress info
        $attemptstodo = count($attempts);
        $attemptsdone = 0;
        @flush();@ob_flush();
        $attemptschanged = array();
        foreach ($attempts as $attempt) {
            $question = $questions[$attempt->questionid];
            $changed = regrade_question_in_attempt($question, $attempt, $quiz, true);
            if ($changed) {
                $attemptschanged[] = $attempt->uniqueid;
                $usersschanged[] = $attempt->userid;
            }
            if (!empty($apb)) {
                $attemptsdone++;
                $a = new stdClass();
                $a->done = $attemptsdone;
                $a->todo = $attemptstodo;
                $apb->update($attemptsdone, $attemptstodo, get_string('attemptprogress', 'quiz_overview', $a));
            }
        }
        $this->check_overall_grades($quiz, array(), $attemptschanged);
    }

    function clear_regrade_table($quiz, $groupstudents) {
        global $DB;
        // Fetch all attempts that need regrading
        if ($groupstudents) {
            list($usql, $params) = $DB->get_in_or_equal($groupstudents);
            $where = "userid $usql AND ";
        } else {
            $usql = '';
            $where = '';
            $params = array();
        }
        $params[] = $quiz->id;
        $delsql = 'DELETE FROM {quiz_question_regrade} WHERE attemptid IN
                (SELECT uniqueid FROM {quiz_attempts} WHERE ' . $where . ' quiz = ?)';
        if (!$DB->execute($delsql, $params)) {
            print_error('err_failedtodeleteregrades', 'quiz_overview');
        }
    }

    function check_overall_grades($quiz, $userids=array(), $attemptids=array()) {
        global $DB;
        //recalculate $attempt->sumgrade
        //already updated in regrade_question_in_attempt
        $sql = "UPDATE {quiz_attempts} SET sumgrades= " .
                    "COALESCE((SELECT SUM(qs.grade) FROM {question_sessions} qns, {question_states} qs " .
                        "WHERE qns.newgraded = qs.id AND qns.attemptid = {quiz_attempts}.uniqueid ), 0) WHERE ";
        $attemptsql='';
        if (!$attemptids) {
            if ($userids) {
                list($usql, $params) = $DB->get_in_or_equal($userids);
                $attemptsql .= "{quiz_attempts}.userid $usql AND ";
            } else {
                $params = array();
            }
            $attemptsql .= "{quiz_attempts}.quiz =? AND preview = 0";
            $params[] = $quiz->id;
        } else {
            list($asql, $params) = $DB->get_in_or_equal($attemptids);
            $attemptsql .= "{quiz_attempts}.uniqueid $asql";
        }
        $sql .= $attemptsql;
        if (!$DB->execute($sql, $params)) {
            print_error('err_failedtorecalculateattemptgrades', 'quiz_overview');
        }

        // Update the overall quiz grades
        if ($attemptids) {
            //make sure we fetch all attempts for users to calculate grade.
            //not just those that have changed.
            $sql = "SELECT qa2.* FROM {quiz_attempts} qa2 WHERE " .
                    "qa2.userid IN (SELECT DISTINCT userid FROM {quiz_attempts} WHERE $attemptsql) " .
                    "AND qa2.timefinish > 0";
        } else {
            $sql = "SELECT * FROM {quiz_attempts} WHERE $attemptsql AND timefinish > 0";
        }
        if ($attempts = $DB->get_records_sql($sql, $params)) {
            $attemptsbyuser = quiz_report_index_by_keys($attempts, array('userid', 'id'));
            foreach($attemptsbyuser as $userid => $attemptsforuser) {
                quiz_save_best_grade($quiz, $userid, $attemptsforuser);
            }
        }
    }

    function delete_selected_attempts($quiz, $cm, $attemptids, $allowed, $groupstudents) {
        global $DB, $COURSE;
        foreach($attemptids as $attemptid) {
            $attempt = $DB->get_record('quiz_attempts', array('id' => $attemptid));
            if (!$attempt || $attempt->quiz != $quiz->id || $attempt->preview != 0) {
                // Ensure the attempt exists, and belongs to this quiz. If not skip.
                continue;
            }
            if ($allowed && !array_key_exists($attempt->userid, $allowed)) {
                // Ensure the attempt belongs to a student included in the report. If not skip.
                continue;
            }
            if ($groupstudents && !array_key_exists($attempt->userid, $groupstudents)) {
                // Additional check in groups mode.
                continue;
            }
            add_to_log($COURSE->id, 'quiz', 'delete attempt', 'report.php?id=' . $cm->id,
                    $attemptid, $cm->id);
            quiz_delete_attempt($attempt, $quiz);
        }
    }

    function regrade_selected_attempts($quiz, $attemptids, $groupstudents) {
        global $DB;
        require_capability('mod/quiz:regrade', $this->context);
        if ($groupstudents) {
            list($usql, $params) = $DB->get_in_or_equal($groupstudents);
            $where = "qa.userid $usql AND ";
        } else {
            $params = array();
            $where = '';
        }
        list($asql, $aparams) = $DB->get_in_or_equal($attemptids);
        $where = "qa.id $asql AND ";
        $params = array_merge($params, $aparams);

        $where .= "qa.quiz = ? AND qa.preview = 0";
        $params[] = $quiz->id;
        if (!$attempts = $DB->get_records_sql('SELECT qa.* FROM {quiz_attempts} qa WHERE '. $where, $params)) {
            print_error('noattemptstoregrade', 'quiz_overview');
        }

        // Fetch all questions
        $questions = question_load_questions(explode(',',quiz_questions_in_quiz($quiz->questions)), 'qqi.grade AS maxgrade, qqi.id AS instance',
            '{quiz_question_instances} qqi ON qqi.quiz = ' . $quiz->id . ' AND q.id = qqi.question');
        $updateoverallgrades = array();
        foreach($attempts as $attempt) {
            foreach ($questions as $question) {
                $changed = regrade_question_in_attempt($question, $attempt, $quiz, true);
            }
            $updateoverallgrades[] = $attempt->uniqueid;
        }
        $this->check_overall_grades($quiz, array(), $updateoverallgrades);
    }
}
