<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.

/**
 * Basic library.
 *
 * @package    mod_game
 * @copyright  2007 Vasilis Daloukas
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */

defined('MOODLE_INTERNAL') || die();

// Include those library functions that are also used by core Moodle or other modules.
require_once(dirname(__FILE__) . '/lib.php');
require_once($CFG->dirroot . '/mod/quiz/locallib.php');

// CONSTANTS.
/*
* Options determining how the grades from individual attempts are combined to give
* the overall grade for a user
*/

define("GAME_GRADEMETHOD_HIGHEST", "1");
define("GAME_GRADEMETHOD_AVERAGE", "2");
define("GAME_GRADEMETHOD_FIRST", "3");
define("GAME_GRADEMETHOD_LAST", "4");

$gamegrademethod = [GAME_GRADEMETHOD_HIGHEST => get_string("gradehighest", "game"),
                             GAME_GRADEMETHOD_AVERAGE => get_string("gradeaverage", "game"),
                             GAME_GRADEMETHOD_FIRST => get_string("attemptfirst", "game"),
                             GAME_GRADEMETHOD_LAST => get_string("attemptlast", "game")];

define("CONST_GAME_TRIES_REPETITION", "5");

/**
 * Returns the version of Moodle.
 *
 * @package mod_game
 *
 * @return string version e.g. 03.10
 */
function game_get_moodle_version() {
    global $DB;

    static $smoodleversion = null;

    if ($smoodleversion != null) {
        return $smoodleversion;
    }

    $rec = $DB->get_record_select('config', "name='release'");
    if ($rec == false) {
        return $smoodleversion = '';
    } else {
        $a = explode('.', $rec->value);
        return $smoodleversion = sprintf('%02u.%02u', $a[0], $a[1]);
    }
}

/**
 * Convert a string to upper.
 *
 * @package mod_game
 *
 * @param string $str
 * @param string $lang
 * @return string the uppercase of $str
 */
function game_upper($str, $lang = '') {
    if ($lang == 'user') {
        return $str;
    }

    $str = game_strtoupper($str);

    switch (substr($lang, 0, 2)) {
        case 'el':
            $from = 'ΆΈΉΊΌΎΏ';
            $to = 'ΑΕΗΙΟΥΩ';
            break;
        default:
            return $str;
    }

    $len = game_strlen($from);
    for ($i = 0; $i < $len; $i++) {
        $str = str_replace(game_substr($from, $i, 1), game_substr($to, $i, 1), $str);
    }

    return $str;
}

/**
 * Returns the HTML of a select control.
 *
 * @package mod_game
 *
 * @param string $name
 * @param array $a
 * @param string $input
 * @param array $events
 *
 * @return string the HTML
 */
function game_showselectcontrol($name, $a, $input, $events = '') {
    $ret = "<select id=\"$name\" name=\"$name\" $events>";

    foreach ($a as $key => $caption) {
        $ret .= '<option value="' . $key . '" ';
        if ($key == $input) {
            $ret .= ' selected="selected" ';
        }
        $ret .= '>' . $caption . "</option>\r\n";
    }
    $ret .= "</select>\r\n";

    return $ret;
}

/**
 * Returns the HTML of a checkbox control.
 *
 * @package mod_game
 *
 * @param string $name
 * @param string $value
 * @return string the HTML
 */
function game_showcheckbox($name, $value) {
    $a = [];
    $a[0] = get_string('no');
    $a[1] = get_string('yes');

    return game_showselectcontrol($name, $a, $value);
}

/**
 * Used by hangman. Returns a short answer.
 *
 * @package mod_game
 *
 * @param stdClass $game
 * @param boolean $allowspaces
 * @param boolean $userepetitions
 *
 * @return string the HTML
 */
function game_question_shortanswer($game, $allowspaces = false, $userepetitions = true) {
    switch ($game->sourcemodule) {
        case 'glossary':
            return game_question_shortanswer_glossary($game, $allowspaces, $userepetitions);
        case 'quiz':
            return game_question_shortanswer_quiz($game, $allowspaces, $userepetitions);
        case 'question':
            return game_question_shortanswer_question($game, $allowspaces, $userepetitions);
    }

    return false;
}

/**
 * Used by hangman. Returns a short answer from glossary.
 *
 * @package mod_game
 *
 * @param stdClass $game
 * @param boolean $allowspaces
 * @param boolean $userepetitions
 *
 */
function game_question_shortanswer_glossary($game, $allowspaces, $userepetitions) {
    global $DB;

    if ($game->glossaryid == 0) {
        throw new moodle_exception('must_select_glossary', 'game');
    }

    $select = "(glossaryid={$game->glossaryid} OR sourceglossaryid={$game->glossaryid})";
    $table = '{glossary_entries} ge';
    if ($game->glossarycategoryid) {
        $table .= ',{glossary_entries_categories} gec';
        $select .= " AND gec.entryid = ge.id AND gec.categoryid = {$game->glossarycategoryid}";
    }
    if ($allowspaces == false) {
        $select .= " AND concept NOT LIKE '% %'  ";
    }

    if ($game->glossaryonlyapproved) {
        // Only approved glossary entries will be used.
        $select .= ' AND (ge.approved=1 OR teacherentry=1)';
    }

    if (($id = game_question_selectrandom($game, $table, $select, 'ge.id', $userepetitions)) == false) {
        return false;
    }

    $sql = 'SELECT id, concept as answertext, definition as questiontext, id as glossaryentryid, ' .
            ' 0 as questionid, glossaryid, attachment, 0 as answerid' .
           " FROM {glossary_entries} ge WHERE id = $id";
    if (($rec = $DB->get_record_sql($sql)) == false) {
        return false;
    }

    if ($rec->attachment != '') {
        $rec->attachment = "glossary/{$game->glossaryid}/$rec->id/$rec->attachment";
    }

    return $rec;
}

/**
 * Used by hangman. Returns a short answer from quiz.
 *
 * @package mod_game
 *
 * @param stdClass $game
 * @param boolean $allowspaces
 * @param boolean $userepetitions
 */
function game_question_shortanswer_quiz($game, $allowspaces, $userepetitions) {
    global $CFG, $DB;

    if ($game->quizid == 0) {
        throw new moodle_exception('must_select_quiz', 'game');
    }

    if (game_get_moodle_version() < '02.07') {
        $select = "qtype='shortanswer' AND quiz='$game->quizid' " .
            " AND qqi.question=q.id";
        $table = "{question} q,{quiz_question_instances} qqi";
    } else if (game_get_moodle_version() >= '04.00') {
        $select = "qs.quizid='$game->quizid' AND qs.id=qr.itemid ";
        $table = "{quiz_slots} qs,{$CFG->prefix}question_references qr";
        $sql = "SELECT qr.questionbankentryid FROM $table WHERE $select";
        $recs = $DB->get_records_sql($sql);
        $ret = [];
        $sql = "SELECT q.* FROM {$CFG->prefix}question_versions qv, {$CFG->prefix}question q " .
            " WHERE qv.questionid=q.id AND qv.questionbankentryid=? ORDER BY version DESC";
        foreach ($recs as $rec) {
            $recsq = $DB->get_records_sql($sql, [$rec->questionbankentryid], 0, 1);
            foreach ($recsq as $recq) {
                $a[] = $recq->id;
            }
        }
        $table = '{question} q';
        if (count($a) == 0) {
            $select = 'q.id IN (0)';
        } else {
            $select = 'q.id IN (' . implode(',', $a) . ')';
        }
    } else {
        $select = "qtype='shortanswer' AND qs.quizid='$game->quizid' " .
            " AND qs.questionid=q.id";
        $table = "{question} q,{quiz_slots} qs";
    }
    $fields = "q.id";

    if (($id = game_question_selectrandom($game, $table, $select, $fields, $userepetitions)) == false) {
        return false;
    }

    $select = "q.id=$id AND qa.question=$id AND qtype='shortanswer'";
    if (game_get_moodle_version() < '04.00') {
        $select .= ' AND q.hidden=0 ';
    }
    $table = "{question} q,{question_answers} qa";
    $fields = "qa.id as answerid, q.id, q.questiontext as questiontext, " .
        "qa.answer as answertext, q.id as questionid, " .
        "0 as glossaryentryid, '' as attachment";

    // Maybe there are more answers to one question. I use as correct the one with bigger fraction.
    $sql = "SELECT $fields FROM $table WHERE $select ORDER BY fraction DESC";
    if (($recs = $DB->get_records_sql($sql, null, 0, 1)) == false) {
        return false;
    }
    foreach ($recs as $rec) {
        return $rec;
    }
}

/**
 * Used by hangman. Returns a short answer from question.
 *
 * @package mod_game
 *
 * @param stdClass $game
 * @param boolean $allowspaces
 * @param boolean $userepetitions
 *
 * @return the short answer record
 */
function game_question_shortanswer_question($game, $allowspaces, $userepetitions) {
    global $CFG, $DB;

    if ($game->questioncategoryid == 0) {
        throw new moodle_exception('must_select_questioncategory', 'game');
    }

    $table = '{question} q';
    if (game_get_moodle_version() >= '04.00') {
        $table .= ",{$CFG->prefix}question_bank_entries qbe,{$CFG->prefix}question_versions qv ";
        $select = 'qbe.id=qv.questionbankentryid AND q.id=qv.questionid AND qbe.questioncategoryid=' . $game->questioncategoryid;
        if ($game->subcategories) {
            $cats = question_categorylist($game->questioncategoryid);
            if (count($cats) > 0) {
                $s = implode(',', $cats);
                $select = 'qbe.questioncategoryid in (' . $s . ')';
            }
        }
    } else {
        $select = 'category=' . $game->questioncategoryid;
        if ($game->subcategories) {
            $cats = question_categorylist($game->questioncategoryid);
            if (count($cats) > 0) {
                $select = 'category in (' . implode(',', $cats) . ')';
            }
        }
    }
    $select .= " AND q.qtype='shortanswer'";

    $fields = 'q.id';

    if (($id = game_question_selectrandom($game, $table, $select, $fields, $userepetitions)) == false) {
        return false;
    }

    $select = "q.id=$id AND qa.question=$id AND qtype='shortanswer'";
    if (game_get_moodle_version() < '04.00') {
        $select .= ' AND q.hidden=0';
    }

    $table = "{question} q,{question_answers} qa";
    $fields = "qa.id as answerid, q.id, q.questiontext as questiontext, " .
        "qa.answer as answertext, q.id as questionid, " .
        "0 as glossaryentryid, '' as attachment";

    // Maybe there are more answers to one question. I use as correct the one with bigger fraction.
    $sql = "SELECT $fields FROM $table WHERE $select ORDER BY fraction DESC";
    if (($recs = $DB->get_records_sql($sql, null, 0, 1)) == false) {
        return false;
    }
    foreach ($recs as $rec) {
        return $rec;
    }
}

