예제 #1
0
 /**
  * Constructor.
  * @param question_usage_by_activity $quba the question usage we will be picking variants for.
  * @param qubaid_condition $qubaids ids of the usages to consider when counting previous uses of each variant.
  */
 public function __construct(\question_usage_by_activity $quba, \qubaid_condition $qubaids)
 {
     $questionidtoseed = array();
     foreach ($quba->get_attempt_iterator() as $qa) {
         $question = $qa->get_question();
         if ($question->get_num_variants() > 1) {
             $questionidtoseed[$question->id] = $question->get_variants_selection_seed();
         }
     }
     if (empty($questionidtoseed)) {
         return;
     }
     $this->variantsusecounts = array_fill_keys($questionidtoseed, array());
     $variantsused = \question_engine::load_used_variants(array_keys($questionidtoseed), $qubaids);
     foreach ($variantsused as $questionid => $usagecounts) {
         $seed = $questionidtoseed[$questionid];
         foreach ($usagecounts as $variant => $count) {
             if (isset($this->variantsusecounts[$seed][$variant])) {
                 $this->variantsusecounts[$seed][$variant] += $count;
             } else {
                 $this->variantsusecounts[$seed][$variant] = $count;
             }
         }
     }
 }
예제 #2
0
 /**
  * Run this test against a particular question.
  * @param question_usage_by_activity $quba the useage to use when running the test.
  * @param qtype_stack_question $question the question to test.
  * @param int $seed the random seed to use.
  * @return stack_question_test_result the test results.
  */
 public function test_question(question_usage_by_activity $quba, qtype_stack_question $question, $seed)
 {
     $slot = $quba->add_question($question, $question->defaultmark);
     $quba->start_question($slot, $seed);
     $response = self::compute_response($question, $this->inputs);
     $quba->process_action($slot, $response);
     $results = new stack_question_test_result($this);
     foreach ($this->inputs as $inputname => $notused) {
         $inputstate = $question->get_input_state($inputname, $response);
         // The _val below is a hack.  Not all inputnames exist explicitly in
         // the response, but the _val does. Some inputs, e.g. matrices have
         // many entries in the response so none match $response[$inputname].
         if (array_key_exists($inputname, $response)) {
             $inputresponse = $response[$inputname];
         } else {
             $inputresponse = $response[$inputname . '_val'];
         }
         $results->set_input_state($inputname, $inputresponse, $inputstate->contentsdisplayed, $inputstate->status, $inputstate->errors);
     }
     foreach ($this->expectedresults as $prtname => $expectedresult) {
         $result = $question->get_prt_result($prtname, $response, false);
         $results->set_prt_result($prtname, $result);
     }
     return $results;
 }
 /**
  * Save a {@link question_usage_by_activity} to the database. This works either
  * if the usage was newly created by {@link make_questions_usage_by_activity()}
  * or loaded from the database using {@link load_questions_usage_by_activity()}
  * @param question_usage_by_activity the usage to save.
  */
 public static function save_questions_usage_by_activity(question_usage_by_activity $quba)
 {
     $dm = new question_engine_data_mapper();
     $observer = $quba->get_observer();
     if ($observer instanceof question_engine_unit_of_work) {
         $observer->save($dm);
     } else {
         $dm->insert_questions_usage_by_activity($quba);
     }
 }
예제 #4
0
 protected function setup_initial_test_state($testdata)
 {
     $records = new question_test_recordset($testdata);
     $this->quba = question_usage_by_activity::load_from_records($records, 1);
     $this->slot = 1;
     $this->observer = new testable_question_engine_unit_of_work($this->quba);
     $this->quba->set_observer($this->observer);
 }
 /**
  * Closes the attempt
  *
  * @param \mod_activequiz\activequiz $rtq
  *
  * @return bool Weather or not it was successful
  */
 public function close_attempt($rtq)
 {
     $this->quba->finish_all_questions(time());
     $this->attempt->status = self::FINISHED;
     $this->attempt->timefinish = time();
     $this->save();
     $params = array('objectid' => $this->attempt->id, 'context' => $rtq->getContext(), 'relateduserid' => $this->attempt->userid);
     $event = \mod_activequiz\event\attempt_ended::create($params);
     $event->add_record_snapshot('activequiz_attempts', $this->attempt);
     $event->trigger();
     return true;
 }
예제 #6
0
 protected function get_contains_select_expectation($name, $choices, $selected = null, $enabled = null)
 {
     $fullname = $this->quba->get_field_prefix($this->slot) . $name;
     return new question_contains_select_expectation($fullname, $choices, $selected, $enabled);
 }
예제 #7
0
파일: datalib.php 프로젝트: covex-nn/moodle
 /**
  * Update a question_usages row to refect any changes in a usage (but not
  * any of its question_attempts.
  * @param question_usage_by_activity $quba the usage that has changed.
  */
 public function update_questions_usage_by_activity(question_usage_by_activity $quba)
 {
     $record = new stdClass();
     $record->id = $quba->get_id();
     $record->contextid = $quba->get_owning_context()->id;
     $record->component = $quba->get_owning_component();
     $record->preferredbehaviour = $quba->get_preferred_behaviour();
     $this->db->update_record('question_usages', $record);
 }
예제 #8
0
 /**
  * To create an instance of this class, use
  * {@link question_usage_by_activity::get_attempt_iterator()}.
  * @param $quba the usage to iterate over.
  */
 public function __construct(question_usage_by_activity $quba) {
     $this->quba = $quba;
     $this->slots = $quba->get_slots();
     $this->rewind();
 }
예제 #9
0
파일: lib.php 프로젝트: mongo0se/moodle
 /**
  * Helper method for preparing the $forcedchoices array.
  * @param array                      $variantsbyslot slot number => variant to select.
  * @param question_usage_by_activity $quba           the question usage we need a strategy for.
  * @throws coding_exception when variant cannot be forced as doesn't work.
  * @return array that can be passed to the constructor as $forcedchoices.
  */
 public static function prepare_forced_choices_array(array $variantsbyslot, question_usage_by_activity $quba)
 {
     $forcedchoices = array();
     foreach ($variantsbyslot as $slot => $varianttochoose) {
         $question = $quba->get_question($slot);
         $seed = $question->get_variants_selection_seed();
         if (array_key_exists($seed, $forcedchoices) && $forcedchoices[$seed] != $varianttochoose) {
             throw new coding_exception('Inconsistent forced variant detected at slot ' . $slot);
         }
         if ($varianttochoose > $question->get_num_variants()) {
             throw new coding_exception('Forced variant out of range at slot ' . $slot);
         }
         $forcedchoices[$seed] = $varianttochoose;
     }
     return $forcedchoices;
 }
