Пример #1
0
/**
 * transform all entities of $string to their hexadecimal representation
 *
 * @param $string string original string to entify
 * @param $quote_style string Constant choosen among ENT_COMPAT, ENT_QUOTES OR ENT_NOQUOTES (see claro_htmlentities doc for more info)
 *
 * @return string entified string
 */
function xmlentities($string, $quote_style = ENT_QUOTES)
{
    static $trans;
    // remove all html entities before xml encoding
    // must convert all quotes to avoid remaining html entity in code
    $string = claro_html_entity_decode($string, ENT_QUOTES);
    // xml encoding
    if (!isset($trans)) {
        $trans = get_html_translation_table(HTML_ENTITIES, $quote_style);
        foreach (array_keys($trans) as $key) {
            $trans[$key] = '&#' . ord($key) . ';';
        }
        // dont translate the '&' in case it is part of &xxx;
        $trans[chr(38)] = '&';
    }
    // after the initial translation, _do_ map standalone '&' into '&'
    return preg_replace("/&(?![A-Za-z]{0,4}\\w{2,3};|#[0-9]{2,3};)/", "&", strtr($string, $trans));
}
Пример #2
0
function _entityDecode($text)
{
    return claro_html_entity_decode(strtr($text, array(''' => '\'')), ENT_QUOTES, 'UTF-8');
}
Пример #3
0
/**
 * Returns decoded utf-8 $str. No changes are made if it was not utf-8
 *
 */
function claro_utf8_decode($str, $toCharset = '')
{
    if ($toCharset != '') {
        $charset = $toCharset;
    } else {
        $charset = $GLOBALS['charset'];
    }
    if (strtoupper($charset) == 'UTF-8' || !seems_utf8($str)) {
        return $str;
    } elseif (strtoupper($charset) == 'ISO-8859-1') {
        return utf8_decode($str);
    } elseif (function_exists('mb_convert_encoding')) {
        return mb_convert_encoding($str, 'UTF-8', $charset);
    } elseif (function_exists('iconv')) {
        return iconv('UTF-8', $charset . '//TRANSLIT', $str);
    } else {
        $converted = claro_htmlentities($str, ENT_NOQUOTES, 'UTF-8');
        return claro_html_entity_decode($converted, ENT_NOQUOTES, $charset);
    }
}
Пример #4
0
 /**
  * Reverse function for claro_htmlentities. 
  * Convert entities in UTF-8.
  * @param $text_to_convert Text to convert.
  * @return string converted
  * @access public
  */
 public function unhtmlentities($text_to_convert)
 {
     return claro_html_entity_decode($text_to_convert, ENT_QUOTES, $this->encoding);
 }
Пример #5
0
    /**
     * Create files (quiz) needed in the export of this module
     *
     * @copyright   (c) 2001-2011, Universite catholique de Louvain (UCL)
     * @param int $quizId id of the Quiz
     * @param object $item item of the path
     * @param string $destDir path when the files need to be copied
     * @param int $deepness deepness of the destinationd directory
     * @return boolean
     */
    public function prepareFiles($quizId, &$item, $destDir, $deepness)
    {
        $completionThresold = $item->getCompletionThreshold();
        if (empty($completionThresold)) {
            $completionThresold = 50;
        }
        $quizId = (int) $quizId;
        $quiz = new Exercise();
        if (!$quiz->load($quizId)) {
            $this->error[] = get_lang('Unable to load the exercise');
            return false;
        }
        $deep = '';
        if ($deepness) {
            for ($i = $deepness; $i > 0; $i--) {
                $deep .= ' ../';
            }
        }
        // Generate standard page header
        $pageHeader = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html>
    <head>
    <title>' . $quiz->getTitle() . '</title>
    <meta http-equiv="expires" content="Tue, 05 DEC 2000 07:00:00 GMT">
    <meta http-equiv="Pragma" content="no-cache">
    <meta http-equiv="Content-Type" content="text/HTML; charset=' . get_locale('charset') . '"  />

    <link rel="stylesheet" type="text/css" href="' . $deep . get_conf('claro_stylesheet') . '/main.css" media="screen, projection, tv" />
    <script language="javascript" type="text/javascript" src="' . $deep . 'js/jquery.js"></script>
    <script language="javascript" type="text/javascript" src="' . $deep . 'js/claroline.js"></script>
    <script language="javascript" type="text/javascript" src="' . $deep . 'js/claroline.ui.js"></script>

    <script language="javascript" type="text/javascript" src="' . $deep . 'js/APIWrapper.js"></script>
    <script language="javascript" type="text/javascript" src="' . $deep . 'js/connector13.js"></script>
    <script language="javascript" type="text/javascript" src="' . $deep . 'js/scores.js"></script>
    </head>
    ' . "\n";
        $pageBody = '<body onload="loadPage()">
    <div id="claroBody"><form id="quiz">
    <table width="100%" border="0" cellpadding="1" cellspacing="0" class="claroTable">' . "\n";
        // Get the question list
        $questionList = $quiz->getQuestionList();
        $questionCount = count($questionList);
        // Keep track of raw scores (ponderation) for each question
        $questionPonderationList = array();
        // Keep track of correct texts for fill-in type questions
        // TODO La variable $fillAnswerList n'apparaît qu'une fois
        $fillAnswerList = array();
        // Display each question
        $questionCount = 0;
        foreach ($questionList as $question) {
            // Update question number
            $questionCount++;
            // read the question, abort on error
            $scormQuestion = new ScormQuestion();
            if (!$scormQuestion->load($question['id'])) {
                $this->error[] = get_lang('Unable to load exercise\'s question');
                return false;
            }
            $questionPonderationList[] = $scormQuestion->getGrade();
            $pageBody .= '<thead>' . "\n" . '<tr>' . "\n" . '<th>' . get_lang('Question') . ' ' . $questionCount . '</th>' . "\n" . '</tr>' . "\n" . '</thead>' . "\n";
            $pageBody .= '<tr>' . "\n" . '<td>' . "\n" . $scormQuestion->export() . "\n" . '</td>' . "\n" . '</tr>' . "\n";
        }
        $pageEnd = '
    <tr>
        <td align="center"><br /><input type="button" value="' . get_lang('Ok') . '" onclick="calcScore()" /></td>
    </tr>
    </table>
    </form>
    </div></body></html>' . "\n";
        /* Generate the javascript that'll calculate the score
         * We have the following variables to help us :
         * $idCounter : number of elements to check. their id are "scorm_XY"
         * $raw_to_pass : score (on 100) needed to pass the quiz
         * $fillAnswerList : a list of arrays (text, score) indexed on <input>'s names
         *
         */
        $pageHeader .= '
    <script type="text/javascript" language="javascript">
        var raw_to_pass = ' . $completionThresold . ';
        var weighting = ' . array_sum($questionPonderationList) . ';
        var rawScore;
        var scoreCommited = false;
        var showScore = true;
        var fillAnswerList = new Array();' . "\n";
        // This is the actual code present in every exported exercise.
        // use claro_html_entity_decode in output to prevent double encoding errors with some languages...
        $pageHeader .= '

        function calcScore()
        {
            if( !scoreCommited )
            {
                rawScore = CalculateRawScore(document, ' . getIdCounter() . ', fillAnswerList);
                var score = Math.max(Math.round(rawScore * 100 / weighting), 0);
                var oldScore = doLMSGetValue("cmi.score.raw");
    
                doLMSSetValue("cmi.score.max", weighting);
                doLMSSetValue("cmi.score.min", 0);
    
                computeTime();
    
                if (score > oldScore) // Update only if score is better than the previous time.
                {
                    doLMSSetValue("cmi.raw", rawScore);
                }
                
                var oldStatus = doLMSGetValue( "cmi.completion_status" )
                if (score >= raw_to_pass)
                {
                    doLMSSetValue("cmi.completion_status", "completed");
                }
                else if (oldStatus != "completed" ) // If passed once, never mark it as failed.
                {
                    doLMSSetValue("cmi.completion_status", "failed");
                }
    
                doLMSCommit();
                doLMSFinish();
                scoreCommited = true;
                if(showScore) alert(\'' . clean_str_for_javascript(claro_html_entity_decode(get_lang('Score'))) . ' :\\n\' + rawScore + \'/\' + weighting );
            }
        }
    
    </script>
    ';
        // Construct the HTML file and save it.
        $filename = "quiz_" . $quizId . ".html";
        $pageContent = $pageHeader . $pageBody . $pageEnd;
        if (!($f = fopen($destDir . '/' . $filename, 'w'))) {
            $this->error = get_lang('Unable to create file : ') . $filename;
            return false;
        }
        fwrite($f, $pageContent);
        fclose($f);
        return true;
    }
Пример #6
0
 /**
  * Parse and execute wiki2xhtml macros
  *
  *  enabled macros are :
  *      - """start_html"""  : start of html block, the block begins on the next line
  *      - """end_html"""    : end of html block
  *      - """home""" or """main""" : link to Main page
  *      - default           : the surrounded string is interpreted as embedded html
  * @access private
  * @see class.wiki2xhtml.php
  * @return string macro execution result
  */
 protected function parseMacro($str, &$tag, &$attr, &$type)
 {
     $tag = '';
     $attr = '';
     $trimmedStr = trim($str, '"');
     $matches = array();
     if (preg_match('/^color([0-9])/', $trimmedStr, $matches)) {
         $colorCodeList = array(0 => '#DD0000', 1 => '#006600', 2 => '#0000DD', 3 => '#660099', 4 => '#008888', 5 => '#55AA22', 6 => '#888800', 7 => '#DE8822', 8 => '#804020', 9 => '#990022');
         $colorCode = isset($colorCodeList[(int) $matches[1]]) ? $colorCodeList[(int) $matches[1]] : '#000000';
         $trimmedStr = 'color';
     } elseif (preg_match('/^color\\(([a-zA-Z]+|#[a-fA-F0-9]{3}|#[a-fA-F0-9]{6})\\)/', $trimmedStr, $matches)) {
         $colorCode = $matches[1];
         $trimmedStr = 'color';
     }
     switch ($trimmedStr) {
         // start of html block
         case 'start_html':
             $this->setOpt('inline_html_allowed', 1);
             $str = "<!-- start of embedded html -->\n";
             break;
             // end of html block
         // end of html block
         case 'end_html':
             $this->setOpt('inline_html_allowed', 0);
             $str = "<!-- start of embedded html -->\n";
             break;
             // link to main page
         // link to main page
         case 'home':
         case 'main':
             $str = "<a href=\"" . $_SERVER['PHP_SELF'] . "?action=show&title=" . rawurlencode('__MainPage__') . "&wikiId=" . $this->wiki->getWikiId() . "\" class=\"wikiShow\">" . get_lang('Main page') . "</a>";
             break;
             // toc
         // toc
         case 'toc':
             $str = '';
             $this->addAtEnd[] = '<script type="text/javascript" src="./js/toc.js"></script>';
             $this->addAtEnd[] = '<script type="text/javascript">createTOC();</script>';
             break;
         case 'color':
             $str = '<span style="color: ' . $colorCode . ';">';
             break;
         case '/color':
             $str = '</span>';
             break;
             // embedded html
         // embedded html
         default:
             // FIXME secure embedded html !!!
             // - remove dangerous tags and attributes
             // - protect against XSS
             $str = trim($str, '"');
             $str = claro_html_entity_decode($str);
             $str = $this->san->sanitize($str);
     }
     return $str;
 }
Пример #7
0
 /**
  *  Parse the manifest file given in argument
  *  @param   string manifestPath, path to the manifest file
  *  @return  bool false on failure, array moduleInfo on success
  */
 public function parse($manifestPath)
 {
     // reset state
     $this->elementPile = array();
     $this->moduleInfo = array();
     $this->backlog->info('Parsing manifest file ' . $manifestPath);
     if (!file_exists($manifestPath)) {
         $this->backlog->failure(get_lang('Manifest missing : %filename', array('%filename' => $manifestPath)));
         return false;
     }
     $xmlParser = xml_parser_create();
     xml_set_element_handler($xmlParser, array(&$this, 'startElement'), array(&$this, 'endElement'));
     xml_set_character_data_handler($xmlParser, array(&$this, 'elementData'));
     // read manifest file
     if (false === ($data = @file_get_contents($manifestPath))) {
         $this->backlog->failure(get_lang('Cannot open manifest file'));
         return false;
     } else {
         $this->backlog->debug('Manifest open : ' . $manifestPath);
         $data = claro_html_entity_decode(urldecode($data));
     }
     if (!xml_parse($xmlParser, $data)) {
         // if reading of the xml file in not successfull :
         // set errorFound, set error msg, break while statement
         $this->backlog->failure(get_lang('Error while parsing manifest'));
         return false;
     }
     // liberate parser ressources
     xml_parser_free($xmlParser);
     // complete module info for missing optional elements
     if (!array_key_exists('ENTRY', $this->moduleInfo)) {
         $this->moduleInfo['ENTRY'] = 'entry.php';
     }
     // Module License
     if (!array_key_exists('LICENSE', $this->moduleInfo)) {
         $this->moduleInfo['LICENSE'] = '';
     }
     // Module version
     if (!array_key_exists('VERSION', $this->moduleInfo)) {
         $this->moduleInfo['VERSION'] = '';
     }
     // Module description
     if (!array_key_exists('DESCRIPTION', $this->moduleInfo)) {
         $this->moduleInfo['DESCRIPTION'] = '';
     }
     // Author informations
     if (!array_key_exists('AUTHOR', $this->moduleInfo)) {
         $this->moduleInfo['AUTHOR'] = array();
     }
     // Author name
     if (!array_key_exists('NAME', $this->moduleInfo['AUTHOR'])) {
         $this->moduleInfo['AUTHOR']['NAME'] = '';
     }
     // Author email
     if (!array_key_exists('EMAIL', $this->moduleInfo['AUTHOR'])) {
         $this->moduleInfo['AUTHOR']['EMAIL'] = '';
     }
     // Author website
     if (!array_key_exists('WEB', $this->moduleInfo['AUTHOR'])) {
         $this->moduleInfo['AUTHOR']['WEB'] = '';
     }
     // Module website
     if (!array_key_exists('WEB', $this->moduleInfo)) {
         $this->moduleInfo['WEB'] = '';
     }
     // Set default module context
     if (!array_key_exists('CONTEXTS', $this->moduleInfo) || empty($this->moduleInfo['CONTEXTS'])) {
         if (strtoupper($this->moduleInfo['TYPE']) == 'TOOL') {
             $this->moduleInfo['CONTEXTS'] = array('course');
         } else {
             $this->moduleInfo['CONTEXTS'] = array('platform');
         }
     }
     return $this->moduleInfo;
 }
Пример #8
0
 $manifestData['scos'] = array();
 // for path of start asset id of each new module to create
 $xml_parser = xml_parser_create();
 xml_set_element_handler($xml_parser, "startElement", "endElement");
 xml_set_character_data_handler($xml_parser, "elementData");
 // this file has to exist in a SCORM conformant package
 // this file must be in the root the sent zip
 $file = "imsmanifest.xml";
 if (file_exists($file)) {
     if (!isset($manifestPath)) {
         $manifestPath = "";
     }
     array_push($okMsgs, get_lang('Manifest found in zip file : ') . $manifestPath . "imsmanifest.xml");
     $fileContent = file_get_contents($manifestPath . $file);
     $charset = get_xml_charset($fileContent);
     $data = claro_html_entity_decode(urldecode($fileContent), ENT_COMPAT, $charset);
     if (!xml_parse($xml_parser, $data)) {
         // if reading of the xml file in not successfull :
         // set errorFound, set error msg, break while statement
         $errorFound = true;
         array_push($errorMsgs, get_lang('Error reading <i>manifest</i> file'));
         if (claro_debug_mode()) {
             $debugMessage = strtr('Debug : %message (error code %code) on line %line and column %column', array('%message' => xml_error_string(xml_get_error_code($xml_parser)), '%code' => xml_get_error_code($xml_parser), '%line' => xml_get_current_line_number($xml_parser), '%column' => xml_get_current_column_number($xml_parser)));
             array_push($errorMsgs, $debugMessage);
         }
     }
 } else {
     $errorFound = true;
     array_push($errorMsgs, get_lang('Cannot find <i>manifest</i> file in the package.<br /> File not found : imsmanifest.xml'));
 }
 // liberate parser ressources
Пример #9
0
 /**
  * Evaluates the message and returns modifications for inline images and backgrounds
  * @access public
  * @return $message
  */
 public function MsgHTML($message, $basedir = '')
 {
     preg_match_all("/(src|background)=\"(.*)\"/Ui", $message, $images);
     if (isset($images[2])) {
         foreach ($images[2] as $i => $url) {
             // do not change urls for absolute images (thanks to corvuscorax)
             if (!preg_match('#^[A-z]+://#', $url)) {
                 $filename = basename($url);
                 $directory = dirname($url);
                 $directory == '.' ? $directory = '' : '';
                 $cid = 'cid:' . md5($filename);
                 $ext = pathinfo($filename, PATHINFO_EXTENSION);
                 $mimeType = self::_mime_types($ext);
                 if (strlen($basedir) > 1 && substr($basedir, -1) != '/') {
                     $basedir .= '/';
                 }
                 if (strlen($directory) > 1 && substr($directory, -1) != '/') {
                     $directory .= '/';
                 }
                 if ($this->AddEmbeddedImage($basedir . $directory . $filename, md5($filename), $filename, 'base64', $mimeType)) {
                     $message = preg_replace("/" . $images[1][$i] . "=\"" . preg_quote($url, '/') . "\"/Ui", $images[1][$i] . "=\"" . $cid . "\"", $message);
                 }
             }
         }
     }
     $this->IsHTML(true);
     $this->Body = $message;
     $textMsg = trim(strip_tags(preg_replace('/<(head|title|style|script)[^>]*>.*?<\\/\\1>/s', '', $message)));
     if (!empty($textMsg) && empty($this->AltBody)) {
         $this->AltBody = claro_html_entity_decode($textMsg);
     }
     if (empty($this->AltBody)) {
         $this->AltBody = 'To view this email message, open it in a program that understands HTML!' . "\n\n";
     }
 }
Пример #10
0
        /**
         * Exports an exercise as a SCO.
         * This method is intended to be called from the prepare method.
         *
         *@note There's a lot of nearly cut-and-paste from exercise.lib.php here
         *      because of some little differences...
         *      Perhaps something that could be refactorised ?
         *
         * @see prepare
         * @param $quizId The quiz
         * @param $raw_to_pass The needed score to attain
         * @return False on error, True if everything went well.
         * @author  Amand Tihon <amand@alrj.org>
         */
        public function prepareQuiz($quizId, $raw_to_pass = 50)
        {
            global $claro_stylesheet;
            // those two variables are needed by display_attached_file()
            global $attachedFilePathWeb;
            global $attachedFilePathSys;
            $attachedFilePathWeb = 'Exercises';
            $attachedFilePathSys = $this->destDir . '/Exercises';
            // read the exercise
            $quiz = new Exercise();
            if (!$quiz->load($quizId)) {
                $this->error[] = get_lang('Unable to load the exercise');
                return false;
            }
            // Generate standard page header
            $pageHeader = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <title>' . $quiz->getTitle() . '</title>
    <meta http-equiv="expires" content="Tue, 05 DEC 2000 07:00:00 GMT">
    <meta http-equiv="Pragma" content="no-cache">
    <meta http-equiv="Content-Type" content="text/HTML; charset=' . get_locale('charset') . '"  />

    <link rel="stylesheet" type="text/css" href="' . get_conf('claro_stylesheet') . '/main.css" media="screen, projection, tv" />
    <script language="javascript" type="text/javascript" src="jquery.js"></script>
    <script language="javascript" type="text/javascript" src="claroline.js"></script>
    <script language="javascript" type="text/javascript" src="claroline.ui.js"></script>

    <script language="javascript" type="text/javascript" src="APIWrapper.js"></script>
    <script language="javascript" type="text/javascript" src="scores.js"></script>
    ' . "\n";
            $pageBody = '<body onload="loadPage()">
        <div id="claroBody"><form id="quiz">
        <table width="100%" border="0" cellpadding="1" cellspacing="0" class="claroTable">' . "\n";
            // Get the question list
            $questionList = $quiz->getQuestionList();
            $questionCount = count($questionList);
            // Keep track of raw scores (ponderation) for each question
            $questionPonderationList = array();
            // Keep track of correct texts for fill-in type questions
            // TODO La variable $fillAnswerList n'appara�t qu'une fois
            $fillAnswerList = array();
            // Display each question
            $questionCount = 0;
            foreach ($questionList as $question) {
                // Update question number
                $questionCount++;
                // read the question, abort on error
                $scormQuestion = new ScormQuestion();
                if (!$scormQuestion->load($question['id'])) {
                    $this->error[] = get_lang('Unable to load exercise\'s question');
                    return false;
                }
                $questionPonderationList[] = $scormQuestion->getGrade();
                $pageBody .= '<tr class="headerX">' . "\n" . '<th>' . get_lang('Question') . ' ' . $questionCount . '</th>' . "\n" . '</tr>' . "\n";
                $pageBody .= '<tr>' . "\n" . '<td>' . "\n" . $scormQuestion->export() . "\n" . '</td>' . "\n" . '</tr>' . "\n";
                /*
                                if( !empty($scormQuestion->getAttachment()) )
                                {
                                    // copy the attached file
                                    if ( !claro_copy_file($this->srcDirExercise . '/' . $attachedFile, $this->destDir . '/Exercises') )
                                    {
                                        $this->error[] = get_lang('Unable to copy file : %filename', array ( '%filename' => $attachedFile  ));
                                        return false;
                                    }
                
                                    // Ok, if it was an mp3, we need to copy the flash mp3-player too.
                                    $extension=substr(strrchr($attachedFile, '.'), 1);
                                    if ( $extension == 'mp3')   $this->mp3Found = true;
                
                                    $pageBody .= '<tr><td colspan="2">' . display_attached_file($attachedFile) . '</td></tr>' . "\n";
                                }
                */
                /*
                 * Display the possible answers
                 */
                // End of the question
            }
            // foreach($questionList as $questionId)
            // No more questions, add the button.
            $pageEnd = '
                <tr>
                    <td align="center"><br /><input type="button" value="' . get_lang('Ok') . '" onclick="calcScore()" /></td>
                </tr>
                </table>
                </form>
                </div></body></html>' . "\n";
            /* Generate the javascript that'll calculate the score
             * We have the following variables to help us :
             * $idCounter : number of elements to check. their id are "scorm_XY"
             * $raw_to_pass : score (on 100) needed to pass the quiz
             * $fillAnswerList : a list of arrays (text, score) indexed on <input>'s names
             *
             */
            $pageHeader .= '
    <script type="text/javascript" language="javascript">
        var raw_to_pass = ' . $raw_to_pass . ';
        var weighting = ' . array_sum($questionPonderationList) . ';
        var rawScore;
        var scoreCommited = false;
        var showScore = true;
        var fillAnswerList = new Array();' . "\n";
            // This is the actual code present in every exported exercise.
            // use claro_html_entity_decode in output to prevent double encoding errors with some languages...
            $pageHeader .= '

        function calcScore()
        {
            if( !scoreCommited )
            {
                rawScore = CalculateRawScore(document, ' . getIdCounter() . ', fillAnswerList);
                var score = Math.max(Math.round(rawScore * 100 / weighting), 0);
                var oldScore = doLMSGetValue("cmi.core.score.raw");

                doLMSSetValue("cmi.core.score.max", weighting);
                doLMSSetValue("cmi.core.score.min", 0);

                computeTime();

                if (score > oldScore) // Update only if score is better than the previous time.
                {
                    doLMSSetValue("cmi.core.score.raw", rawScore);
                }

                var mode = doLMSGetValue( "cmi.core.lesson_mode" );
                if ( mode != "review"  &&  mode != "browse" )
                {
                    var oldStatus = doLMSGetValue( "cmi.core.lesson_status" )
                    if (score >= raw_to_pass)
                    {
                        doLMSSetValue("cmi.core.lesson_status", "passed");
                    }
                    else if (oldStatus != "passed" ) // If passed once, never mark it as failed.
                    {
                        doLMSSetValue("cmi.core.lesson_status", "failed");
                    }
                }

                doLMSCommit();
                doLMSFinish();
                scoreCommited = true;
                if(showScore) alert(\'' . clean_str_for_javascript(claro_html_entity_decode(get_lang('Score'))) . ' :\\n\' + rawScore + \'/\' + weighting );
            }
        }
    
    </script>
    ';
            // Construct the HTML file and save it.
            $filename = "quiz_" . $quizId . ".html";
            $pageContent = $pageHeader . $pageBody . $pageEnd;
            if (!($f = fopen($this->destDir . '/' . $filename, 'w'))) {
                $this->error[] = get_lang('Unable to create file : ') . $filename;
                return false;
            }
            fwrite($f, $pageContent);
            fclose($f);
            // Went well.
            return True;
        }