/**
 * Select a random question. Used by millionaire, game_question_shortanswer_quiz, hidden picture.
 *
 * @package mod_game
 *
 * @param stdClass $game
 * @param string $table
 * @param string $select
 * @param string $idfields
 * @param boolean $userepetitions
 *
 * @return stdClass the random question
 */
function game_question_selectrandom($game, $table, $select, $idfields = 'id', $userepetitions = true) {
    global $DB, $USER;

    $count = $DB->get_field_sql("SELECT COUNT(*) FROM $table WHERE $select");

    if ($count == 0) {
        return false;
    }

    $minnum = 0;
    $minid = 0;
    for ($i = 1; $i <= CONST_GAME_TRIES_REPETITION; $i++) {
        $sel = mt_rand(0, $count - 1);

        $sql = "SELECT $idfields, $idfields FROM " . $table . " WHERE $select";
        if (($recs = $DB->get_records_sql($sql, null, $sel, 1)) == false) {
            return false;
        }

        $id = 0;
        foreach ($recs as $rec) {
            $id = $rec->id;
        }
        if ($minid == 0) {
            $minid = $id;
        }

        if ($userepetitions == false) {
            return $id;
        }

        if ($count == 1) {
            break;
        }

        $questionid = $glossaryentryid = 0;
        if ($game->sourcemodule == 'glossary') {
            $glossaryentryid = $id;
        } else {
            $questionid = $id;
        }

        $a = ['gameid' => $game->id, 'userid' => $USER->id, 'questionid' => $questionid,
            'glossaryentryid' => $glossaryentryid];
        if (($rec = $DB->get_record('game_repetitions', $a, 'id,repetitions AS r')) != false) {
            if (($rec->r < $minnum) || ($minnum == 0)) {
                $minnum = $rec->r;
                $minid = $id;
            }
        } else {
            $minid = $questionid;
            break;
        }
    }

    if ($game->sourcemodule == 'glossary') {
        game_update_repetitions($game->id, $USER->id, 0, $minid);
    } else {
        game_update_repetitions($game->id, $USER->id, $minid, 0);
    }

    return $minid;
}


/**
 * Updates the repetition table.
 *
 * @package mod_game
 *
 * @param int $gameid
 * @param int $userid
 * @param int $questionid
 * @param int $glossaryentryid
 */
function game_update_repetitions($gameid, $userid, $questionid, $glossaryentryid) {
    global $DB;

    $a = ['gameid' => $gameid, 'userid' => $userid, 'questionid' => $questionid, 'glossaryentryid' => $glossaryentryid];
    if (($rec = $DB->get_record('game_repetitions', $a, 'id,repetitions AS r')) != false) {
        $updrec = new stdClass();
        $updrec->id = $rec->id;
        $updrec->repetitions = $rec->r + 1;
        if (!$DB->update_record('game_repetitions', $updrec)) {
            throw new moodle_exception('game_error', 'game', "Update page: can't update game_repetitions id={$updrec->id}");
        }
    } else {
        $newrec = new stdClass();
        $newrec->gameid = $gameid;
        $newrec->userid = $userid;
        $newrec->questionid = $questionid;
        $newrec->glossaryentryid = $glossaryentryid;
        $newrec->repetitions = 1;

        if ($newrec->questionid == '') {
            $newrec->questionid = 0;
        }
        if ($newrec->glossaryentryid == '') {
            $newrec->glossaryentryid = 0;
        }
        if (!$DB->insert_record('game_repetitions', $newrec)) {
            throw new moodle_exception('game_error', 'game', "Insert page: new page game_repetitions not inserted");
        }
    }
}

/**
 * Select random questions for Sudoku.
 *
 * @param stdClass $game
 * @param int $count
 *
 * @return array: the random record(s)
 * @throws coding_exception
 * @throws dml_exception
 * @throws moodle_exception
 * @package mod_game
 *
 */
function game_questions_selectrandom($game, $count = 1) {
    global $CFG, $DB;

    $useversion = false;

    switch ($game->sourcemodule) {
        case 'quiz':
            if ($game->quizid == 0) {
                throw new moodle_exception('must_select_quiz', 'game');
            }
            if (game_get_moodle_version() < '02.07') {
                $table = '{question} q, {quiz_question_instances} qqi';
                $select = " qqi.quiz=$game->quizid" .
                    " AND qqi.question=q.id " .
                    " AND q.qtype in ('shortanswer', 'truefalse', 'multichoice')";
            } else if (game_get_moodle_version() >= '04.00') {
                $select = "qs.quizid='$game->quizid' AND qs.id=qr.itemid ";
                $table = "{quiz_slots} qs,{$CFG->prefix}question_references qr";
                $sql = "SELECT qr.questionbankentryid FROM $table WHERE $select";
                $recs = $DB->get_records_sql($sql);
                $ret = [];
                $sql = "SELECT q.* FROM {$CFG->prefix}question_versions qv, {$CFG->prefix}question q " .
                    ' WHERE qv.questionid=q.id AND qv.questionbankentryid=? ORDER BY version DESC';
                foreach ($recs as $rec) {
                    $recsq = $DB->get_records_sql($sql, [$rec->questionbankentryid], 0, 1);
                    foreach ($recsq as $recq) {
                        $a[] = $recq->id;
                    }
                }
                $table = '{question} q';
                if (count($a) == 0) {
                    $select = 'q.id IN (0)';
                } else {
                    $select = 'q.id IN (' . implode(',', $a) . ')';
                }
            } else {
                $table = '{question} q, {quiz_slots} qs';
                $select = " qs.quizid=$game->quizid" .
                    " AND qs.questionid=q.id " .
                    " AND q.qtype in ('shortanswer', 'truefalse', 'multichoice')";
            }
            if (game_get_moodle_version() < '04.00') {
                $select .= " AND q.hidden=0";
            }
            // Todo 'match'.
            $field = "q.id as id";
            $table2 = 'question';
            $fields2 = 'id as questionid,0 as glossaryentryid,qtype';
            break;
        case 'glossary':
            if ($game->glossaryid == 0) {
                throw new moodle_exception('must_select_glossary', 'game');
            }
            $table = '{glossary_entries} ge';
            $select = "(glossaryid='{$game->glossaryid}' OR sourceglossaryid='{$game->glossaryid}')";
            if ($game->glossarycategoryid) {
                $table .= ',{glossary_entries_categories} gec';
                $select .= " AND gec.entryid = ge.id " .
                    " AND gec.categoryid = {$game->glossarycategoryid}";
            }
            $field = 'ge.id';
            $table2 = 'glossary_entries';
            $fields2 = 'id as glossaryentryid, 0 as questionid';
            break;
        case 'question':
            if ($game->questioncategoryid == 0) {
                throw new moodle_exception('must_select_questioncategory', 'game');
            }
            $table = '{question} q';

            // Include subcategories.
            if (game_get_moodle_version() >= '04.00') {
                $useversion = true;
                $table .= ",{$CFG->prefix}question_bank_entries qbe,{$CFG->prefix}question_versions qv ";
                $select = 'qbe.id=qv.questionbankentryid AND q.id=qv.questionid ' .
                    ' AND qbe.questioncategoryid=' . $game->questioncategoryid;
                if ($game->subcategories) {
                    $cats = question_categorylist($game->questioncategoryid);
                    if (count($cats) > 0) {
                        $select = 'qbe.questioncategoryid in (' . implode(',', $cats) . ')';
                    }
                }
            } else {
                $select = 'category=' . $game->questioncategoryid;
                if ($game->subcategories) {
                    $cats = question_categorylist($game->questioncategoryid);
                    if (count($cats)) {
                        $select = 'category in (' . implode(',', $cats) . ')';
                    }
                }
            }

            $select .= " AND q.qtype in ('shortanswer', 'truefalse', 'multichoice')";
            if (game_get_moodle_version() < '04.00') {
                $select .= " AND q.hidden=0";
            }
            // Todo 'match'.
            $field = "q.id";

            $table2 = 'question';
            $fields2 = 'id as questionid,0 as glossaryentryid';
            break;
        default:
            throw new moodle_exception('game_error', 'game', 'No sourcemodule defined');
            break;
    }

    $ids = game_questions_selectrandom_detail($table, $select, $field, $count, $useversion);
    if ($ids === false) {
        throw new moodle_exception('no_questions', 'game');
    }

    if (count($ids) > 1) {
        // Randomize the array.
        shuffle($ids);
    }

    $ret = [];
    foreach ($ids as $id) {
        if ($recquestion = $DB->get_record($table2, ['id' => $id], $fields2)) {
            $new = new stdClass();
            $new->questionid = (int)$recquestion->questionid;
            $new->glossaryentryid = (int)$recquestion->glossaryentryid;
            $ret[] = $new;
        }
    }

    return $ret;
}

/**
 * Select random questions. Used by game_questions_selectrandom.
 *
 * @package mod_game
 *
 * @param string $table
 * @param string $select
 * @param int $idfield
 * @param int $count
 * @param boolean $useversion
 *
 * @return stdClass the random record(s)
 */
function game_questions_selectrandom_detail($table, $select, $idfield = "id", $count = 1, $useversion = true) {
    global $DB;

    $versions = $useversion ? game_get_moodle_version() >= '04.00' : false;
    $fields = $versions ? ',qv.questionbankentryid,qv.version' : '';
    $order = $versions ? ' ORDER BY qv.questionbankentryid,qv.version DESC' : '';

    $sql = "SELECT $idfield{$fields} FROM $table WHERE $select $order";
    if (($recs = $DB->get_records_sql($sql)) == false) {
        return false;
    }

    // The array contains the ids of all questions.
    $map = $a = [];
    foreach ($recs as $rec) {
        if ($versions) {
            if (array_key_exists($rec->questionbankentryid, $map)) {
                continue;
            } else {
                $map[$rec->questionbankentryid] = 1;
            }
        }
        $a[$rec->id] = $rec->id;
    }

    if ($count >= count($a)) {
        return $a;
    } else {
        $id = array_rand($a, $count);
        return ($count == 1 ? [$id] : $id);
    }
}