예제 #10
0
    public function test_load() {
        $records = new question_test_recordset(array(
        array('qubaid', 'contextid', 'component', 'preferredbehaviour',
                                               'questionattemptid', 'contextid', 'questionusageid', 'slot',
                                                              'behaviour', 'questionid', 'variant', 'maxmark', 'minfraction', 'flagged',
                                                                                                             'questionsummary', 'rightanswer', 'responsesummary', 'timemodified',
                                                                                                                                     'attemptstepid', 'sequencenumber', 'state', 'fraction',
                                                                                                                                                                     'timecreated', 'userid', 'name', 'value'),
        array(1, 1, 'unit_test', 'interactive', 1, 123, 1, 1, 'interactive', -1, 1, 2.0000000, 0.0000000, 0, '', '', '', 1256233790, 1, 0, 'todo',             null, 1256233700, 1,       null, null),
        array(1, 1, 'unit_test', 'interactive', 1, 123, 1, 1, 'interactive', -1, 1, 2.0000000, 0.0000000, 0, '', '', '', 1256233790, 2, 1, 'todo',             null, 1256233705, 1,   'answer',  '1'),
        array(1, 1, 'unit_test', 'interactive', 1, 123, 1, 1, 'interactive', -1, 1, 2.0000000, 0.0000000, 0, '', '', '', 1256233790, 5, 2, 'gradedright', 1.0000000, 1256233720, 1,  '-finish',  '1'),
        ));

        $question = test_question_maker::make_question('truefalse', 'true');
        $question->id = -1;

        question_bank::start_unit_test();
        question_bank::load_test_question_data($question);
        $quba = question_usage_by_activity::load_from_records($records, 1);
        question_bank::end_unit_test();

        $this->assertEquals('unit_test', $quba->get_owning_component());
        $this->assertEquals(1, $quba->get_id());
        $this->assertInstanceOf('question_engine_unit_of_work', $quba->get_observer());
        $this->assertEquals('interactive', $quba->get_preferred_behaviour());

        $qa = $quba->get_question_attempt(1);

        $this->assertEquals($question->questiontext, $qa->get_question()->questiontext);

        $this->assertEquals(3, $qa->get_num_steps());

        $step = $qa->get_step(0);
        $this->assertEquals(question_state::$todo, $step->get_state());
        $this->assertNull($step->get_fraction());
        $this->assertEquals(1256233700, $step->get_timecreated());
        $this->assertEquals(1, $step->get_user_id());
        $this->assertEquals(array(), $step->get_all_data());

        $step = $qa->get_step(1);
        $this->assertEquals(question_state::$todo, $step->get_state());
        $this->assertNull($step->get_fraction());
        $this->assertEquals(1256233705, $step->get_timecreated());
        $this->assertEquals(1, $step->get_user_id());
        $this->assertEquals(array('answer' => '1'), $step->get_all_data());

        $step = $qa->get_step(2);
        $this->assertEquals(question_state::$gradedright, $step->get_state());
        $this->assertEquals(1, $step->get_fraction());
        $this->assertEquals(1256233720, $step->get_timecreated());
        $this->assertEquals(1, $step->get_user_id());
        $this->assertEquals(array('-finish' => '1'), $step->get_all_data());
    }
예제 #11
0
파일: locallib.php 프로젝트: rwijaya/moodle
/**
 * Start a subsequent new attempt, in each attempt builds on last mode.
 *
 * @param question_usage_by_activity    $quba         this question usage
 * @param object                        $attempt      this attempt
 * @param object                        $lastattempt  last attempt
 * @return object                       modified attempt object
 *
 */
function quiz_start_attempt_built_on_last($quba, $attempt, $lastattempt) {
    $oldquba = question_engine::load_questions_usage_by_activity($lastattempt->uniqueid);

    $oldnumberstonew = array();
    foreach ($oldquba->get_attempt_iterator() as $oldslot => $oldqa) {
        $newslot = $quba->add_question($oldqa->get_question(), $oldqa->get_max_mark());

        $quba->start_question_based_on($newslot, $oldqa);

        $oldnumberstonew[$oldslot] = $newslot;
    }

    // Update attempt layout.
    $newlayout = array();
    foreach (explode(',', $lastattempt->layout) as $oldslot) {
        if ($oldslot != 0) {
            $newlayout[] = $oldnumberstonew[$oldslot];
        } else {
            $newlayout[] = 0;
        }
    }
    $attempt->layout = implode(',', $newlayout);
    return $attempt;
}
예제 #12
0
/**
 * Counts the multichoice question in a questionusage.
 *
 * @param question_usage_by_activity $questionusage
 * @return number
 */
function offlinequiz_count_multichoice_questions(question_usage_by_activity $questionusage)
{
    $count = 0;
    $slots = $questionusage->get_slots();
    foreach ($slots as $slot) {
        $question = $questionusage->get_question($slot);
        if ($question->qtype->name() == 'multichoice' || $question->qtype->name() == 'multichoiceset') {
            $count++;
        }
    }
    return $count;
}
 /**
  * add the questions to the question usage
  * This is called by the question_attmept class on construct of a new attempt
  *
  * @param \question_usage_by_activity $quba
  *
  * @return array
  */
 public function add_questions_to_quba(\question_usage_by_activity $quba)
 {
     // we need the questionids of our questions
     $questionids = array();
     foreach ($this->qbankOrderedQuestions as $qbankquestion) {
         /** @var activequiz_question $qbankquestion */
         if (!in_array($qbankquestion->getQuestion()->id, $questionids)) {
             $questionids[] = $qbankquestion->getQuestion()->id;
         }
     }
     $questions = question_load_questions($questionids);
     // loop through the ordered question bank questions and add them to the quba
     // object
     $attemptlayout = array();
     foreach ($this->qbankOrderedQuestions as $qbankquestion) {
         $questionid = $qbankquestion->getQuestion()->id;
         $q = \question_bank::make_question($questions[$questionid]);
         $attemptlayout[$qbankquestion->getId()] = $quba->add_question($q, $qbankquestion->getPoints());
     }
     // start the questions in the quba
     $quba->start_all_questions();
     /**
      * return the attempt layout which is a set of ids that are the slot ids from the question engine usage by activity instance
      * these are what are used during an actual attempt rather than the questionid themselves, since the question engine will handle
      * the translation
      */
     return $attemptlayout;
 }
예제 #14
0
    public function test_load_data_no_qas() {
        // The code had a bug where if a question_usage had no question_attempts,
        // load_from_records got stuck in an infinite loop. This test is to
        // verify that no longer happens.
        $scid = context_system::instance()->id;
        $records = new question_test_recordset(array(
        array('qubaid', 'contextid', 'component', 'preferredbehaviour',
                                                   'questionattemptid', 'questionusageid', 'slot',
                                                                        'behaviour', 'questionid', 'variant', 'maxmark', 'minfraction', 'flagged',
                                                                                                               'questionsummary', 'rightanswer', 'responsesummary', 'timemodified',
                                                                                                                                         'attemptstepid', 'sequencenumber', 'state', 'fraction',
                                                                                                                                                                   'timecreated', 'userid', 'name', 'value'),
        array(1, $scid, 'unit_test', 'interactive', null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null),
        ));

        question_bank::start_unit_test();
        $quba = question_usage_by_activity::load_from_records($records, 1);
        question_bank::end_unit_test();

        $this->assertEquals('unit_test', $quba->get_owning_component());
        $this->assertEquals(1, $quba->get_id());
        $this->assertInstanceOf('question_engine_unit_of_work', $quba->get_observer());
        $this->assertEquals('interactive', $quba->get_preferred_behaviour());

        $this->assertEquals(array(), $quba->get_slots());
    }
/**
 * Generates the LaTeX question form for an offlinequiz group.
 *
 * @param question_usage_by_activity $templateusage the template question  usage for this offline group
 * @param object $offlinequiz The offlinequiz object
 * @param object $group the offline group object
 * @param int $courseid the ID of the Moodle course
 * @param object $context the context of the offline quiz.
 * @param boolean correction if true the correction form is generated.
 * @return stored_file instance, the generated PDF file.
 */
