/** * Prints a preview for a question in an offlinequiz to Stdout. * * @param object $question * @param array $choiceorder * @param int $number * @param object $context */ function offlinequiz_print_question_preview($question, $choiceorder, $number, $context, $page) { global $CFG, $DB; require_once $CFG->dirroot . '/filter/mathjaxloader/filter.php'; $letterstr = 'abcdefghijklmnopqrstuvwxyz'; echo '<div id="q' . $question->id . '" class="preview"> <div class="question"> <span class="number">'; if ($question->qtype != 'description') { echo $number . ') '; } echo ' </span>'; $text = question_rewrite_question_preview_urls($question->questiontext, $question->id, $question->contextid, 'question', 'questiontext', $question->id, $context->id, 'offlinequiz'); // Remove leading paragraph tags because the cause a line break after the question number. $text = preg_replace('!^<p>!i', '', $text); // Filter only for tex formulas. $texfilter = null; $mathjaxfilter = null; $filters = filter_get_active_in_context($context); if (array_key_exists('mathjaxloader', $filters)) { $mathjaxfilter = new filter_mathjaxloader($context, array()); $mathjaxfilter->setup($page, $context); } if (array_key_exists('tex', $filters)) { $texfilter = new filter_tex($context, array()); } if ($mathjaxfilter) { $text = $mathjaxfilter->filter($text); if ($question->qtype != 'description') { foreach ($choiceorder as $key => $answer) { $question->options->answers[$answer]->answer = $mathjaxfilter->filter($question->options->answers[$answer]->answer); } } } else { if ($texfilter) { $text = $texfilter->filter($text); if ($question->qtype != 'description') { foreach ($choiceorder as $key => $answer) { $question->options->answers[$answer]->answer = $texfilter->filter($question->options->answers[$answer]->answer); } } } } echo $text; echo ' </div>'; if ($question->qtype != 'description') { echo ' <div class="grade">'; echo '(' . get_string('marks', 'quiz') . ': ' . ($question->maxmark + 0) . ')'; echo ' </div>'; foreach ($choiceorder as $key => $answer) { $answertext = $question->options->answers[$answer]->answer; // 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); $answertext = preg_replace("/<\\/p[^>]*>/ms", "", $answertext); echo "<div class=\"answer\">{$letterstr[$key]}) "; echo $answertext; echo "</div>"; } } echo "</div>"; }
/** * 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) . ') '; $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) . ') '; $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; }
/** * 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; }