/**
 * Tries to detect the language of word.
 *
 * @package mod_game
 *
 * @param string $word
 *
 * @return the language detected
 */
function game_detectlanguage($word) {
    $langs = get_string_manager()->get_list_of_translations();

    // English has more priority.
    if (array_key_exists('en', $langs)) {
        unset($langs['en']);
        $langs[''] = '';
    }
    ksort($langs);
    $langsinstalled = get_string_manager()->get_list_of_translations();

    foreach ($langs as $lang => $name) {
        if ($lang == '') {
            $lang = 'en';
        }

        if (!array_key_exists($lang, $langsinstalled)) {
            continue;
        }

        $strings = get_string_manager()->load_component_strings('game', $lang);
        if (isset($strings['lettersall'])) {
            $letters = $strings['lettersall'];
            $word2 = game_upper($word, $lang);

            if (hangman_existall($word2, $letters)) {
                return $lang;
            }
        }
    }

    return false;
}

/**
 * The words maybe are in two languages e.g. greek or english so I try to find the correct one.
 *
 * @package mod_game
 *
 * @param string $word
 * @param string $lang
 * @param string $userlanguage
 *
 * @return the letters detected
 */
function game_getallletters($word, $lang = '', $userlanguage = '') {
    for (;;) {
        if ($lang == 'user') {
            $letters = $userlanguage;
            if (hangman_existall($word, $letters)) {
                return $letters;
            } else {
                return '';
            }
        } else {
            $strings = get_string_manager()->load_component_strings('game', ($lang == '' ? 'en' : $lang));
            if (isset($strings['lettersall'])) {
                $letters = $strings['lettersall'];
                $word2 = game_upper($word, $lang);
                if (hangman_existall($word2, $letters)) {
                    return $letters;
                }
            }

            if ($lang == '') {
                break;
            } else {
                $lang = '';
            }
        }
    }

    return '';
}

/**
 * true if exist all the letters
 *
 * @package mod_game
 *
 * @param string $str
 * @param string $strfind
 *
 * @return the letters detected
 */
function hangman_existall($str, $strfind) {
    $n = game_strlen($str);
    for ($i = 0; $i < $n; $i++) {
        $pos = game_strpos($strfind, game_substr($str, $i, 1));
        if ($pos === false) {
            return false;
        }
    }

    return true;
}

/**
 * return a short answer randomly selected. used by cross
 *
 * @package mod_game
 *
 * @param stdClass $game
 *
 * @return a question
 */
function game_questions_shortanswer($game) {
    switch ($game->sourcemodule) {
        case 'glossary':
            $recs = game_questions_shortanswer_glossary($game);
            break;
        case 'quiz':
            $recs = game_questions_shortanswer_quiz($game);
            break;
        case 'question':
            $recs = game_questions_shortanswer_question($game);
            break;
        default:
            throw new moodle_exception('game_error', 'game', 'No sourcemodule ' . $game->sourcemodule);
    }

    return $recs;
}

/**
 * return a short answer (from glossary) randomly selected. used by cross
 *
 * @package mod_game
 *
 * @param stdClass $game
 *
 * @return a question
 */
function game_questions_shortanswer_glossary($game) {
    global $DB;

    $select = "(glossaryid={$game->glossaryid} OR sourceglossaryid={$game->glossaryid})";
    $table = '{glossary_entries} ge';
    if ($game->glossarycategoryid) {
        $table .= ',{glossary_entries_categories} gec';
        $select .= ' AND gec.entryid = ge.id ' .
            ' AND gec.categoryid = ' . $game->glossarycategoryid;
    }

    if ($game->glossaryonlyapproved) {
        // Only approved glossary entries will be used.
        $select .= ' AND (ge.approved=1 OR ge.teacherentry=1)';
    }

    $sql = 'SELECT ge.id, concept as answertext, definition as questiontext, ge.id as glossaryentryid, ' .
        ' 0 as questionid, attachment ' .
        " FROM $table WHERE $select";

    return $DB->get_records_sql($sql);
}

/**
 * return a short answer (from quiz) randomly selected. used by cross
 *
 * @package mod_game
 *
 * @param stdClass $game
 *
 * @return a question
 */
function game_questions_shortanswer_quiz($game) {
    global $CFG, $DB;

    if ($game->quizid == 0) {
        throw new moodle_exception('must_select_quiz', 'game');
    }

    if (game_get_moodle_version() < '02.07') {
        $select = "qtype='shortanswer' AND quiz='$game->quizid' " .
            " AND qqi.question=q.id" .
            " AND qa.question=q.id" .
            " AND q.hidden=0";
        $table = "{question} q,{quiz_question_instances} qqi,{question_answers} qa";
        $fields = "qa.id as qaid, q.id, q.questiontext as questiontext, " .
            "qa.answer as answertext, q.id as questionid," .
            " 0 as glossaryentryid,'' as attachment";
    } else if (game_get_moodle_version() >= '04.00') {
        $select = "qs.quizid='$game->quizid' AND qs.id=qr.itemid ";
        $table = "{quiz_slots} qs,{$CFG->prefix}question_references qr";
        $sql = "SELECT qr.questionbankentryid FROM $table WHERE $select";
        $recs = $DB->get_records_sql($sql);
        $sql = "SELECT q.* FROM {$CFG->prefix}question_versions qv, {$CFG->prefix}question q " .
            " WHERE qv.questionid=q.id AND qv.questionbankentryid=? ORDER BY version DESC";
        foreach ($recs as $rec) {
            $recsq = $DB->get_records_sql($sql, [$rec->questionbankentryid], 0, 1);
            foreach ($recsq as $recq) {
                $a[] = $recq->id;
            }
        }

        if ($a == null || count($a) == 0) {
            $a = [0];
        }
        $select = "qtype='shortanswer' AND q.id IN (" . implode(',', $a) . ')' .
            " AND qa.question=q.id";
        $table = "{question} q,{question_answers} qa";
        $fields = "qa.id as qaid, q.id, q.questiontext as questiontext, " .
            "qa.answer as answertext, q.id as questionid," .
            " 0 as glossaryentryid,'' as attachment";
    } else {
        $select = "qtype='shortanswer' AND qs.quizid='$game->quizid' " .
            " AND qs.questionid=q.id" .
            " AND qa.question=q.id" .
            " AND q.hidden=0";
        $table = "{question} q,{quiz_slots} qs,{question_answers} qa";
        $fields = "qa.id as qaid, q.id, q.questiontext as questiontext, " .
            "qa.answer as answertext, q.id as questionid," .
            " 0 as glossaryentryid,'' as attachment";
    }

    return game_questions_shortanswer_question_fraction($table, $fields, $select, false);
}

/**
 * return a short answer (from question) randomly selected. used by cross
 *
 * @package mod_game
 *
 * @param stdClass $game
 *
 * @return a question
 */
function game_questions_shortanswer_question($game) {
    global $CFG;

    if ($game->questioncategoryid == 0) {
        throw new moodle_exception('must_select_questioncategory', 'game');
    }

    // Include subcategories.
    $table = "{question} q,{question_answers} qa";
    if (game_get_moodle_version() >= '04.00') {
        $table .= ",{$CFG->prefix}question_bank_entries qbe,{$CFG->prefix}question_versions qv ";
        $select = 'qbe.id=qv.questionbankentryid AND q.id=qv.questionid AND qbe.questioncategoryid=' . $game->questioncategoryid;
        if ($game->subcategories) {
            $cats = question_categorylist($game->questioncategoryid);
            if (count($cats) > 0) {
                $select = 'qbe.questioncategoryid in (' . implode(',', $cats) . ')';
            }
        }
    } else {
        $select = "q.category={$game->questioncategoryid} AND q.hidden=0";
        if ($game->subcategories) {
            $cats = question_categorylist($game->questioncategoryid);
            if (count($cats)) {
                $select = 'q.category in (' . implode(',', $cats) . ')';
            }
        }
    }

    $select .= " AND qtype='shortanswer' AND qa.question=q.id";
    $fields = "qa.id as qaid, q.id, q.questiontext as questiontext, " .
        "qa.answer as answertext, q.id as questionid";

    if (game_get_moodle_version() >= '04.00') {
        $fields .= ',qv.questionbankentryid, qv.version';
    }

    return game_questions_shortanswer_question_fraction($table, $fields, $select);
}

/**
 * question fraction
 *
 * @package mod_game
 *
 * @param string $table
 * @param string $fields
 * @param string $select
 * @param boolean $useversion
 *
 * @return the record
 */
function game_questions_shortanswer_question_fraction($table, $fields, $select, $useversion = true) {
    global $DB;

    $versions = $useversion ? game_get_moodle_version() >= '04.00' : false;

    $order = ($versions ? 'version DESC,' : '') . 'fraction';
    $sql = "SELECT $fields FROM $table WHERE $select ORDER BY $order DESC";

    $recs = $DB->get_records_sql($sql);
    if ($recs == false) {
        throw new moodle_exception('no_questions', 'game');
    }

    $recs2 = $map = $map2 = [];
    foreach ($recs as $rec) {
        if (array_key_exists($rec->questionid, $map)) {
            continue;
        }

        if ($versions) {
            if (isset($rec->questionbankentryid)) {
                if (array_key_exists($rec->questionbankentryid, $map2)) {
                    continue;
                } else {
                    $map2[$rec->questionbankentryid] = 1;
                }
            }
        }

        $rec2 = new stdClass();
        $rec2->id = $rec->id;
        $rec2->questiontext = $rec->questiontext;
        $rec2->answertext = $rec->answertext;
        $rec2->questionid = $rec->questionid;
        $rec2->glossaryentryid = 0;
        $rec2->attachment = '';
        $recs2[] = $rec2;

        $map[$rec->questionid] = $rec->questionid;
    }

    return $recs2;
}

/**
 * sets char
 *
 * @package mod_game
 *
 * @param string $s
 * @param int $pos
 * @param string $char
 */
function game_setchar(&$s, $pos, $char) {
    $ret = "";

    if ($pos > 0) {
        $ret .= game_substr($s, 0, $pos);
    }

    $s = $ret . $char . game_substr($s, $pos + 1);
}

/**
 * Insert a record
 *
 * @package mod_game
 *
 * @param stdClass $table
 * @param stdClass $rec
 */