function offlinequiz_create_latex_question(question_usage_by_activity $templateusage, $offlinequiz, $group, $courseid, $context, $correction = false)
{
    global $CFG, $DB, $OUTPUT;
    $letterstr = 'abcdefghijklmnopqrstuvwxyz';
    $groupletter = strtoupper($letterstr[$group->number - 1]);
    $coursecontext = context_course::instance($courseid);
    $course = $DB->get_record('course', array('id' => $courseid));
    $title = format_text($offlinequiz->name, FORMAT_HTML);
    $title .= ",  " . get_string('group') . $groupletter;
    // Load all the questions needed for this offline quiz group.
    $sql = "SELECT q.*, c.contextid, ogq.page, ogq.slot, ogq.maxmark\n              FROM {offlinequiz_group_questions} ogq,\n                   {question} q,\n                   {question_categories} c\n             WHERE ogq.offlinequizid = :offlinequizid\n               AND ogq.offlinegroupid = :offlinegroupid\n               AND q.id = ogq.questionid\n               AND q.category = c.id\n          ORDER BY ogq.slot ASC ";
    $params = array('offlinequizid' => $offlinequiz->id, 'offlinegroupid' => $group->id);
    // Load the questions.
    $questions = $DB->get_records_sql($sql, $params);
    if (!$questions) {
        echo $OUTPUT->box_start();
        echo $OUTPUT->error_text(get_string('noquestionsfound', 'offlinequiz', $groupletter));
        echo $OUTPUT->box_end();
        return;
    }
    // Load the question type specific information.
    if (!get_question_options($questions)) {
        print_error('Could not load question options');
    }
    $number = 1;
    // We need a mapping from question IDs to slots, assuming that each question occurs only once.
    $slots = $templateusage->get_slots();
    $latexforquestions = '\\begin{enumerate}' . "\n";
    // If shufflequestions has been activated we go through the questions in the order determined by
    // the template question usage.
    if ($offlinequiz->shufflequestions) {
        foreach ($slots as $slot) {
            $slotquestion = $templateusage->get_question($slot);
            $currentquestionid = $slotquestion->id;
            $question = $questions[$currentquestionid];
            $questiontext = offlinequiz_convert_html_to_latex($question->questiontext);
            $latexforquestions .= '\\item ' . $questiontext . "\n";
            if ($question->qtype == 'multichoice' || $question->qtype == 'multichoiceset') {
                // There is only a slot for multichoice questions.
                $attempt = $templateusage->get_question_attempt($slot);
                $order = $slotquestion->get_order($attempt);
                // Order of the answers.
                $latexforquestions .= '\\begin{enumerate}' . " \n";
                foreach ($order as $key => $answer) {
                    $latexforquestions .= offlinequiz_get_answer_latex($question, $answer);
                }
                $latexforquestions .= '\\end{enumerate}' . "\n";
                $infostr = offlinequiz_get_question_infostring($offlinequiz, $question);
                if ($infostr) {
                    $latexforquestions .= $infostr . "\n";
                }
            }
        }
        $latexforquestions .= '\\end{enumerate}' . "\n";
    } else {
        // No shufflequestions, so go through the questions as they have been added to the offlinequiz group.
        // We also have to show description questions that are not in the template.
        // First, compute mapping  questionid -> slotnumber.
        $questionslots = array();
        foreach ($slots as $slot) {
            $questionslots[$templateusage->get_question($slot)->id] = $slot;
        }
        foreach ($questions as $question) {
            $currentquestionid = $question->id;
            $questiontext = $question->questiontext;
            $questiontext = offlinequiz_convert_html_to_latex($question->questiontext);
            if ($question->qtype == 'description') {
                $latexforquestions .= "\n" . '\\ ' . $questiontext . "\n";
            } else {
                $latexforquestions .= '\\item ' . $questiontext . "\n";
            }
            if ($question->qtype == 'multichoice' || $question->qtype == 'multichoiceset') {
                $slot = $questionslots[$currentquestionid];
                // There is only a slot for multichoice questions.
                $slotquestion = $templateusage->get_question($slot);
                $attempt = $templateusage->get_question_attempt($slot);
                $order = $slotquestion->get_order($attempt);
                // Order of the answers.
                $latexforquestions .= '\\begin{enumerate}' . " \n";
                foreach ($order as $key => $answer) {
                    $latexforquestions .= offlinequiz_get_answer_latex($question, $answer);
                }
                $latexforquestions .= '\\end{enumerate}' . "\n";
                $infostr = offlinequiz_get_question_infostring($offlinequiz, $question);
                if ($infostr) {
                    $latexforquestions .= $infostr . "\n";
                }
            }
        }
        $latexforquestions .= '\\end{enumerate}' . "\n";
    }
    $a = array();
    $a['latexforquestions'] = $latexforquestions;
    $a['coursename'] = offlinequiz_convert_html_to_latex($course->fullname);
    $a['groupname'] = $groupletter;
    // TODO exceptionhandling?
    if ($offlinequiz->time) {
        $a['date'] = ', ' . userdate($offlinequiz->time);
    } else {
        $a['date'] = '';
    }
    $latex = get_string('questionsheetlatextemplate', 'offlinequiz', $a);
    $fs = get_file_storage();
    $fileprefix = get_string('fileprefixform', 'offlinequiz');
    // Prepare file record object.
    $date = usergetdate(time());
    $timestamp = sprintf('%04d%02d%02d_%02d%02d%02d', $date['year'], $date['mon'], $date['mday'], $date['hours'], $date['minutes'], $date['seconds']);
    $fileinfo = array('contextid' => $context->id, 'component' => 'mod_offlinequiz', 'filearea' => 'pdfs', 'filepath' => '/', 'itemid' => 0, 'filename' => $fileprefix . '_' . $groupletter . '_' . $timestamp . '.tex');
    if ($oldfile = $fs->get_file($fileinfo['contextid'], $fileinfo['component'], $fileinfo['filearea'], $fileinfo['itemid'], $fileinfo['filepath'], $fileinfo['filename'])) {
        $oldfile->delete();
    }
    $file = $fs->create_file_from_string($fileinfo, $latex);
    return $file;
}
예제 #16
0
/**
 * Generates the PDF question/correction form for an offlinequiz group.
 *
 * @param question_usage_by_activity $templateusage the template question  usage for this offline group
 * @param object $offlinequiz The offlinequiz object
 * @param object $group the offline group object
 * @param int $courseid the ID of the Moodle course
 * @param object $context the context of the offline quiz.
 * @param boolean correction if true the correction form is generated.
 * @return stored_file instance, the generated PDF file.
 */