function game_insert_record($table, $rec) {
    global $DB;

    if ($DB->get_record($table, ['id' => $rec->id], 'id,id') == false) {
        $sql = 'INSERT INTO {' . $table . '}(id) VALUES(' . $rec->id . ')';
        if (!$DB->execute($sql)) {
            throw new moodle_exception('game_error', 'game', "Cannot insert an empty $table with id=$rec->id");
            return false;
        }
    }

    return $DB->update_record($table, $rec);
}

/**
 * If score is negative doesn't update the record. The field score is between 0 and 1.
 * Updates score on table game_attempts
 *
 * @package mod_game
 *
 * @param stdClass $game
 * @param stdClass $attempt
 * @param float $score
 * @param boolean $finished
 * @param stdClass $cm
 * @param stdClass $course
 *
 * @return the record
 */
function game_updateattempts($game, $attempt, $score, $finished, $cm, $course) {
    global $DB, $USER;

    if ($attempt != false) {
        $updrec = new stdClass();
        $updrec->id = $attempt->id;
        $updrec->timelastattempt = time();

        if ($score >= 0) {
            $updrec->score = $score;
        }

        if ($finished) {
            $updrec->timefinish = $updrec->timelastattempt;
        }

        $updrec->attempts = $attempt->attempts + 1;

        if (!$DB->update_record('game_attempts', $updrec)) {
            throw new moodle_exception('game_error', 'game', "game_updateattempts: Can't update game_attempts id=$updrec->id");
        }

        // Update grade item and send all grades to gradebook.
        $grades = new stdClass();
        $grades->userid = $USER->id;
        $grades->rawgrade = game_score_to_grade($score, $game);
        $grades->datesubmitted = time();

        // Updates table grade_grades.
        game_grade_item_update($game, $grades);
        game_update_grades($game, $grades->userid);
    }

    // Update table game_grades.
    game_save_best_score($game);

    // Update completion state.
    $completion = new completion_info($course);

    if ($completion->is_enabled($cm) && ($game->completionattemptsexhausted || $game->completionpass)) {
        if (!$finished) {
            game_save_best_score($game);
        }
        $completion->update_state($cm, COMPLETION_COMPLETE);
    } else if ($completion->is_enabled($cm) && (! is_null($cm->completiongradeitemnumber)) && ($game->completionpass == 0)) {
        $completion->update_state($cm, COMPLETION_COMPLETE);
    }
}

/**
 * Computes max grade
 *
 * @package mod_game
 *
 * @param stdClass $game
 * @param stdClass $attempt
 * @param float $grade
 * @param boolean $finished
 * @param stdClass $cm
 * @param stdClass $course
 *
 * @return the record
 */
function game_updateattempts_maxgrade($game, $attempt, $grade, $finished, $cm, $course) {
    global $DB;

    $recgrade = $DB->get_field('game_attempts', 'score', ['id' => $attempt->id]);

    if ($recgrade > $grade) {
        $grade = -1;    // Don't touch the grade.
    }

    game_updateattempts($game, $attempt, $grade, $finished, $cm, $course);
}

/**
 * Update queries
 *
 * @package mod_game
 *
 * @param stdClass $game
 * @param stdClass $attempt
 * @param string $query
 * @param float $score
 * @param string $studentanswer
 * @param boolean $updatetries
 *
 * @return the record
 */
function game_update_queries($game, $attempt, $query, $score, $studentanswer, $updatetries = false) {
    global $DB;

    if ($query->id != 0) {
        $select = "id=$query->id";
    } else {
        $select = "attemptid = $attempt->id AND sourcemodule = '{$query->sourcemodule}'";
        switch ($query->sourcemodule) {
            case 'quiz':
                $select .= " AND questionid='$query->questionid' ";
                break;
            case 'glossary':
                $select .= " AND glossaryentryid='$query->glossaryentryid'";
                break;
        }
    }

    if (($recq = $DB->get_record_select('game_queries', $select)) === false) {
        $recq = new stdClass();
        $recq->gamekind = $game->gamekind;
        $recq->gameid = $attempt->gameid;
        $recq->userid = $attempt->userid;
        $recq->attemptid = $attempt->id;
        $recq->sourcemodule = $query->sourcemodule;
        $recq->questionid = $query->questionid;
        $recq->glossaryentryid = $query->glossaryentryid;
        if ($updatetries) {
            $recq->tries = 1;
        }

        if (!($recq->id = $DB->insert_record('game_queries', $recq))) {
            throw new moodle_exception('game_error', 'game', 'Insert page: new page game_queries not inserted');
        }
    }

    $updrec = new stdClass();
    $updrec->id = $recq->id;
    $updrec->timelastattempt = time();

    if ($score >= 0) {
        $updrec->score = $score;
    }

    if ($studentanswer != '') {
        $updrec->studentanswer = $studentanswer;
    }

    if ($updatetries) {
        $updrec->tries = $recq->tries + 1;
    }

    if (!($DB->update_record('game_queries', $updrec))) {
        throw new moodle_exception('game_error', 'game', "game_update_queries: not updated id=$updrec->id");
    }
}

/**
 * get attempt
 *
 * @package mod_game
 *
 * @param stdClass $game
 * @param stdclass $detail
 * @param boolean $autoadd
 */
function game_getattempt($game, &$detail, $autoadd = false) {
    global $DB, $USER;

    $select = "gameid=$game->id AND userid=$USER->id and timefinish=0 ";
    if ($USER->id == 1) {
        $key = 'mod/game:instanceid' . $game->id;
        if (array_key_exists($key, $_SESSION)) {
            $select .= ' AND id="' . $_SESSION[$key] . '"';
        } else {
            $select .= ' AND id=-1';
        }
    }

    if (($recs = $DB->get_records_select('game_attempts', $select))) {
        foreach ($recs as $attempt) {
            if ($USER->id == 1) {
                $_SESSION[$key] = $attempt->id;
            }

            $detail = $DB->get_record('game_' . $game->gamekind, ['id' => $attempt->id]);

            return $attempt;
        }
    }

    if ($autoadd) {
        game_addattempt($game);
        return game_getattempt($game, $detail, false);
    }

    return false;
}

/**
 * get user attempts
 *
 * @package mod_game
 *
 * @param integer $gameid the game id.
 * @param integer $userid the userid.
 * @param string $status 'all', 'finished' or 'unfinished' to control
 *
 * @return array: of all the user's attempts at this game. Returns an empty array if there are none.
 */
function game_get_user_attempts($gameid, $userid, $status = 'finished') {
    global $DB;

    $statuscondition = ['all' => '', 'finished' => ' AND timefinish > 0', 'unfinished' => ' AND timefinish = 0'];
    $attempts = $DB->get_records_select(
        'game_attempts',
        "gameid = ? AND userid = ? AND preview = 0" . $statuscondition[$status],
        [$gameid, $userid],
        'attempt ASC'
    );
    if ($attempts !== false) {
        return $attempts;
    } else {
        return [];
    }
}

/**
 * Returns an unfinished attempt (if there is one) for the given user on the given game.
 * This function does not return preview attempts.
 *
 * @package mod_game
 *
 * @param integer $gameid the id of the game.
 * @param integer $userid the id of the user.
 *
 * @return mixed the unfinished attempt if there is one, false if not.
 */
function game_get_user_attempt_unfinished($gameid, $userid) {
    $attempts = game_get_user_attempts($gameid, $userid, 'unfinished');
    if ($attempts) {
        return array_shift($attempts);
    } else {
        return false;
    }
}

/**
 * Get the best current score for a particular user in a game.
 *
 * @package mod_game
 *
 * @param object $game the game object.
 * @param integer $userid the id of the user.
 *
 * @return float the user's current grade for this game.
 */
function game_get_best_score($game, $userid) {
    global $DB;

    $score = $DB->get_field('game_grades', 'score', ['gameid' => $game->id, 'userid' => $userid]);

    // Need to detect errors/no result, without catching 0 scores.
    if (is_numeric($score)) {
        return $score;
    } else {
        return null;
    }
}

/**
 * Get the best current grade for a particular user in a game.
 *
 * @package mod_game
 *
 * @param object $game the game object.
 * @param integer $userid the id of the user.
 *
 * @return float the user's current grade for this game.
 */
function game_get_best_grade($game, $userid) {
    $score = game_get_best_score($game, $userid);

    if (is_numeric($score)) {
        return round($score * $game->grade, $game->decimalpoints === null ? 2 : $game->decimalpoints);
    } else {
        return null;
    }
}

/**
 * Converts score to grade
 *
 * @package mod_game
 *
 * @param float $score
 * @param stdClass $game
 *
 * @return float the user's current grade for this game.
 */
function game_score_to_grade($score, $game) {
    if ($score) {
        return round($score * $game->grade, $game->decimalpoints === null ? 2 : $game->decimalpoints);
    } else {
        return 0;
    }
}

/**
 * Determine review options
 *
 * @package mod_game
 *
 * @param object $game the game instance.
 * @param object $attempt the attempt in question.
 * @param object $context the roles and permissions context, for game module instance
 *
 * @return object an object with boolean fields responses, scores, feedback,
 *          correct_responses, solutions and general feedback
 */
function game_get_reviewoptions($game, $attempt, $context = null) {
    $options = new stdClass();
    $options->readonly = true;

    // Provide the links to the question review and comment script.
    $options->questionreviewlink = '/mod/game/reviewquestion.php';

    if ($context && !$attempt->preview) {
        // The teacher should be shown everything except during preview when the teachers wants to see just what the students see.
        $options->responses = true;
        $options->scores = true;
        $options->feedback = true;
        $options->correct_responses = true;
        $options->solutions = false;
        $options->generalfeedback = true;
        $options->overallfeedback = true;

        // Show a link to the comment box only for closed attempts.
        if ($attempt->timefinish) {
            $options->questioncommentlink = '/mod/game/comment.php';
        }
    } else {
        if (((time() - $attempt->timefinish) < 120) || $attempt->timefinish == 0) {
            $gamestatemask = GAME_REVIEW_IMMEDIATELY;
        } else if (!$game->timeclose || time() < $game->timeclose) {
            $gamestatemask = GAME_REVIEW_OPEN;
        } else {
            $gamestatemask = GAME_REVIEW_CLOSED;
        }
        $options->responses = ($game->review & $gamestatemask & GAME_REVIEW_RESPONSES) ? 1 : 0;
        $options->scores = ($game->review & $gamestatemask & GAME_REVIEW_SCORES) ? 1 : 0;
        $options->feedback = ($game->review & $gamestatemask & GAME_REVIEW_FEEDBACK) ? 1 : 0;
        $options->correct_responses = ($game->review & $gamestatemask & GAME_REVIEW_ANSWERS) ? 1 : 0;
        $options->solutions = ($game->review & $gamestatemask & GAME_REVIEW_SOLUTIONS) ? 1 : 0;
        $options->generalfeedback = ($game->review & $gamestatemask & GAME_REVIEW_GENERALFEEDBACK) ? 1 : 0;
        $options->overallfeedback = $attempt->timefinish && ($game->review & $gamestatemask & GAME_REVIEW_FEEDBACK);
    }

    return $options;
}

/**
 * Compute attempt layout
 *
 * @package mod_game
 *
 * @param object $game the game object.
 * @param stdClass $attempt
 * @return float the user's current grade for this game.
 */
function game_compute_attempt_layout($game, &$attempt) {
    global $DB;

    $ret = '';
    $recs = $DB->get_records_select(
        'game_queries',
        "attemptid=$attempt->id",
        null,
        '',
        'id,questionid,sourcemodule,glossaryentryid'
    );
    if ($recs) {
        foreach ($recs as $rec) {
            if ($rec->sourcemodule == 'glossary') {
                $ret .= $rec->glossaryentryid . 'G,';
            } else {
                $ret .= $rec->questionid . ',';
            }
        }
    }

    $attempt->layout = $ret . '0';
}

/**
 * Combines the review options from a number of different game attempts.
 *
 * @package mod_game
 *
 * @param object $game the game instance.
 * @param array $attempts an array of attempt objects.
 * @param object $context the roles and permissions context, the game instance
 *
 * @return array of two options objects, one showing which options are true for
 *          at least one of the attempts, the other showing which options are true
 *          for all attempts.
 */
function game_get_combined_reviewoptions($game, $attempts, $context = null) {
    $fields = ['readonly', 'scores', 'feedback', 'correct_responses', 'solutions', 'generalfeedback', 'overallfeedback'];
    $someoptions = new stdClass();
    $alloptions = new stdClass();
    foreach ($fields as $field) {
        $someoptions->$field = false;
        $alloptions->$field = true;
    }
    foreach ($attempts as $attempt) {
        $attemptoptions = game_get_reviewoptions($game, $attempt, $context);
        foreach ($fields as $field) {
            $someoptions->$field = $someoptions->$field || $attemptoptions->$field;
            $alloptions->$field = $alloptions->$field && $attemptoptions->$field;
        }
    }
    return [$someoptions, $alloptions];
}

/**
 * Save the overall grade for a user at a game in the game_grades table
 *
 * @package mod_game
 *
 * @param object $game The game for which the best grade is to be calculated and then saved.
 *
 * @return boolean Indicates success or failure.
 */
function game_save_best_score($game) {
    global $DB, $USER;

    // Get all the attempts made by the user.
    if (!$attempts = game_get_user_attempts($game->id, $USER->id, 'all')) {
        throw new moodle_exception('game_error', 'game', "Could not find any user attempts gameid={$game->id} userid={$USER->id}");
    }

    // Calculate the best grade.
    $bestscore = game_calculate_best_score($game, $attempts);

    // Save the best grade in the database.
    if ($grade = $DB->get_record('game_grades', ['gameid' => $game->id, 'userid' => $USER->id])) {
        $grade->score = $bestscore;
        $grade->timemodified = time();
        if (!$DB->update_record('game_grades', $grade)) {
            throw new moodle_exception('game_error', 'game', 'Could not update best grade');
        }
    } else {
        $grade = new stdClass();
        $grade->gameid = $game->id;
        $grade->userid = $USER->id;
        $grade->score = $bestscore;
        $grade->timemodified = time();
        if (!$DB->insert_record('game_grades', $grade)) {
            throw new moodle_exception('game_error', 'game', 'Could not insert new best grade');
        }
    }

    // Updates gradebook.
    $grades = new stdClass();
    $grades->userid = $USER->id;
    $grades->rawgrade = game_score_to_grade($bestscore, $game);
    $grades->datesubmitted = time();
    game_grade_item_update($game, $grades);

    return true;
}

/**
 * Calculate the overall score for a game given a number of attempts by a particular user.
 *
 * @package mod_game
 *
 * @return double         The overall score
 * @param object $game    The game for which the best score is to be calculated
 * @param array $attempts An array of all the attempts of the user at the game
 */
function game_calculate_best_score($game, $attempts) {

    switch ($game->grademethod) {
        case GAME_GRADEMETHOD_FIRST:
            foreach ($attempts as $attempt) {
                return $attempt->score;
            }
            break;
        case GAME_GRADEMETHOD_LAST:
            foreach ($attempts as $attempt) {
                $final = $attempt->score;
            }
            return $final;
        case GAME_GRADEMETHOD_AVERAGE:
            $sum = 0;
            $count = 0;
            foreach ($attempts as $attempt) {
                $sum += $attempt->score;
                $count++;
            }
            return (float)$sum / $count;
        default:
        case GAME_GRADEMETHOD_HIGHEST:
            $max = 0;
            foreach ($attempts as $attempt) {
                if ($attempt->score > $max) {
                    $max = $attempt->score;
                }
            }
            return $max;
    }
}

/**
 * Return the attempt with the best score for a game
 *
 * @package mod_game
 *
 * Which attempt is the best depends on $game->grademethod. If the  grade
 * method is GRADEAVERAGE then this function simply returns the last attempt.
 * @return object         The attempt with the best grade
 * @param object $game    The game for which the best grade is to be calculated
 * @param array $attempts An array of all the attempts of the user at the game
 */
function game_calculate_best_attempt($game, $attempts) {

    switch ($game->grademethod) {
        case GAME_ATTEMPTFIRST:
            foreach ($attempts as $attempt) {
                return $attempt;
            }
            break;
        case GAME_GRADEAVERAGE: // Need to do something with it.
        case GAME_ATTEMPTLAST:
            foreach ($attempts as $attempt) {
                $final = $attempt;
            }
            return $final;
        default:
        case GAME_GRADEHIGHEST:
            $max = -1;
            foreach ($attempts as $attempt) {
                if ($attempt->sumgrades > $max) {
                    $max = $attempt->sumgrades;
                    $maxattempt = $attempt;
                }
            }
            return $maxattempt;
    }
}

/**
 * get questions for sudoku
 *
 * @package mod_game
 *
 * @param string $questionlist
 */
function game_sudoku_getquestions($questionlist) {
    global $CFG, $DB;

    // Load the questions.
    $sql = "SELECT q.*,qmo.single " .
        " FROM {$CFG->prefix}question " .
            " LEFT JOIN {$CFG->prefix}qtype_multichoice_options qmo ON q.id=qmo.questionid AND q.qtype='multichoice' " .
        " WHERE q.id IN ($questionlist)";
    if (!$questions = $DB->get_records_select('question', "id IN ($questionlist)")) {
        throw new moodle_exception('no_questions', 'game');
    }

    // Load the question type specific information.
    if (!get_question_options($questions)) {
        throw new moodle_exception('game_error', 'game', 'Could not load question options');
    }

    $sql = "SELECT value FROM {$CFG->prefix}config_plugins WHERE plugin = ? AND name = ?";
    $rec = $DB->get_record_select('config_plugins', 'plugin = ? AND name = ?', ['qtype_multichoice', 'shuffleanswers']);
    if ($rec != false) {
        if ($rec->value == 1) {
            foreach ($questions as $question) {
                if (isset($question->options)) {
                    if (isset($question->options->answers)) {
                        $list = $question->options->answers;
                        $keys = array_keys($list);
                        shuffle($keys);
                        $random = [];
                        foreach ($keys as $key) {
                            $random[$key] = $list[$key];
                        }
                        $question->options->answers = $random;
                    }
                }
            }
        }
    }

    return $questions;
}

/**
 * Filter glossary
 *
 * @package mod_game
 *
 * @param string $text
 * @param int $entryid
 * @param int $contextid
 * @param int $courseid
 */
function game_filterglossary($text, $entryid, $contextid, $courseid) {
    global $CFG, $DB;

    for (;;) {
        $find = '@@PLUGINFILE@@';
        $pos = strpos($text, $find);
        if ($pos === false) {
            break;
        }

        $pos2 = strpos($text, '/', $pos);
        if ($pos2 === false) {
            break;
        }

        $pos3 = strpos($text, '"', $pos);
        if ($pos3 === false) {
            break;
        }

        $file = substr($text, $pos2 + 1, $pos3 - $pos2 - 1);

        $new = $CFG->wwwroot . "/pluginfile.php/$contextid/mod_glossary/entry/$entryid/$file";
        $text = substr($text, 0, $pos) . $new . substr($text, $pos3);
    }

    $questiontext = str_replace('$$' . '\\' . '\\' . 'frac', '$$\\' . 'frac', $text);
    return game_filtertext($text, $courseid);
}

/**
 * Filter book
 *
 * @package mod_game
 *
 * @param string $text
 * @param int $chapterid
 * @param int $contextid
 * @param int $courseid
 */
function game_filterbook($text, $chapterid, $contextid, $courseid) {
    global $CFG;

    for (;;) {
        $find = '@@PLUGINFILE@@';
        $pos = strpos($text, $find);
        if ($pos === false) {
            break;
        }

        $pos2 = strpos($text, '/', $pos);
        if ($pos2 === false) {
            break;
        }

        $pos3 = strpos($text, '"', $pos);
        if ($pos3 === false) {
            break;
        }

        $file = substr($text, $pos2 + 1, $pos3 - $pos2 - 1);

        $new = $CFG->wwwroot . "/pluginfile.php/$contextid/mod_book/chapter/$chapterid/$file";
        $text = substr($text, 0, $pos) . $new . substr($text, $pos3);
    }

    $questiontext = str_replace('$$' . '\\' . '\\' . 'frac', '$$\\' . 'frac', $text);
    return game_filtertext($text, $courseid);
}

/**
 * Filter questio
 *
 * @package mod_game
 *
 * @param string $questiontext
 * @param int $questionid
 * @param int $contextid
 * @param int $courseid
 */
function game_filterquestion($questiontext, $questionid, $contextid, $courseid) {
    global $CFG, $DB;

    for (;;) {
        $find = '@@PLUGINFILE@@';
        $pos = strpos($questiontext, $find);
        if ($pos === false) {
            break;
        }

        $pos2 = strpos($questiontext, '/', $pos);
        if ($pos2 === false) {
            break;
        }

        $pos3 = strpos($questiontext, '"', $pos);
        if ($pos3 === false) {
            break;
        }

        $file = substr($questiontext, $pos2 + 1, $pos3 - $pos2 - 1);

        $new = $CFG->wwwroot . "/pluginfile.php/$contextid/mod_game/questiontext/$questionid/$file";
        $questiontext = substr($questiontext, 0, $pos) . $new . substr($questiontext, $pos3);
    }

    $questiontext = str_replace('$$' . '\\' . '\\' . 'frac', '$$\\' . 'frac', $questiontext);
    return game_filtertext($questiontext, $courseid);
}