function offlinequiz_create_pdf_question(question_usage_by_activity $templateusage, $offlinequiz, $group, $courseid, $context, $correction = false)
{
    global $CFG, $DB, $OUTPUT;
    $letterstr = 'abcdefghijklmnopqrstuvwxyz';
    $groupletter = strtoupper($letterstr[$group->number - 1]);
    $coursecontext = context_course::instance($courseid);
    $pdf = new offlinequiz_question_pdf('P', 'mm', 'A4');
    $trans = new offlinequiz_html_translator();
    $title = offlinequiz_str_html_pdf($offlinequiz->name);
    if (!empty($offlinequiz->time)) {
        $title .= ": " . offlinequiz_str_html_pdf(userdate($offlinequiz->time));
    }
    $title .= ",  " . offlinequiz_str_html_pdf(get_string('group') . " {$groupletter}");
    $pdf->set_title($title);
    $pdf->SetMargins(15, 28, 15);
    $pdf->SetAutoPageBreak(false, 25);
    $pdf->AddPage();
    // Print title page.
    $pdf->SetFont('FreeSans', 'B', 14);
    $pdf->Ln(4);
    if (!$correction) {
        $pdf->Cell(0, 4, offlinequiz_str_html_pdf(get_string('questionsheet', 'offlinequiz')), 0, 0, 'C');
        $pdf->Rect(34, 46, 137, 53, 'D');
        $pdf->SetFont('FreeSans', '', 10);
        // Line breaks to position name string etc. properly.
        $pdf->Ln(20);
        $pdf->Cell(58, 10, offlinequiz_str_html_pdf(get_string('name')) . ":", 0, 0, 'R');
        $pdf->Rect(76, 60, 80, 0.3, 'F');
        $pdf->Ln(10);
        $pdf->Cell(58, 10, offlinequiz_str_html_pdf(get_string('idnumber', 'offlinequiz')) . ":", 0, 0, 'R');
        $pdf->Rect(76, 70, 80, 0.3, 'F');
        $pdf->Ln(10);
        $pdf->Cell(58, 10, offlinequiz_str_html_pdf(get_string('studycode', 'offlinequiz')) . ":", 0, 0, 'R');
        $pdf->Rect(76, 80, 80, 0.3, 'F');
        $pdf->Ln(10);
        $pdf->Cell(58, 10, offlinequiz_str_html_pdf(get_string('signature', 'offlinequiz')) . ":", 0, 0, 'R');
        $pdf->Rect(76, 90, 80, 0.3, 'F');
        $pdf->Ln(33);
        $pdf->SetFont('FreeSans', '', $offlinequiz->fontsize);
        $pdf->SetFontSize($offlinequiz->fontsize);
        // The PDF intro text can be arbitrarily long so we have to catch page overflows.
        if (!empty($offlinequiz->pdfintro)) {
            $oldx = $pdf->GetX();
            $oldy = $pdf->GetY();
            $pdf->checkpoint();
            $pdf->writeHTMLCell(165, round($offlinequiz->fontsize / 2), $pdf->GetX(), $pdf->GetY(), $offlinequiz->pdfintro);
            $pdf->Ln();
            if ($pdf->is_overflowing()) {
                $pdf->backtrack();
                $pdf->SetX($oldx);
                $pdf->SetY($oldy);
                $paragraphs = preg_split('/<p>/', $offlinequiz->pdfintro);
                foreach ($paragraphs as $paragraph) {
                    if (!empty($paragraph)) {
                        $sentences = preg_split('/<br\\s*\\/>/', $paragraph);
                        foreach ($sentences as $sentence) {
                            $pdf->checkpoint();
                            $pdf->writeHTMLCell(165, round($offlinequiz->fontsize / 2), $pdf->GetX(), $pdf->GetY(), $sentence . '<br/>');
                            $pdf->Ln();
                            if ($pdf->is_overflowing()) {
                                $pdf->backtrack();
                                $pdf->AddPage();
                                $pdf->Ln(14);
                                $pdf->writeHTMLCell(165, round($offlinequiz->fontsize / 2), $pdf->GetX(), $pdf->GetY(), $sentence);
                                $pdf->Ln();
                            }
                        }
                    }
                }
            }
        }
        $pdf->AddPage();
        $pdf->Ln(2);
    }
    $pdf->SetMargins(15, 15, 15);
    // Load all the questions needed for this offline quiz group.
    $sql = "SELECT q.*, c.contextid, ogq.page, ogq.slot, ogq.maxmark \n              FROM {offlinequiz_group_questions} ogq,\n                   {question} q,\n                   {question_categories} c\n             WHERE ogq.offlinequizid = :offlinequizid\n               AND ogq.offlinegroupid = :offlinegroupid\n               AND q.id = ogq.questionid\n               AND q.category = c.id\n          ORDER BY ogq.slot ASC ";
    $params = array('offlinequizid' => $offlinequiz->id, 'offlinegroupid' => $group->id);
    // Load the questions.
    $questions = $DB->get_records_sql($sql, $params);
    if (!$questions) {
        echo $OUTPUT->box_start();
        echo $OUTPUT->error_text(get_string('noquestionsfound', 'offlinequiz', $groupletter));
        echo $OUTPUT->box_end();
        return;
    }
    // Load the question type specific information.
    if (!get_question_options($questions)) {
        print_error('Could not load question options');
    }
    // Restore the question sessions to their most recent states.
    // Creating new sessions where required.
    $number = 1;
    // We need a mapping from question IDs to slots, assuming that each question occurs only once.
    $slots = $templateusage->get_slots();
    $texfilter = new filter_tex($context, array());
    // If shufflequestions has been activated we go through the questions in the order determined by
    // the template question usage.
    if ($offlinequiz->shufflequestions) {
        foreach ($slots as $slot) {
            $slotquestion = $templateusage->get_question($slot);
            $currentquestionid = $slotquestion->id;
            // Add page break if necessary because of overflow.
            if ($pdf->GetY() > 230) {
                $pdf->AddPage();
                $pdf->Ln(14);
            }
            set_time_limit(120);
            $question = $questions[$currentquestionid];
            /*****************************************************/
            /*  Either we print the question HTML */
            /*****************************************************/
            $pdf->checkpoint();
            $questiontext = $question->questiontext;
            // Filter only for tex formulas.
            if (!empty($texfilter)) {
                $questiontext = $texfilter->filter($questiontext);
            }
            // Remove all HTML comments (typically from MS Office).
            $questiontext = preg_replace("/<!--.*?--\\s*>/ms", "", $questiontext);
            // Remove <font> tags.
            $questiontext = preg_replace("/<font[^>]*>[^<]*<\\/font>/ms", "", $questiontext);
            // Remove <script> tags that are created by mathjax preview.
            $questiontext = preg_replace("/<script[^>]*>[^<]*<\\/script>/ms", "", $questiontext);
            // Remove all class info from paragraphs because TCPDF won't use CSS.
            $questiontext = preg_replace('/<p[^>]+class="[^"]*"[^>]*>/i', "<p>", $questiontext);
            $questiontext = $trans->fix_image_paths($questiontext, $question->contextid, 'questiontext', $question->id, 1, 300);
            $html = '';
            $html .= $questiontext . '<br/><br/>';
            if ($question->qtype == 'multichoice' || $question->qtype == 'multichoiceset') {
                // Save the usage slot in the group questions table.
                //                 $DB->set_field('offlinequiz_group_questions', 'usageslot', $slot,
                //                         array('offlinequizid' => $offlinequiz->id,
                //                                 'offlinegroupid' => $group->id, 'questionid' => $question->id));
                // There is only a slot for multichoice questions.
                $attempt = $templateusage->get_question_attempt($slot);
                $order = $slotquestion->get_order($attempt);
                // Order of the answers.
                foreach ($order as $key => $answer) {
                    $answertext = $question->options->answers[$answer]->answer;
                    // Filter only for tex formulas.
                    if (!empty($texfilter)) {
                        $answertext = $texfilter->filter($answertext);
                    }
                    // Remove all HTML comments (typically from MS Office).
                    $answertext = preg_replace("/<!--.*?--\\s*>/ms", "", $answertext);
                    // Remove all paragraph tags because they mess up the layout.
                    $answertext = preg_replace("/<p[^>]*>/ms", "", $answertext);
                    // Remove <script> tags that are created by mathjax preview.
                    $answertext = preg_replace("/<script[^>]*>[^<]*<\\/script>/ms", "", $answertext);
                    $answertext = preg_replace("/<\\/p[^>]*>/ms", "", $answertext);
                    $answertext = $trans->fix_image_paths($answertext, $question->contextid, 'answer', $answer, 1, 300);
                    if ($correction) {
                        if ($question->options->answers[$answer]->fraction > 0) {
                            $html .= '<b>';
                        }
                        $answertext .= " (" . round($question->options->answers[$answer]->fraction * 100) . "%)";
                    }
                    $html .= number_in_style($key, $question->options->answernumbering) . ') &nbsp; ';
                    $html .= $answertext;
                    if ($correction) {
                        if ($question->options->answers[$answer]->fraction > 0) {
                            $html .= '</b>';
                        }
                    }
                    $html .= "<br/>\n";
                }
                if ($offlinequiz->showgrades) {
                    $pointstr = get_string('points', 'grades');
                    if ($question->maxmark == 1) {
                        $pointstr = get_string('point', 'offlinequiz');
                    }
                    $html .= '<br/>(' . ($question->maxmark + 0) . ' ' . $pointstr . ')<br/>';
                }
            }
            // Finally print the question number and the HTML string.
            if ($question->qtype == 'multichoice' || $question->qtype == 'multichoiceset') {
                $pdf->SetFont('FreeSans', 'B', $offlinequiz->fontsize);
                $pdf->Cell(4, round($offlinequiz->fontsize / 2), "{$number})  ", 0, 0, 'R');
                $pdf->SetFont('FreeSans', '', $offlinequiz->fontsize);
            }
            $pdf->writeHTMLCell(165, round($offlinequiz->fontsize / 2), $pdf->GetX(), $pdf->GetY() + 0.3, $html);
            $pdf->Ln();
            if ($pdf->is_overflowing()) {
                $pdf->backtrack();
                $pdf->AddPage();
                $pdf->Ln(14);
                // Print the question number and the HTML string again on the new page.
                if ($question->qtype == 'multichoice' || $question->qtype == 'multichoiceset') {
                    $pdf->SetFont('FreeSans', 'B', $offlinequiz->fontsize);
                    $pdf->Cell(4, round($offlinequiz->fontsize / 2), "{$number})  ", 0, 0, 'R');
                    $pdf->SetFont('FreeSans', '', $offlinequiz->fontsize);
                }
                $pdf->writeHTMLCell(165, round($offlinequiz->fontsize / 2), $pdf->GetX(), $pdf->GetY() + 0.3, $html);
                $pdf->Ln();
            }
            $number += $questions[$currentquestionid]->length;
        }
    } else {
        // No shufflequestions, so go through the questions as they have been added to the offlinequiz group.
        // We also have to show description questions that are not in the template.
        // First, compute mapping  questionid -> slotnumber.
        $questionslots = array();
        foreach ($slots as $slot) {
            $questionslots[$templateusage->get_question($slot)->id] = $slot;
        }
        $currentpage = 1;
        foreach ($questions as $question) {
            $currentquestionid = $question->id;
            // Add page break if set explicitely by teacher.
            if ($question->page > $currentpage) {
                $pdf->AddPage();
                $pdf->Ln(14);
                $currentpage++;
            }
            // Add page break if necessary because of overflow.
            if ($pdf->GetY() > 230) {
                $pdf->AddPage();
                $pdf->Ln(14);
            }
            set_time_limit(120);
            /**
             * **************************************************
             * either we print the question HTML 
             * **************************************************
             */
            $pdf->checkpoint();
            $questiontext = $question->questiontext;
            // Filter only for tex formulas.
            if (!empty($texfilter)) {
                $questiontext = $texfilter->filter($questiontext);
            }
            // Remove all HTML comments (typically from MS Office).
            $questiontext = preg_replace("/<!--.*?--\\s*>/ms", "", $questiontext);
            // Remove <font> tags.
            $questiontext = preg_replace("/<font[^>]*>[^<]*<\\/font>/ms", "", $questiontext);
            // Remove <script> tags that are created by mathjax preview.
            $questiontext = preg_replace("/<script[^>]*>[^<]*<\\/script>/ms", "", $questiontext);
            // Remove all class info from paragraphs because TCPDF won't use CSS.
            $questiontext = preg_replace('/<p[^>]+class="[^"]*"[^>]*>/i', "<p>", $questiontext);
            $questiontext = $trans->fix_image_paths($questiontext, $question->contextid, 'questiontext', $question->id, 1, 300);
            $html = '';
            $html .= $questiontext . '<br/><br/>';
            if ($question->qtype == 'multichoice' || $question->qtype == 'multichoiceset') {
                $slot = $questionslots[$currentquestionid];
                // Save the usage slot in the group questions table.
                // $DB->set_field('offlinequiz_group_questions', 'usageslot', $slot,
                // array('offlinequizid' => $offlinequiz->id,
                // 'offlinegroupid' => $group->id, 'questionid' => $question->id));
                // There is only a slot for multichoice questions.
                $slotquestion = $templateusage->get_question($slot);
                $attempt = $templateusage->get_question_attempt($slot);
                $order = $slotquestion->get_order($attempt);
                // Order of the answers.
                foreach ($order as $key => $answer) {
                    $answertext = $question->options->answers[$answer]->answer;
                    // Filter only for tex formulas.
                    if (!empty($texfilter)) {
                        $answertext = $texfilter->filter($answertext);
                    }
                    // Remove all HTML comments (typically from MS Office).
                    $answertext = preg_replace("/<!--.*?--\\s*>/ms", "", $answertext);
                    // Remove all paragraph tags because they mess up the layout.
                    $answertext = preg_replace("/<p[^>]*>/ms", "", $answertext);
                    // Remove <script> tags that are created by mathjax preview.
                    $answertext = preg_replace("/<script[^>]*>[^<]*<\\/script>/ms", "", $answertext);
                    $answertext = preg_replace("/<\\/p[^>]*>/ms", "", $answertext);
                    $answertext = $trans->fix_image_paths($answertext, $question->contextid, 'answer', $answer, 1, 300);
                    // Was $pdf->GetK()).
                    if ($correction) {
                        if ($question->options->answers[$answer]->fraction > 0) {
                            $html .= '<b>';
                        }
                        $answertext .= " (" . round($question->options->answers[$answer]->fraction * 100) . "%)";
                    }
                    $html .= number_in_style($key, $question->options->answernumbering) . ') &nbsp; ';
                    $html .= $answertext;
                    if ($correction) {
                        if ($question->options->answers[$answer]->fraction > 0) {
                            $html .= '</b>';
                        }
                    }
                    $html .= "<br/>\n";
                }
                if ($offlinequiz->showgrades) {
                    $pointstr = get_string('points', 'grades');
                    if ($question->maxmark == 1) {
                        $pointstr = get_string('point', 'offlinequiz');
                    }
                    $html .= '<br/>(' . ($question->maxmark + 0) . ' ' . $pointstr . ')<br/>';
                }
            }
            // Finally print the question number and the HTML string.
            if ($question->qtype == 'multichoice' || $question->qtype == 'multichoiceset') {
                $pdf->SetFont('FreeSans', 'B', $offlinequiz->fontsize);
                $pdf->Cell(4, round($offlinequiz->fontsize / 2), "{$number})  ", 0, 0, 'R');
                $pdf->SetFont('FreeSans', '', $offlinequiz->fontsize);
            }
            $pdf->writeHTMLCell(165, round($offlinequiz->fontsize / 2), $pdf->GetX(), $pdf->GetY() + 0.3, $html);
            $pdf->Ln();
            if ($pdf->is_overflowing()) {
                $pdf->backtrack();
                $pdf->AddPage();
                $pdf->Ln(14);
                // Print the question number and the HTML string again on the new page.
                if ($question->qtype == 'multichoice' || $question->qtype == 'multichoiceset') {
                    $pdf->SetFont('FreeSans', 'B', $offlinequiz->fontsize);
                    $pdf->Cell(4, round($offlinequiz->fontsize / 2), "{$number})  ", 0, 0, 'R');
                    $pdf->SetFont('FreeSans', '', $offlinequiz->fontsize);
                }
                $pdf->writeHTMLCell(165, round($offlinequiz->fontsize / 2), $pdf->GetX(), $pdf->GetY() + 0.3, $html);
                $pdf->Ln();
            }
            $number += $questions[$currentquestionid]->length;
        }
    }
    $fs = get_file_storage();
    $fileprefix = 'form';
    if ($correction) {
        $fileprefix = 'correction';
    }
    // Prepare file record object.
    $timestamp = date('Ymd_His', time());
    $fileinfo = array('contextid' => $context->id, 'component' => 'mod_offlinequiz', 'filearea' => 'pdfs', 'filepath' => '/', 'itemid' => 0, 'filename' => $fileprefix . '-' . strtolower($groupletter) . '_' . $timestamp . '.pdf');
    if ($oldfile = $fs->get_file($fileinfo['contextid'], $fileinfo['component'], $fileinfo['filearea'], $fileinfo['itemid'], $fileinfo['filepath'], $fileinfo['filename'])) {
        $oldfile->delete();
    }
    $pdfstring = $pdf->Output('', 'S');
    $file = $fs->create_file_from_string($fileinfo, $pdfstring);
    $trans->remove_temp_files();
    return $file;
}
예제 #17
0
/**
 * Generates the DOCX question/correction form for an offlinequiz group.
 *
 * @param question_usage_by_activity $templateusage the template question  usage for this offline group
 * @param object $offlinequiz The offlinequiz object
 * @param object $group the offline group object
 * @param int $courseid the ID of the Moodle course
 * @param object $context the context of the offline quiz.
 * @param boolean correction if true the correction form is generated.
 * @return stored_file instance, the generated DOCX file.
 */