/**
 * Filter question answer
 *
 * @package mod_game
 *
 * @param string $questiontext
 * @param int $questionid
 * @param int $contextid
 * @param int $courseid
 */
function game_filterquestion_answer($questiontext, $questionid, $contextid, $courseid) {
    global $CFG, $DB;

    for (;;) {
        $find = '@@PLUGINFILE@@';
        $pos = strpos($questiontext, $find);
        if ($pos === false) {
            break;
        }

        $pos2 = strpos($questiontext, '/', $pos);
        if ($pos2 === false) {
            break;
        }

        $pos3 = strpos($questiontext, '"', $pos);
        if ($pos3 === false) {
            break;
        }

        $file = substr($questiontext, $pos2 + 1, $pos3 - $pos2 - 1);

        $new = $CFG->wwwroot . "/pluginfile.php/$contextid/mod_game/answer/$questionid/$file";
        $questiontext = substr($questiontext, 0, $pos) . $new . substr($questiontext, $pos3);
    }

    return game_filtertext($questiontext, $courseid);
}

/**
 * Filter text
 *
 * @package mod_game
 *
 * @param string $text
 * @param int $courseid
 */
function game_filtertext($text, $courseid) {
    $formatoptions = new stdClass();
    $formatoptions->noclean = true;
    $formatoptions->filter = 1;
    $text = trim(format_text($text, FORMAT_MOODLE, $formatoptions));

    $start = '<div class="text_to_html">';
    if (substr($text, 0, strlen($start)) == $start) {
        if (substr($text, -6) == '</div>') {
            $text = substr($text, strlen($start), -6);
        }
    }
    if (substr($text, 0, 3) == '<p>') {
        if (substr($text, -4) == '</p>') {
            $text = substr($text, 3, -4);
        }
    }

    return $text;
}

/**
 * To javascript string
 *
 * @package mod_game
 *
 * @param string $text
 */
function game_tojavascriptstring($text) {
    $from = ['"', "\r", "\n"];
    $to = ['\"', ' ', ' '];

    $from[] = '<script ';
    $to[] = '<" + "script ';
    $from[] = '</script>';
    $to[] = '<" + "/script>';

    $text = str_replace($from, $to, $text);

    return $text;
}

/**
 * Repair question
 *
 * @package mod_game
 *
 * @param string $s
 */
function game_repairquestion($s) {
    if (substr($s, 0, 3) == '<p>') {
        $s = substr($s, 3);
    }
    if (substr($s, -4) == '</p>') {
        $s = substr($s, 0, -4);
    }
    if (substr($s, 0, 4) == '<br>') {
        $s = substr($s, 4);
    }
    if (substr($s, 0, 6) == '<br />') {
        $s = substr($s, 6);
    }
    if (substr($s, 0, 5) == '<div ' && substr($s, -6) == '</div>') {
        $pos = strpos($s, '>');
        if ($pos != false) {
            $s = substr($s, $pos + 1);
        }
        $s = substr($s, 0, -6);
    }
    return str_replace(["\'", '\"'], ["'", '"'], $s);
}

/**
 * Delete a game attempt.
 *
 * @package mod_game
 *
 * @param stdClass $attempt
 * @param stdClass $game
 */
function game_delete_attempt($attempt, $game) {
    global $DB;

    if (is_numeric($attempt)) {
        if (!$attempt = $DB->get_record('game_attempts', 'id', $attempt)) {
            return;
        }
    }

    if ($attempt->gameid != $game->id) {
        debugging("Trying to delete attempt $attempt->id which belongs to game $attempt->gameid " .
                "but was passed gameid $game->id.");
        return;
    }

    $DB->delete_records('game_attempts', ['id' => $attempt->id]);

    /* Search game_attempts for other instances by this user.
       If none, then delete record for this game, this user from game_grades
       else recalculate best grade */
    $userid = $attempt->userid;
    if (!$DB->record_exists('game_attempts', ['userid' => $userid, 'gameid' => $game->id])) {
        $DB->delete_records('game_grades', ['userid' => $userid, 'gameid' => $game->id]);
    } else {
        game_save_best_score($game);
    }

    game_update_grades($game, $userid);
}

/**
 * Returns the most recent attempt by a given user on a given game. May be finished, or may not.
 *
 * @package mod_game
 *
 * @param integer $gameid the id of the game.
 * @param integer $userid the id of the user.
 *
 * @return mixed the attempt if there is one, false if not.
 */
function game_get_latest_attempt_by_user($gameid, $userid) {
    global $DB;

    $attempt = $DB->get_records_sql('SELECT qa.* FROM {game_attempts} qa
            WHERE qa.gameid=? AND qa.userid= ? ORDER BY qa.timestart DESC, qa.id DESC', [$gameid, $userid], 0, 1);

    if ($attempt) {
        return array_shift($attempt);
    } else {
        return false;
    }
}

/**
 * get grading option name
 *
 * @package mod_game
 *
 * @param int $option one of the values GAME_GRADEHIGHEST, GAME_GRADEAVERAGE, GAME_ATTEMPTFIRST or GAME_ATTEMPTLAST.
 * @return the lang string for that option.
 */
function game_get_grading_option_name($option) {
    if ($option == 0) {
        $option = GAME_GRADEHIGHEST;
    }

    $strings = game_get_grading_options();

    return $strings[$option];
}

/**
 * Right to left.
 *
 * @package mod_game
 *
 * @param string $lang
 */
function game_right_to_left($lang) {
    return (get_string_manager()->get_string('thisdirection', 'langconfig', null, $lang) == 'rtl');
}

/**
 * Compute reverse print
 *
 * @package mod_game
 *
 * @param stdClass $attempt
 * @param string $wordrtl
 * @param boolean $reverseprint
 */
function game_compute_reserve_print($attempt, &$wordrtl, &$reverseprint) {
    if (function_exists('right_to_left')) {
        if ($attempt->language != '') {
            $wordrtl = game_right_to_left($attempt->language);
        } else {
            $wordrtl = right_to_left();
        }
        $reverseprint = ($wordrtl != right_to_left());
    } else {
        $reverseprint = false;
        $wordrtl = 'ltr';
    }
}

/**
 * select from repetitions
 *
 * @package mod_game
 *
 * @param stdClass $game
 * @param stdClass $recs
 * @param booolean $need
 */
function game_select_from_repetitions($game, $recs, $need) {
    global $DB, $USER;

    $ret = [];

    $field = ($game->sourcemodule == 'glossary' ? 'glossaryentryid' : 'questionid');

    if (count($recs) <= $need) {
        foreach ($recs as $rec) {
            $id = $rec->$field;
            $ret[$id] = 1;
        }

        return $ret;
    }

    $countzero = 0;
    foreach ($recs as $rec) {
        $a = ['gameid' => $game->id, 'userid' => $USER->id, 'questionid' => $rec->questionid,
            'glossaryentryid' => $rec->glossaryentryid];
        $id = $rec->$field;
        if (($rec = $DB->get_record('game_repetitions', $a, 'id,repetitions AS r')) != false) {
            $reps[$id] = $rec->r;
        } else {
            $reps[$id] = 0;
            if (++$countzero >= $need) {
                break;
            }
        }
    }
    asort($reps);
    foreach ($reps as $id => $r) {
        $ret[$id] = 1;
        if (count($ret) >= $need) {
            break;
        }
    }

    return $ret;
}

/**
 * Grades responses
 *
 * @package mod_game
 *
 * @param stdClass $question
 * @param array $responses
 * @param int $maxgrade
 * @param string $answertext
 * @param boolean $answered
 */
function game_grade_responses($question, $responses, $maxgrade, &$answertext, &$answered) {
    $answered = true;

    if ($question->qtype == 'multichoice') {
        if ($question->options->single == 0) {
            return game_grade_responses_multianswer($question, $responses, $maxgrade, $answertext);
        }
        $name = "resp{$question->id}_";
        if (!isset($responses->$name)) {
            $answered = false;
            return 0;
        }
        $value = $responses->$name;
        $answer = $question->options->answers[$value];
        $answertext = $answer->answer;

        return $answer->fraction * $maxgrade;
    } else {
        $name = "resp{$question->id}_";
        if (!isset($responses->$name)) {
            $answered = false;
            return 0; // Not answered this question.
        }
        $answertext = game_upper($responses->$name);

        foreach ($question->options->answers as $answer) {
            if (game_upper($answer->answer) == $answertext) {
                return $answer->fraction * $maxgrade;
            }
        }

        return 0;
    }
}

/**
 * Responses multianswer
 *
 * @package mod_game
 *
 * @param stdClass $question
 * @param array $responses
 * @param int $maxgrade
 * @param string $answertext
 */
function game_grade_responses_multianswer($question, $responses, $maxgrade, &$answertext) {
    $name = "resp{$question->id}_";

    $fraction = 0;
    foreach ($responses as $key => $value) {
        $sub = substr($key, 0, strlen($name));
        if ($sub != $name) {
            continue;
        }

        $name2 = $name . $value;
        $value2 = $responses->$name2;
        $answer = $question->options->answers[$value2];
        $fraction += $answer->fraction;
    }

    return $fraction * $maxgrade;
}

/**
 * Print question
 *
 * @package mod_game
 *
 * @param stdClass $game
 * @param string $question
 * @param stdClass $context
 */
function game_print_question($game, $question, $context) {
    if ($question->qtype == 'multichoice') {
        if ($question->options->single == 0) {
            game_print_question_multianswer($game, $question, $context);
        } else {
            game_print_question_multichoice($game, $question, $context);
        }
    } else if ($question->qtype == 'shortanswer') {
        game_print_question_shortanswer($game, $question, $context);
    }
}

/**
 * Print question multichoice
 *
 * @package mod_game
 *
 * @param stdClass $game
 * @param string $question
 * @param stdClass $context
 */
function game_print_question_multichoice($game, $question, $context) {
    $questiontext = $question->questiontext;
    $answerprompt = get_string('singleanswer', 'quiz');
    $anss = [];
    foreach ($question->options->answers as $a) {
        $answer = new stdClass();
        if (substr($a->answer, 0, 3) == '<p>' || substr($a->answer, 0, 3) == '<P>') {
            $a->answer = substr($a->answer, 3);
            $s = rtrim($a->answer);
            if (substr($s, 0, -3) == '<p>' || substr($s, 0, -3) == '<P>') {
                $a->answer = substr($a->answer, 0, -3);
            }
        }

        if (substr($a->answer, 0, 3) == '<p ' && substr($a->answer, -4) == '</p>') {
                $a->answer = '<span ' . substr($a->answer, 3, strlen($a->answer) - 7) . '</span>';
        }

        $a->answer = game_filterquestion_answer(str_replace('\"', '"', $a->answer), $a->id, $context->id, $game->course);
        $answer->control = "<input  id=\"resp{$question->id}_{$a->id}\" name=\"resp{$question->id}_\"  " .
            " type=\"radio\" value=\"{$a->id}\" /> " . $a->answer;
        $answer->class = 'radio';
        $answer->id = $a->id;
        $answer->text = $a->answer;
        $answer->feedbackimg = '';
        $answer->feedback = '';
        $anss[] = $answer;
    }
?>
<div class="qtext">
    <?php echo game_filterquestion(str_replace('\"', '"', $questiontext), $question->id, $context->id, $game->course); ?>
</div>


<div class="ablock clearfix">
    <div class="prompt">
        <?php echo $answerprompt; ?>
    </div>

    <table class="answer">
    <?php
    $row = 1;
    foreach ($anss as $answer) {
    ?>
        <tr class="<?php echo 'r' . $row = $row ? 0 : 1; ?>">
            <td>
                <?php echo $answer->control; ?>
            </td>
        </tr>
        <?php
    }
        ?>
    </table>
</div>
    <?php
}

/**
 * Print question multianswer
 *
 * @package mod_game
 *
 * @param object $game the game object.
 * @param stdClass $question
 * @param stdClass $context
 */
function game_print_question_multianswer($game, $question, $context) {
    $questiontext = $question->questiontext;
    $answerprompt = get_string('singleanswer', 'quiz');
    $feedback = '';
    $anss = [];
    foreach ($question->options->answers as $a) {
        $answer = new stdClass();
        if (substr($a->answer, 0, 3) == '<p>' || substr($a->answer, 0, 3) == '<P>') {
            $a->answer = substr($a->answer, 3);
            $s = rtrim($a->answer);
            if (substr($s, 0, -3) == '<p>' || substr($s, 0, -3) == '<P>') {
                $a->answer = substr($a->answer, 0, -3);
            }
        }
        $a->answer = game_filterquestion_answer(str_replace('\"', '"', $a->answer), $a->id, $context->id, $game->course);
        $answer->control = "<input  id=\"resp{$question->id}_{$a->id}\" name=\"resp{$question->id}_{$a->id}\"  " .
            " type=\"checkbox\" value=\"{$a->id}\" /> " . $a->answer;
        $answer->class = 'radio';
        $answer->id = $a->id;
        $answer->text = $a->answer;
        $answer->feedbackimg = '';
        $answer->feedback = '';
        $anss[] = $answer;
    }
?>
<div class="qtext">
    <?php echo game_filterquestion(str_replace('\"', '"', $questiontext), $question->id, $context->id, $game->course); ?>
</div>


<div class="ablock clearfix">
    <div class="prompt">
        <?php echo $answerprompt; ?>
    </div>

    <table class="answer">
    <?php
    $row = 1;
    foreach ($anss as $answer) {
    ?>
        <tr class="<?php echo 'r' . $row; ?>">
            <td>
                <?php echo $answer->control; ?>
            </td>
        </tr>
        <?php
    }
        ?>
    </table>
</div>
    <?php
}

/**
 * Print question show answer
 *
 * @package mod_game
 *
 * @param stdClass $game
 * @param stdClass $question
 * @param stdClass $context
 */
function game_print_question_shortanswer($game, $question, $context) {
    $questiontext = $question->questiontext;

?>
<div class="qtext">
    <?php
    echo game_filterquestion(str_replace('\"', '"', $questiontext), $question->id, $context->id, $game->course);
    ?>
</div>

<div class="ablock clearfix">
  <div class="prompt">
    <?php echo get_string("answer", "quiz") . ': '; ?>
  </div>
  <div class="answer">
    <input type="text" name="resp<?php echo $question->id; ?>_" size="80"/>
  </div>
</div>
    <?php
}

/**
 * Snakes get board.
 *
 * @package mod_game
 *
 * @param object $game the game object.
 */
function game_snakes_get_board($game) {
    global $CFG, $DB;

    if ($game->param3 != 0) {
        $board = $DB->get_record('game_snakes_database', ['id' => $game->param3]);
        if ($board == false) {
            require_once(dirname(__FILE__) . '/../db/importsnakes.php');
            $board = $DB->get_record('game_snakes_database', ['id' => $game->param3]);
        }
        if ($board == false) {
            throw new moodle_exception('game_error', 'game', 'No board');
        }
        $board->imagesrc = $CFG->wwwroot . '/mod/game/snakes/boards/' . $board->fileboard;
        [$board->width, $board->height] = getimagesize(__DIR__ . '/snakes/boards/' . $board->fileboard);
    } else {
        // User defined board.
        $board = game_snakes_create_user_defined_board($game);
    }

    return $board;
}

/**
 * Creates user defined board.
 *
 * @package mod_game
 *
 * @param object $game the game object.
 */
function game_snakes_create_user_defined_board(&$game) {
    global $CFG, $DB;

    $board = game_snakes_get_board_params($game);

    $cmg = get_coursemodule_from_instance('game', $game->id, $game->course);
    $modcontext = game_get_context_module_instance($cmg->id);

    if ($game->param5) {
        // Param5 means dirty image. Create it again.
        require("snakes/createboard.php");
        $fs = get_file_storage();
        $files = $fs->get_area_files($modcontext->id, 'mod_game', 'snakes_file', $game->id);
        $f = false;
        foreach ($files as $f) {
            if ($f->is_directory()) {
                continue;
            }
            break;
        }
        if ($f === false) {
            throw new moodle_exception('game_error', 'game', 'No image specified');
        }
        $im = game_createsnakesboard(
            $f->get_content(),
            $board->usedcols,
            $board->usedrows,
            $board->headery,
            $board->headery,
            $board->footerx,
            $board->headerx,
            $board->data,
            $board->width,
            $board->height
        );
        ob_start();
        imagepng($im);
        $data = ob_get_contents();
        ob_end_clean();
        $fileinfo = [
            'contextid' => $modcontext->id, // ID of context.
            'component' => 'mod_game', // Usually = table name.
            'filearea' => 'snakes_board', // Usually = table name.
            'itemid' => $game->id, // Usually = ID of row in table.
            'filepath' => '/', // Any path beginning and ending in /.
            'filename' => 'board.png']; // Any filename.
        $fs->delete_area_files($modcontext->id, 'mod_game', 'snakes_board', $game->id);
        $file = $fs->create_file_from_string($fileinfo, $data);
        $imageinfo = $file->get_imageinfo();
        $game->param6 = $imageinfo['width'];
        $game->param7 = $imageinfo['height'];
        $sql = "UPDATE {$CFG->prefix}game SET param5=0,param6=$game->param6,param7=$game->param7 WHERE id=$game->id";
        if (!$DB->execute($sql)) {
            error('problem in ' . $sql);
        }
    }

    $board->imagesrc = "{$CFG->wwwroot}/pluginfile.php/{$modcontext->id}/mod_game/{$game->id}/snakes_board/board.png";
    $board->width = $game->param6;
    $board->height = $game->param7;
    $board->direction = 1;
    $board->fileboard = 'board.png';

    return $board;
}

/**
 * Snakes get board params.
 *
 * @package mod_game
 *
 * @param object $game the game object.
 */
function game_snakes_get_board_params($game) {
    $board = new stdClass();

    $a = explode('#', $game->param9);
    foreach ($a as $s) {
        $pos = strpos($s, ':');
        if ($pos) {
            $name = substr($s, 0, $pos);
            if (substr($name, 0, 7) == 'snakes_') {
                $name = substr($name, 7);
            }
            $board->$name = substr($s, $pos + 1);
        }
    }

    return $board;
}

/**
 * Export create temp dir
 *
 * @package mod_game
 */
function game_export_createtempdir() {
    global $CFG;

    // Creates a random upload directory in temp.
    $newdir = $CFG->dataroot . "/temp/game";
    if (!file_exists($newdir)) {
        mkdir($newdir);
    }

    srand((double)microtime() * 1000000);
    while (true) {
        $rbasedir = "game/" . date("Y-m-d-H.i.s-") . rand(0, 10000);
        $newdir = $CFG->dataroot . '/temp/' . $rbasedir;
        if (!file_exists($newdir)) {
            mkdir($newdir);
            return $newdir;
        }
    }
}

/**
 * Compatibility wrapper for creating zip archives.
 *
 * Uses legacy zip_files() when available; otherwise falls back to
 * Moodle file packer API.
 *
 * @param array $fullsrcfiles Full OS paths to files that should be zipped.
 * @param string $filezip Full OS path to the target zip file.
 * @param string $srcdir Base source directory, used to compute relative paths inside zip.
 * @return bool
 */
function game_zip_files_compat(array $fullsrcfiles, string $filezip, string $srcdir): bool {
    global $CFG;

    // Legacy Moodle support.
    if (function_exists('zip_files')) {
        return zip_files($fullsrcfiles, $filezip);
    }

    require_once($CFG->libdir . '/filelib.php');

    $packer = get_file_packer('application/zip');
    if (!$packer) {
        return false;
    }

    $zipfiles = [];
    $srcdir = rtrim($srcdir, '/\\');

    foreach ($fullsrcfiles as $fullpath) {
        if (!is_file($fullpath) || !is_readable($fullpath)) {
            continue;
        }

        // Relative path inside zip.
        $relativepath = substr($fullpath, strlen($srcdir) + 1);
        $relativepath = str_replace('\\', '/', $relativepath);

        $zipfiles[$relativepath] = $fullpath;
    }

    if (empty($zipfiles)) {
        return false;
    }

    return $packer->archive_to_pathname($zipfiles, $filezip);
}

/**
 * Create zip
 *
 * @package mod_game
 *
 * @param string $srcdir
 * @param int $courseid
 * @param string $filename
 */
function game_create_zip($srcdir, $courseid, $filename) {
    global $CFG;

    $dir = $CFG->dataroot . '/' . $courseid;
    $filezip = $dir . "/export/{$filename}";

    if (file_exists($filezip)) {
        unlink($filezip);
    }

    if (!file_exists($dir)) {
        mkdir($dir);
    }

    if (!file_exists($dir . '/export')) {
        mkdir($dir . '/export');
    }

    $srcfiles = get_directory_list($srcdir, '', true, true, true);
    $fullsrcfiles = [];
    foreach ($srcfiles as $file) {
        $fullsrcfiles[] = $srcdir . '/' . $file;
    }

    game_zip_files_compat($fullsrcfiles, $filezip, $srcdir);

    return (file_exists($filezip) ? $filezip : '');
}

/**
 * Get string lang
 *
 * @package mod_game
 *
 * @param string $identifier
 * @param string $module
 * @param string $lang
 */
function game_get_string_lang($identifier, $module, $lang) {
    return get_string_manager()->get_string($identifier, $module, null, $lang);
}

/**
 * Inserts a record to game_attempts.
 *
 * @package mod_game
 *
 * @param object $game the game object.
 */
function game_addattempt($game) {
    global $DB, $USER;

    $newrec = new stdClass();
    $newrec->gamekind = $game->gamekind;
    $newrec->gameid = $game->id;
    $newrec->userid = $USER->id;
    $newrec->timestart = time();
    $newrec->timefinish = 0;
    $newrec->timelastattempt = 0;
    $newrec->preview = 0;
    $params = ['gameid' => $game->id, 'userid' => $USER->id];
    $newrec->attempt = $DB->get_field('game_attempts', 'max(attempt)', $params) + 1;
    $newrec->score = 0;

    if (!($newid = $DB->insert_record('game_attempts', $newrec))) {
        throw new moodle_exception('game_error', 'game', 'Insert game_attempts: new rec not inserted');
    }

    if ($USER->username == 'guest') {
        $key = 'mod/game:instanceid' . $game->id;
        $_SESSION[$key] = $newid;
    }

    return $DB->get_record_select('game_attempts', 'id=' . $newid);
}

/**
 * Get contexts
 *
 * @package mod_game
 */
function game_get_contexts() {
    global $CFG, $COURSE;

    require($CFG->dirroot . '/question/editlib.php');
    $thiscontext = game_get_context_course_instance($COURSE->id);
    $contexts = new question_edit_contexts($thiscontext);
    $caps = ['moodle/question:viewmine', 'moodle/question:viewall'];

    return $contexts->having_one_cap($caps);
}

/**
 * Export split files
 *
 * @package mod_game
 *
 * @param int $courseid
 * @param stdClass $context
 * @param string $filearea
 * @param int $id
 * @param string $line
 * @param string $destdir
 * @param array $files
 */
function game_export_split_files($courseid, $context, $filearea, $id, $line, $destdir, &$files) {
    global $CFG, $DB;

    $contextcourse = false;

    $fs = get_file_storage();

    for (;;) {
        $pos1 = strpos($line, '@@PLUGINFILE@@');
        if ($pos1 === false) {
            break;
        }

        $pos2 = strpos($line, '"', $pos1);
        if ($pos2 === false) {
            break;
        }

        $file = urldecode(substr($line, $pos1 + 15, $pos2 - $pos1 - 15));

        $posext = strrpos($file, '.');
        $filenoext = substr($file, $posext);
        $ext = substr($file, $posext + 1);
        $oldfile = $CFG->wwwroot . "/pluginfile.php/$context->id/mod_game/$filearea/$id/$file";
        for ($i = 0;; $i++) {
            $newfile = $filenoext . $i;
            $newfile = md5($newfile) . '.' . $ext;
            if (!array_search($newfile, $files)) {
                break;
            }
        }

        $line = substr($line, 0, $pos1) . 'images/' . $newfile . substr($line, $pos2);
        $files[$oldfile] = $newfile;

        // Have to copy the files.
        if (count($files) == 1) {
            mkdir($destdir . '/images');
        }

        if ($contextcourse === false) {
            if (!$contextcourse = game_get_context_course_instance($courseid)) {
                throw new moodle_exception('game_error', 'game', 'nocontext');
            }
        }
        $params = ['component' => 'question', 'filearea' => $filearea,
            'itemid' => $id, 'filename' => $file, 'contextid' => $contextcourse->id];
        $rec = $DB->get_record('files', $params);

        if (!$file = $fs->get_file_by_hash($rec->pathnamehash) || $file->is_directory()) {
            continue;
        }
        $file->copy_content_to($destdir . '/images/' . $newfile);
    }

    return $line;
}

/**
 * Grade questions
 *
 * @package mod_game
 *
 * @param array $questions
 */
function game_grade_questions($questions) {
    $grades = [];
    foreach ($_POST as $key => $value) {
        $id = game_question_get_id_from_name_prefix($key);
        if ($id === false) {
            continue;
        }

        if (array_key_exists($id, $grades)) {
            $grade = $grades[$id];
        } else {
            $grade = new stdClass();
            $grade->grade = 0;
            $grade->id = $id;
        }

        $grade->response = $value;

        $question = $questions[$id];
        if ($question->qtype == 'multichoice') {
            $answer = $question->options->answers[$value];
            $grade->grade += $answer->fraction;
        } else if ($question->qtype == 'shortanswer') {
            foreach ($question->options->answers as $answerid => $answer) {
                if (game_upper($answer->answer == $value)) {
                    $grade->grade = $answer->fraction;
                    break;
                }
            }
        }

        $grades[$grade->id] = $grade;
    }

    return $grades;
}

/**
 * Extract question id from the prefix of form element names
 *
 * @package mod_game
 *
 * @return int The question id
 * @param string $name The name that contains a prefix that was constructed with question_make_name_prefix()
 */
function game_question_get_id_from_name_prefix($name) {
    if (!preg_match('/^resp([0-9]+)_/', $name, $matches)) {
        return false;
    }
    return (int)$matches[1];
}

/**
 * Get version.
 *
 * @package mod_game
 */
function game_get_version() {
    global $CFG, $DB;

    if (($rec = $DB->get_record('modules', ['name' => 'game'])) === false) {
        return '';
    }

    if (isset($rec->version)) {
        return $rec->version;
    }

    $module = new StdClass();
    require($CFG->dirroot . '/mod/game/version.php');
    return $module->version;
}

/**
 * Can start a new attempt
 *
 * @package mod_game
 *
 * @param object $game the game object.
 */
function game_can_start_new_attempt($game) {
    global $DB, $USER;

    if ($game->maxattempts == 0) {
        return true;
    }

    $sql = "SELECT COUNT(*) as c FROM {game_attempts} WHERE gameid={$game->id} AND userid={$USER->id}";
    if (($rec = $DB->get_record_sql($sql)) === false) {
        return true;
    }

    if ($rec->c >= $game->maxattempts) {
        return false;
    } else {
        return true;
    }
}

/**
 * strlen
 *
 * @package mod_game
 *
 * @param string $str
 */
function game_strlen($str) {
    if (game_get_moodle_version() >= '02.08') {
        return core_text::strlen($str);
    } else if (game_get_moodle_version() >= '02.04') {
        return textlib::strlen($str);
    } else {
        return textlib_get_instance()->strlen($str);
    }
}

/**
 * substr
 *
 * @package mod_game
 */
function game_substr() {
    $num = func_num_args();
    $a = func_get_args();

    $pos = $a[1] === null ? 0 : $a[1];
    if ($num == 3) {
        if (game_get_moodle_version() >= '02.08') {
            return core_text::substr($a[0], $pos, $a[2]);
        } else if (game_get_moodle_version() >= '02.04') {
            return textlib::substr($a[0], $pos, $a[2]);
        } else {
            return textlib_get_instance()->substr($a[0], $pos, $a[2]);
        }
    } else if ($num == 2) {
        if (game_get_moodle_version() >= '02.08') {
            return core_text::substr($a[0], $pos);
        }
        if (game_get_moodle_version() >= '02.04') {
            return textlib::substr($a[0], $pos);
        } else {
            return textlib_get_instance()->substr($a[0], $pos);
        }
    } else {
        die('Substr requires 2 or 3 parameters');
    }
}

/**
 * strtoupper
 *
 * @package mod_game
 *
 * @param string $str
 */
function game_strtoupper($str) {
    if (game_get_moodle_version() >= '02.08') {
        return core_text::strtoupper($str);
    }
    if (game_get_moodle_version() >= '02.04') {
        return textlib::strtoupper($str);
    } else if (game_get_moodle_version() >= '02.01') {
        return textlib_get_instance()->strtoupper($str);
    } else {
        return textlib_get_instance()->qstrtoupper($str);
    }
}

/**
 * strpos
 *
 * @package mod_game
 *
 * @param string $haystack
 * @param string $needle
 * @param int $offset
 */
function game_strpos($haystack, $needle, $offset = 0) {
    if (game_get_moodle_version() >= '02.08') {
        return core_text::strpos($haystack, $needle, $offset);
    }

    if (game_get_moodle_version() >= '02.04') {
        return textlib::strpos($haystack, $needle, $offset);
    }

    return textlib_get_instance()->strpos($haystack, $needle, $offset);
}

/**
 * show query
 *
 * @package mod_game
 *
 * @param object $game the game
 * @param stdClass $query
 * @param string $text
 */
function game_show_query($game, $query, $text) {
    global $CFG, $DB;

    if ($game->glossaryid) {
        $sql = "SELECT id,course FROM {$CFG->prefix}glossary WHERE id={$game->glossaryid}";
        $glossary = $DB->get_record_sql($sql);
        $cmglossary = get_coursemodule_from_instance('glossary', $game->glossaryid, $glossary->course);
        $contextglossary = game_get_context_module_instance($cmglossary->id);
        return game_filterglossary(str_replace('\"', '"', $text), $query->glossaryentryid, $contextglossary->id, $game->course);
    } else if ($query->questionid) {
        $cmgame = get_coursemodule_from_instance('game', $game->id, $game->course);
        $context = game_get_context_module_instance($cmgame->id);
        $text = str_replace(["\'", '\"'], ["'", '"'], $text);
        return game_filterquestion($text, $query->questionid, $context->id, $game->course);
    }

    return $text;
}

/**
 * use events?
 *
 * @package mod_game
 */
function game_use_events() {
    $version = game_get_moodle_version();

    return($version >= '02.07');
}