function offlinequiz_create_docx_question(question_usage_by_activity $templateusage, $offlinequiz, $group, $courseid, $context, $correction = false)
{
    global $CFG, $DB, $OUTPUT;
    $letterstr = 'abcdefghijklmnopqrstuvwxyz';
    $groupletter = strtoupper($letterstr[$group->number - 1]);
    $coursecontext = context_course::instance($courseid);
    PHPWord_Media::resetMedia();
    $docx = new PHPWord();
    $trans = new offlinequiz_html_translator();
    // Define cell style arrays.
    $cellstyle = array('valign' => 'center');
    // Add text styles.
    // Normal style.
    $docx->addFontStyle('nStyle', array('size' => $offlinequiz->fontsize));
    // Italic style.
    $docx->addFontStyle('iStyle', array('italic' => true, 'size' => $offlinequiz->fontsize));
    // Bold style.
    $docx->addFontStyle('bStyle', array('bold' => true, 'size' => $offlinequiz->fontsize));
    $docx->addFontStyle('brStyle', array('bold' => true, 'align' => 'right', 'size' => $offlinequiz->fontsize));
    // Underline style.
    $docx->addFontStyle('uStyle', array('underline' => PHPWord_Style_Font::UNDERLINE_SINGLE, 'size' => $offlinequiz->fontsize));
    $docx->addFontStyle('ibStyle', array('italic' => true, 'bold' => true, 'size' => $offlinequiz->fontsize));
    $docx->addFontStyle('iuStyle', array('italic' => true, 'underline' => PHPWord_Style_Font::UNDERLINE_SINGLE, 'size' => $offlinequiz->fontsize));
    $docx->addFontStyle('buStyle', array('bold' => true, 'underline' => PHPWord_Style_Font::UNDERLINE_SINGLE, 'size' => $offlinequiz->fontsize));
    $docx->addFontStyle('ibuStyle', array('italic' => true, 'bold' => true, 'underline' => PHPWord_Style_Font::UNDERLINE_SINGLE, 'size' => $offlinequiz->fontsize));
    // Header style.
    $docx->addFontStyle('hStyle', array('bold' => true, 'size' => $offlinequiz->fontsize + 4));
    // Center style.
    $docx->addParagraphStyle('cStyle', array('align' => 'center', 'spaceAfter' => 100));
    $docx->addParagraphStyle('cStyle', array('align' => 'center', 'spaceAfter' => 100));
    $docx->addParagraphStyle('questionTab', array('tabs' => array(new PHPWord_Style_Tab("left", 360))));
    // Define table style arrays.
    $tablestyle = array('borderSize' => 0, 'borderColor' => 'FFFFFF', 'cellMargin' => 20, 'align' => 'center');
    $firstrowstyle = array('borderBottomSize' => 0, 'borderBottomColor' => 'FFFFFF', 'bgColor' => 'FFFFFF');
    $docx->addTableStyle('tableStyle', $tablestyle, $firstrowstyle);
    $boldfont = new PHPWord_Style_Font();
    $boldfont->setBold(true);
    $boldfont->setSize($offlinequiz->fontsize);
    $normalfont = new PHPWord_Style_Font();
    $normalfont->setSize($offlinequiz->fontsize);
    // Define custom list item style for question answers.
    $level1 = new PHPWord_Style_Paragraph();
    $level1->setTabs(new PHPWord_Style_Tabs(array(new PHPWord_Style_Tab('clear', 720), new PHPWord_Style_Tab('num', 360))));
    $level1->setIndentions(new PHPWord_Style_Indentation(array('left' => 360, 'hanging' => 360)));
    $level2 = new PHPWord_Style_Paragraph();
    $level2->setTabs(new PHPWord_Style_Tabs(array(new PHPWord_Style_Tab('left', 720), new PHPWord_Style_Tab('num', 720))));
    $level2->setIndentions(new PHPWord_Style_Indentation(array('left' => 720, 'hanging' => 360)));
    // Create the section that will be used for all outputs.
    $section = $docx->createSection();
    $title = offlinequiz_str_html_docx($offlinequiz->name);
    if (!empty($offlinequiz->time)) {
        if (strlen($title) > 35) {
            $title = substr($title, 0, 33) . ' ...';
        }
        $title .= ": " . offlinequiz_str_html_docx(userdate($offlinequiz->time));
    } else {
        if (strlen($title) > 40) {
            $title = substr($title, 0, 37) . ' ...';
        }
    }
    $title .= ",  " . offlinequiz_str_html_docx(get_string('group') . " {$groupletter}");
    // Add a header.
    $header = $section->createHeader();
    $header->addText($title, array('size' => 10), 'cStyle');
    $header->addImage($CFG->dirroot . '/mod/offlinequiz/pix/line.png', array('width' => 600, 'height' => 5, 'align' => 'center'));
    // Add a footer.
    $footer = $section->createFooter();
    $footer->addImage($CFG->dirroot . '/mod/offlinequiz/pix/line.png', array('width' => 600, 'height' => 5, 'align' => 'center'));
    $footer->addPreserveText($title . '  |  ' . get_string('page') . ' ' . '{PAGE} / {NUMPAGES}', null, array('align' => 'left'));
    // Print title page.
    if (!$correction) {
        $section->addText(offlinequiz_str_html_docx(get_string('questionsheet', 'offlinequiz') . ' - ' . get_string('group') . " {$groupletter}"), 'hStyle', 'cStyle');
        $section->addTextBreak(2);
        $table = $section->addTable('tableStyle');
        $table->addRow();
        $cell = $table->addCell(200, $cellstyle)->addText(offlinequiz_str_html_docx(get_string('name')) . ':  ', 'brStyle');
        $table->addRow();
        $cell = $table->addCell(200, $cellstyle)->addText(offlinequiz_str_html_docx(get_string('idnumber', 'offlinequiz')) . ':  ', 'brStyle');
        $table->addRow();
        $cell = $table->addCell(200, $cellstyle)->addText(offlinequiz_str_html_docx(get_string('studycode', 'offlinequiz')) . ':  ', 'brStyle');
        $table->addRow();
        $cell = $table->addCell(200, $cellstyle)->addText(offlinequiz_str_html_docx(get_string('signature', 'offlinequiz')) . ':  ', 'brStyle');
        $section->addTextBreak(2);
        // The DOCX intro text can be arbitrarily long so we have to catch page overflows.
        if (!empty($offlinequiz->pdfintro)) {
            $blocks = offlinequiz_convert_image_docx($offlinequiz->pdfintro);
            offlinequiz_print_blocks_docx($section, $blocks);
        }
        $section->addPageBreak();
    }
    // Load all the questions needed for this offline quiz group.
    $sql = "SELECT q.*, c.contextid, ogq.page, ogq.slot, ogq.maxmark \n              FROM {offlinequiz_group_questions} ogq,\n                   {question} q,\n                   {question_categories} c\n             WHERE ogq.offlinequizid = :offlinequizid\n               AND ogq.offlinegroupid = :offlinegroupid\n               AND q.id = ogq.questionid\n               AND q.category = c.id\n          ORDER BY ogq.slot ASC ";
    $params = array('offlinequizid' => $offlinequiz->id, 'offlinegroupid' => $group->id);
    // Load the questions.
    $questions = $DB->get_records_sql($sql, $params);
    if (!$questions) {
        echo $OUTPUT->box_start();
        echo $OUTPUT->error_text(get_string('noquestionsfound', 'offlinequiz', $groupletter));
        echo $OUTPUT->box_end();
        return;
    }
    // Load the question type specific information.
    if (!get_question_options($questions)) {
        print_error('Could not load question options');
    }
    // Restore the question sessions to their most recent states.
    // Creating new sessions where required.
    $number = 1;
    // We need a mapping from question IDs to slots, assuming that each question occurs only once.
    $slots = $templateusage->get_slots();
    $texfilter = new filter_tex($context, array());
    // Create the docx question numbering. This is only created once since we number all questions from 1...n.
    $questionnumbering = new PHPWord_Numbering_AbstractNumbering("Question-level", array(new PHPWord_Numbering_Level("1", PHPWord_Numbering_Level::NUMFMT_DECIMAL, "%1)", "left", $level1, $boldfont), new PHPWord_Numbering_Level("1", PHPWord_Numbering_Level::NUMFMT_LOWER_LETTER, "%2)", "left", $level2, $normalfont)));
    $docx->addNumbering($questionnumbering);
    // If shufflequestions has been activated we go through the questions in the order determined by
    // the template question usage.
    if ($offlinequiz->shufflequestions) {
        foreach ($slots as $slot) {
            $slotquestion = $templateusage->get_question($slot);
            $myquestion = $slotquestion->id;
            set_time_limit(120);
            $question = $questions[$myquestion];
            // Either we print the question HTML.
            $questiontext = $question->questiontext;
            // Filter only for tex formulas.
            if (!empty($texfilter)) {
                $questiontext = $texfilter->filter($questiontext);
            }
            // Remove all HTML comments (typically from MS Office).
            $questiontext = preg_replace("/<!--.*?--\\s*>/ms", "", $questiontext);
            // Remove <font> tags.
            $questiontext = preg_replace("/<font[^>]*>[^<]*<\\/font>/ms", "", $questiontext);
            // Remove <script> tags that are created by mathjax preview.
            $questiontext = preg_replace("/<script[^>]*>[^<]*<\\/script>/ms", "", $questiontext);
            // Remove all class info from paragraphs because TCDOCX won't use CSS.
            $questiontext = preg_replace('/<p[^>]+class="[^"]*"[^>]*>/i', "<p>", $questiontext);
            $questiontext = $trans->fix_image_paths($questiontext, $question->contextid, 'questiontext', $question->id, 0.6, 300, 'docx');
            $blocks = offlinequiz_convert_image_docx($questiontext);
            offlinequiz_print_blocks_docx($section, $blocks, $questionnumbering, 0);
            $answernumbering = new PHPWord_Numbering_AbstractNumbering("Adv Multi-level", array(new PHPWord_Numbering_Level("1", PHPWord_Numbering_Level::NUMFMT_DECIMAL, "%1.", "left", $level1, $boldfont), new PHPWord_Numbering_Level("1", PHPWord_Numbering_Level::NUMFMT_LOWER_LETTER, "%2)", "left", $level2, $normalfont)));
            $docx->addNumbering($answernumbering);
            if ($question->qtype == 'multichoice' || $question->qtype == 'multichoiceset') {
                // Save the usage slot in the group questions table.
                //                 $DB->set_field('offlinequiz_group_questions', 'usageslot', $slot,
                //                         array('offlinequizid' => $offlinequiz->id,
                //                                 'offlinegroupid' => $group->id, 'questionid' => $question->id));
                // There is only a slot for multichoice questions.
                $attempt = $templateusage->get_question_attempt($slot);
                $order = $slotquestion->get_order($attempt);
                // Order of the answers.
                foreach ($order as $key => $answer) {
                    $answertext = $question->options->answers[$answer]->answer;
                    // Filter only for tex formulas.
                    if (!empty($texfilter)) {
                        $answertext = $texfilter->filter($answertext);
                    }
                    // Remove all HTML comments (typically from MS Office).
                    $answertext = preg_replace("/<!--.*?--\\s*>/ms", "", $answertext);
                    // Remove all paragraph tags because they mess up the layout.
                    $answertext = preg_replace("/<p[^>]*>/ms", "", $answertext);
                    // Remove <script> tags that are created by mathjax preview.
                    $answertext = preg_replace("/<script[^>]*>[^<]*<\\/script>/ms", "", $answertext);
                    $answertext = preg_replace("/<\\/p[^>]*>/ms", "", $answertext);
                    $answertext = $trans->fix_image_paths($answertext, $question->contextid, 'answer', $answer, 0.6, 200, 'docx');
                    $blocks = offlinequiz_convert_image_docx($answertext);
                    offlinequiz_print_blocks_docx($section, $blocks, $answernumbering, 1);
                }
                if ($offlinequiz->showgrades) {
                    $pointstr = get_string('points', 'grades');
                    if ($question->maxgrade == 1) {
                        $pointstr = get_string('point', 'offlinequiz');
                    }
                    // Indent the question grade like the answers.
                    $textrun = $section->createTextRun($level2);
                    $textrun->addText('(' . ($question->maxgrade + 0) . ' ' . $pointstr . ')', 'bStyle');
                }
            }
            $section->addTextBreak();
            $number++;
        }
    } else {
        // Not shufflequestions.
        // We have to compute the mapping  questionid -> slotnumber.
        $questionslots = array();
        foreach ($slots as $slot) {
            $questionslots[$templateusage->get_question($slot)->id] = $slot;
        }
        // No shufflequestions, so go through the questions as they have been added to the offlinequiz group
        // We also add custom page breaks.
        $currentpage = 1;
        foreach ($questions as $question) {
            // Add page break if set explicitely by teacher.
            if ($question->page > $currentpage) {
                $section->addPageBreak();
                $currentpage++;
            }
            set_time_limit(120);
            // Print the question.
            $questiontext = $question->questiontext;
            // Filter only for tex formulas.
            if (!empty($texfilter)) {
                $questiontext = $texfilter->filter($questiontext);
            }
            // Remove all HTML comments (typically from MS Office).
            $questiontext = preg_replace("/<!--.*?--\\s*>/ms", "", $questiontext);
            // Remove <font> tags.
            $questiontext = preg_replace("/<font[^>]*>[^<]*<\\/font>/ms", "", $questiontext);
            // Remove <script> tags that are created by mathjax preview.
            $questiontext = preg_replace("/<script[^>]*>[^<]*<\\/script>/ms", "", $questiontext);
            // Remove all class info from paragraphs because TCDOCX won't use CSS.
            $questiontext = preg_replace('/<p[^>]+class="[^"]*"[^>]*>/i', "<p>", $questiontext);
            $questiontext = $trans->fix_image_paths($questiontext, $question->contextid, 'questiontext', $question->id, 0.6, 300, 'docx');
            $blocks = offlinequiz_convert_image_docx($questiontext);
            // Description questions are printed without a number because they are not on the answer form.
            if ($question->qtype == 'description') {
                offlinequiz_print_blocks_docx($section, $blocks);
            } else {
                offlinequiz_print_blocks_docx($section, $blocks, $questionnumbering, 0);
            }
            $answernumbering = new PHPWord_Numbering_AbstractNumbering("Adv Multi-level", array(new PHPWord_Numbering_Level("1", PHPWord_Numbering_Level::NUMFMT_DECIMAL, "%1.", "left", $level1), new PHPWord_Numbering_Level("1", PHPWord_Numbering_Level::NUMFMT_LOWER_LETTER, "%2)", "left", $level2)));
            $docx->addNumbering($answernumbering);
            if ($question->qtype == 'multichoice' || $question->qtype == 'multichoiceset') {
                $slot = $questionslots[$question->id];
                // Save the usage slot in the group questions table.
                //                 $DB->set_field('offlinequiz_group_questions', 'usageslot', $slot,
                //                         array('offlinequizid' => $offlinequiz->id,
                //                                 'offlinegroupid' => $group->id, 'questionid' => $question->id));
                // Now retrieve the order of the answers.
                $slotquestion = $templateusage->get_question($slot);
                $attempt = $templateusage->get_question_attempt($slot);
                $order = $slotquestion->get_order($attempt);
                // Order of the answers.
                foreach ($order as $key => $answer) {
                    $answertext = $question->options->answers[$answer]->answer;
                    // Filter only for tex formulas.
                    if (!empty($texfilter)) {
                        $answertext = $texfilter->filter($answertext);
                    }
                    // Remove all HTML comments (typically from MS Office).
                    $answertext = preg_replace("/<!--.*?--\\s*>/ms", "", $answertext);
                    // Remove all paragraph tags because they mess up the layout.
                    $answertext = preg_replace("/<p[^>]*>/ms", "", $answertext);
                    // Remove <script> tags that are created by mathjax preview.
                    $answertext = preg_replace("/<script[^>]*>[^<]*<\\/script>/ms", "", $answertext);
                    $answertext = preg_replace("/<\\/p[^>]*>/ms", "", $answertext);
                    $answertext = $trans->fix_image_paths($answertext, $question->contextid, 'answer', $answer, 0.6, 200, 'docx');
                    $blocks = offlinequiz_convert_image_docx($answertext);
                    offlinequiz_print_blocks_docx($section, $blocks, $answernumbering, 1);
                }
                if ($offlinequiz->showgrades) {
                    $pointstr = get_string('points', 'grades');
                    if ($question->maxgrade == 1) {
                        $pointstr = get_string('point', 'offlinequiz');
                    }
                    // Indent the question grade like the answers.
                    $textrun = $section->createTextRun($level2);
                    $textrun->addText('(' . ($question->maxgrade + 0) . ' ' . $pointstr . ')', 'bStyle');
                }
                $section->addTextBreak();
                $number++;
                // End if multichoice.
            }
        }
        // End forall questions.
    }
    // End else no shufflequestions.
    $fs = get_file_storage();
    $fileprefix = 'form';
    if ($correction) {
        $fileprefix = 'correction';
    }
    srand(microtime() * 1000000);
    $unique = str_replace('.', '', microtime(true) . rand(0, 100000));
    $tempfilename = $CFG->dataroot . '/temp/offlinequiz/' . $unique . '.docx';
    check_dir_exists($CFG->dataroot . '/temp/offlinequiz', true, true);
    if (file_exists($tempfilename)) {
        unlink($tempfilename);
    }
    // Save file.
    $objwriter = PHPWord_IOFactory::createWriter($docx, 'Word2007');
    $objwriter->save($tempfilename);
    // Prepare file record object.
    $timestamp = date('Ymd_His', time());
    $fileinfo = array('contextid' => $context->id, 'component' => 'mod_offlinequiz', 'filearea' => 'pdfs', 'filepath' => '/', 'itemid' => 0, 'filename' => $fileprefix . '-' . strtolower($groupletter) . '_' . $timestamp . '.docx');
    // Delete existing old files, should actually not happen.
    if ($oldfile = $fs->get_file($fileinfo['contextid'], $fileinfo['component'], $fileinfo['filearea'], $fileinfo['itemid'], $fileinfo['filepath'], $fileinfo['filename'])) {
        $oldfile->delete();
    }
    // Create a Moodle file from the temporary file.
    $file = $fs->create_file_from_pathname($fileinfo, $tempfilename);
    // Remove all temporary files.
    unlink($tempfilename);
    $trans->remove_temp_files();
    return $file;
}