You are here

function qformat_webct::readquestions in Quiz 6.6

Same name and namespace in other branches
  1. 6.5 includes/moodle/question/format/webct/format.php \qformat_webct::readquestions()

Parses an array of lines into an array of questions, where each item is a question object as defined by readquestion(). Questions are defined as anything between blank lines.

If your format does not use blank lines as a delimiter then you will need to override this method. Even then try to use readquestion for each question

Parameters

array lines array of lines from readdata:

Return value

array array of question objects

Overrides qformat_default::readquestions

File

includes/moodle/question/format/webct/format.php, line 168

Class

qformat_webct

Code

function readquestions($lines) {
  global $QTYPES;

  //  $qtypecalculated = new qformat_webct_modified_calculated_qtype();
  $webctnumberregex = '[+-]?([0-9]+(\\.[0-9]*)?|\\.[0-9]+)((e|E|\\*10\\*\\*)([+-]?[0-9]+|\\([+-]?[0-9]+\\)))?';
  $questions = array();
  $errors = array();
  $warnings = array();
  $webct_options = array();
  $ignore_rest_of_question = FALSE;
  $nLineCounter = 0;
  $nQuestionStartLine = 0;
  $bIsHTMLText = FALSE;
  $lines[] = ":EOF:";

  // for an easiest processing of the last line
  //    $question = $this->defaultquestion();
  foreach ($lines as $line) {
    $nLineCounter++;
    $line = iconv("Windows-1252", "UTF-8", $line);

    // Processing multiples lines strings
    if (isset($questiontext) and is_string($questiontext)) {
      if (ereg("^:", $line)) {
        $question->questiontext = addslashes(trim($questiontext));
        unset($questiontext);
      }
      else {
        $questiontext .= str_replace('\\:', ':', $line);
        continue;
      }
    }
    if (isset($answertext) and is_string($answertext)) {
      if (ereg("^:", $line)) {
        $answertext = addslashes(trim($answertext));
        $question->answer[$currentchoice] = $answertext;
        $question->subanswers[$currentchoice] = $answertext;
        unset($answertext);
      }
      else {
        $answertext .= str_replace('\\:', ':', $line);
        continue;
      }
    }
    if (isset($responsetext) and is_string($responsetext)) {
      if (ereg("^:", $line)) {
        $question->subquestions[$currentchoice] = addslashes(trim($responsetext));
        unset($responsetext);
      }
      else {
        $responsetext .= str_replace('\\:', ':', $line);
        continue;
      }
    }
    if (isset($feedbacktext) and is_string($feedbacktext)) {
      if (ereg("^:", $line)) {
        $question->feedback[$currentchoice] = addslashes(trim($feedbacktext));
        unset($feedbacktext);
      }
      else {
        $feedbacktext .= str_replace('\\:', ':', $line);
        continue;
      }
    }
    if (isset($generalfeedbacktext) and is_string($generalfeedbacktext)) {
      if (ereg("^:", $line)) {
        $question->tempgeneralfeedback = addslashes(trim($generalfeedbacktext));
        unset($generalfeedbacktext);
      }
      else {
        $generalfeedbacktext .= str_replace('\\:', ':', $line);
        continue;
      }
    }
    $line = trim($line);
    if (eregi("^:(TYPE|EOF):", $line)) {

      // New Question or End of File
      if (isset($question)) {

        // if previous question exists, complete, check and save it
        // Setup default value of missing fields
        if (!isset($question->name)) {
          $question->name = $question->questiontext;
        }
        if (strlen($question->name) > 255) {
          $question->name = substr($question->name, 0, 250) . "...";
          $warnings[] = get_string("questionnametoolong", "quiz", $nQuestionStartLine);
        }
        if (!isset($question->defaultgrade)) {
          $question->defaultgrade = 1;
        }
        if (!isset($question->image)) {
          $question->image = "";
        }

        // Perform sanity checks
        $QuestionOK = TRUE;
        if (strlen($question->questiontext) == 0) {
          $warnings[] = get_string("missingquestion", "quiz", $nQuestionStartLine);
          $QuestionOK = FALSE;
        }
        if (sizeof($question->answer) < 1) {

          // a question must have at least 1 answer
          $errors[] = get_string("missinganswer", "quiz", $nQuestionStartLine);
          $QuestionOK = FALSE;
        }
        else {

          // Create empty feedback array
          foreach ($question->answer as $key => $dataanswer) {
            if (!isset($question->feedback[$key])) {
              $question->feedback[$key] = '';
            }
          }

          // this tempgeneralfeedback allows the code to work with versions from 1.6 to 1.9
          // when question->generalfeedback is undefined, the webct feedback is added to each answer feedback
          if (isset($question->tempgeneralfeedback)) {
            if (isset($question->generalfeedback)) {
              $question->generalfeedback = $question->tempgeneralfeedback;
            }
            else {
              foreach ($question->answer as $key => $dataanswer) {
                if ($question->tempgeneralfeedback != '') {
                  $question->feedback[$key] = $question->tempgeneralfeedback . '<br/>' . $question->feedback[$key];
                }
              }
            }
            unset($question->tempgeneralfeedback);
          }
          $maxfraction = -1;
          $totalfraction = 0;
          foreach ($question->fraction as $fraction) {
            if ($fraction > 0) {
              $totalfraction += $fraction;
            }
            if ($fraction > $maxfraction) {
              $maxfraction = $fraction;
            }
          }
          switch ($question->qtype) {
            case SHORTANSWER:
              if ($maxfraction != 1) {
                $maxfraction = $maxfraction * 100;
                $errors[] = "'{$question->name}': " . get_string("wronggrade", "quiz", $nLineCounter) . ' ' . get_string("fractionsnomax", "quiz", $maxfraction);
                $QuestionOK = FALSE;
              }
              break;
            case MULTICHOICE:
              if ($question->single) {
                if ($maxfraction != 1) {
                  $maxfraction = $maxfraction * 100;
                  $errors[] = "'{$question->name}': " . get_string("wronggrade", "quiz", $nLineCounter) . ' ' . get_string("fractionsnomax", "quiz", $maxfraction);
                  $QuestionOK = FALSE;
                }
              }
              else {
                $totalfraction = round($totalfraction, 2);
                if ($totalfraction != 1) {
                  $totalfraction = $totalfraction * 100;
                  $errors[] = "'{$question->name}': " . get_string("wronggrade", "quiz", $nLineCounter) . ' ' . get_string("fractionsaddwrong", "quiz", $totalfraction);
                  $QuestionOK = FALSE;
                }
              }
              break;
            case CALCULATED:
              foreach ($question->answers as $answer) {
                if ($formulaerror = qtype_calculated_find_formula_errors($answer)) {

                  //$QTYPES['calculated']->
                  $warnings[] = "'{$question->name}': " . $formulaerror;
                  $QuestionOK = FALSE;
                }
              }
              foreach ($question->dataset as $dataset) {
                $dataset->itemcount = count($dataset->datasetitem);
              }
              $question->import_process = TRUE;
              unset($question->answer);

              //not used in calculated question
              break;
            case MATCH:

              // MDL-10680:
              // switch subquestions and subanswers
              foreach ($question->subquestions as $id => $subquestion) {
                $temp = $question->subquestions[$id];
                $question->subquestions[$id] = $question->subanswers[$id];
                $question->subanswers[$id] = $temp;
              }
              if (count($question->answer) < 3) {

                // add a dummy missing question
                $question->name = 'Dummy question added ' . $question->name;
                $question->answer[] = 'dummy';
                $question->subanswers[] = 'dummy';
                $question->subquestions[] = 'dummy';
                $question->fraction[] = '0.0';
                $question->feedback[] = '';
              }
              break;
            default:
          }
        }
        if ($QuestionOK) {

          // echo "<pre>"; print_r ($question);
          $questions[] = $question;

          // store it
          unset($question);

          // and prepare a new one
          $question = $this
            ->defaultquestion();
        }
      }
      $nQuestionStartLine = $nLineCounter;
    }

    // Processing Question Header
    if (eregi("^:TYPE:MC:1(.*)", $line, $webct_options)) {

      // Multiple Choice Question with only one good answer
      $question = $this
        ->defaultquestion();
      $question->feedback = array();
      $question->qtype = MULTICHOICE;
      $question->single = 1;

      // Only one answer is allowed
      $ignore_rest_of_question = FALSE;
      continue;
    }
    if (eregi("^:TYPE:MC:N(.*)", $line, $webct_options)) {

      // Multiple Choice Question with several good answers
      $question = $this
        ->defaultquestion();
      $question->feedback = array();
      $question->qtype = MULTICHOICE;
      $question->single = 0;

      // Many answers allowed
      $ignore_rest_of_question = FALSE;
      continue;
    }
    if (eregi("^:TYPE:S", $line)) {

      // Short Answer Question
      $question = $this
        ->defaultquestion();
      $question->feedback = array();
      $question->qtype = SHORTANSWER;
      $question->usecase = 0;

      // Ignore case
      $ignore_rest_of_question = FALSE;
      continue;
    }
    if (eregi("^:TYPE:C", $line)) {

      // Calculated Question

      /*     $warnings[] = get_string("calculatedquestion", "quiz", $nLineCounter);
                unset($question);
                $ignore_rest_of_question = TRUE;         // Question Type not handled by Moodle
             */
      $question = $this
        ->defaultquestion();
      $question->qtype = CALCULATED;
      $question->answers = array();

      // No problem as they go as :FORMULA: from webct
      $question->units = array();
      $question->dataset = array();

      // To make us pass the end-of-question sanity checks
      $question->answer = array(
        'dummy',
      );
      $question->fraction = array(
        '1.0',
      );
      $question->feedback = array();
      $currentchoice = -1;
      $ignore_rest_of_question = FALSE;
      continue;
    }
    if (eregi("^:TYPE:M", $line)) {

      // Match Question
      $question = $this
        ->defaultquestion();
      $question->qtype = MATCH;
      $question->feedback = array();
      $ignore_rest_of_question = FALSE;

      // match question processing is not debugged
      continue;
    }
    if (eregi("^:TYPE:P", $line)) {

      // Paragraph Question
      $warnings[] = get_string("paragraphquestion", "quiz", $nLineCounter);
      unset($question);
      $ignore_rest_of_question = TRUE;

      // Question Type not handled by Moodle
      continue;
    }
    if (eregi("^:TYPE:", $line)) {

      // Unknow Question
      $warnings[] = get_string("unknowntype", "quiz", $nLineCounter);
      unset($question);
      $ignore_rest_of_question = TRUE;

      // Question Type not handled by Moodle
      continue;
    }
    if ($ignore_rest_of_question) {
      continue;
    }
    if (eregi("^:TITLE:(.*)", $line, $webct_options)) {
      $name = trim($webct_options[1]);
      if (strlen($name) > 255) {
        $name = substr($name, 0, 250) . "...";
        $warnings[] = get_string("questionnametoolong", "quiz", $nLineCounter);
      }
      $question->name = addslashes($name);
      continue;
    }
    if (eregi("^:IMAGE:(.*)", $line, $webct_options)) {
      $filename = trim($webct_options[1]);
      if (eregi("^http://", $filename)) {
        $question->image = $filename;
      }
      continue;
    }

    // Need to put the parsing of calculated items here to avoid ambitiuosness:
    // if question isn't defined yet there is nothing to do here (avoid notices)
    if (!isset($question)) {
      continue;
    }
    if (isset($question->qtype) && CALCULATED == $question->qtype && ereg("^:([[:lower:]].*|::.*)-(MIN|MAX|DEC|VAL([0-9]+))::?:?({$webctnumberregex})", $line, $webct_options)) {
      $datasetname = ereg_replace('^::', '', $webct_options[1]);
      $datasetvalue = qformat_webct_convert_formula($webct_options[4]);
      switch ($webct_options[2]) {
        case 'MIN':
          $question->dataset[$datasetname]->min = $datasetvalue;
          break;
        case 'MAX':
          $question->dataset[$datasetname]->max = $datasetvalue;
          break;
        case 'DEC':
          $datasetvalue = floor($datasetvalue);

          // int only!
          $question->dataset[$datasetname]->length = max(0, $datasetvalue);
          break;
        default:

          // The VAL case:
          $question->dataset[$datasetname]->datasetitem[$webct_options[3]] = new stdClass();
          $question->dataset[$datasetname]->datasetitem[$webct_options[3]]->itemnumber = $webct_options[3];
          $question->dataset[$datasetname]->datasetitem[$webct_options[3]]->value = $datasetvalue;
          break;
      }
      continue;
    }
    $bIsHTMLText = eregi(":H\$", $line);

    // True if next lines are coded in HTML
    if (eregi("^:QUESTION", $line)) {
      $questiontext = "";

      // Start gathering next lines
      continue;
    }
    if (eregi("^:ANSWER([0-9]+):([^:]+):([0-9\\.\\-]+):(.*)", $line, $webct_options)) {

      /// SHORTANSWER
      $currentchoice = $webct_options[1];
      $answertext = $webct_options[2];

      // Start gathering next lines
      $question->fraction[$currentchoice] = $webct_options[3] / 100;
      continue;
    }
    if (eregi("^:ANSWER([0-9]+):([0-9\\.\\-]+)", $line, $webct_options)) {
      $answertext = "";

      // Start gathering next lines
      $currentchoice = $webct_options[1];
      $question->fraction[$currentchoice] = $webct_options[2] / 100;
      continue;
    }
    if (eregi('^:FORMULA:(.*)', $line, $webct_options)) {

      //TODO HACK FIXME skip these until Drupal support formulas
      continue;

      // Answer for a CALCULATED question
      ++$currentchoice;
      $question->answers[$currentchoice] = qformat_webct_convert_formula($webct_options[1]);

      // Default settings:
      $question->fraction[$currentchoice] = 1.0;
      $question->tolerance[$currentchoice] = 0.0;
      $question->tolerancetype[$currentchoice] = 2;

      // nominal (units in webct)
      $question->feedback[$currentchoice] = '';
      $question->correctanswerlength[$currentchoice] = 4;
      $datasetnames = $QTYPES[CALCULATED]
        ->find_dataset_names($webct_options[1]);
      foreach ($datasetnames as $datasetname) {
        $question->dataset[$datasetname] = new stdClass();
        $question->dataset[$datasetname]->datasetitem = array();
        $question->dataset[$datasetname]->name = $datasetname;
        $question->dataset[$datasetname]->distribution = 'uniform';
        $question->dataset[$datasetname]->status = 'private';
      }
      continue;
    }
    if (eregi("^:L([0-9]+)", $line, $webct_options)) {
      $answertext = "";

      // Start gathering next lines
      $currentchoice = $webct_options[1];
      $question->fraction[$currentchoice] = 1;
      continue;
    }
    if (eregi("^:R([0-9]+)", $line, $webct_options)) {
      $responsetext = "";

      // Start gathering next lines
      $currentchoice = $webct_options[1];
      continue;
    }
    if (eregi("^:REASON([0-9]+):?", $line, $webct_options)) {
      $feedbacktext = "";

      // Start gathering next lines
      $currentchoice = $webct_options[1];
      continue;
    }
    if (eregi("^:FEEDBACK([0-9]+):?", $line, $webct_options)) {
      $generalfeedbacktext = "";

      // Start gathering next lines
      $currentchoice = $webct_options[1];
      continue;
    }
    if (eregi('^:FEEDBACK:(.*)', $line, $webct_options)) {
      $generalfeedbacktext = "";

      // Start gathering next lines
      continue;
    }
    if (eregi('^:LAYOUT:(.*)', $line, $webct_options)) {

      //    ignore  since layout in question_multichoice  is no more used in moodle
      //    $webct_options[1] contains either vertical or horizontal ;
      continue;
    }
    if (isset($question->qtype) && CALCULATED == $question->qtype && eregi('^:ANS-DEC:([1-9][0-9]*)', $line, $webct_options)) {

      // We can but hope that this always appear before the ANSTYPE property
      $question->correctanswerlength[$currentchoice] = $webct_options[1];
      continue;
    }
    if (isset($question->qtype) && CALCULATED == $question->qtype && eregi("^:TOL:({$webctnumberregex})", $line, $webct_options)) {

      // We can but hope that this always appear before the TOL property
      $question->tolerance[$currentchoice] = qformat_webct_convert_formula($webct_options[1]);
      continue;
    }
    if (isset($question->qtype) && CALCULATED == $question->qtype && eregi('^:TOLTYPE:percent', $line)) {

      // Percentage case is handled as relative in Moodle:
      $question->tolerance[$currentchoice] /= 100;
      $question->tolerancetype[$currentchoice] = 1;

      // Relative
      continue;
    }
    if (eregi('^:UNITS:(.+)', $line, $webct_options) and $webctunits = trim($webct_options[1])) {

      // This is a guess - I really do not know how different webct units are separated...
      $webctunits = explode(':', $webctunits);
      $unitrec->multiplier = 1.0;

      // Webct does not seem to support this
      foreach ($webctunits as $webctunit) {
        $unitrec->unit = trim($webctunit);
        $question->units[] = $unitrec;
      }
      continue;
    }
    if (!empty($question->units) && eregi('^:UNITREQ:(.*)', $line, $webct_options) && !$webct_options[1]) {

      // There are units but units are not required so add the no unit alternative
      // We can but hope that the UNITS property always appear before this property
      $unitrec->unit = '';
      $unitrec->multiplier = 1.0;
      $question->units[] = $unitrec;
      continue;
    }
    if (!empty($question->units) && eregi('^:UNITCASE:', $line)) {

      // This could be important but I was not able to figure out how
      // it works so I ignore it for now
      continue;
    }
    if (isset($question->qtype) && CALCULATED == $question->qtype && eregi('^:ANSTYPE:dec', $line)) {
      $question->correctanswerformat[$currentchoice] = '1';
      continue;
    }
    if (isset($question->qtype) && CALCULATED == $question->qtype && eregi('^:ANSTYPE:sig', $line)) {
      $question->correctanswerformat[$currentchoice] = '2';
      continue;
    }
  }

  // FIXME hacked Moodle to not quit on these errors
  // TODO think of a way to get the errors without changing Moodle code

  /*
  if (sizeof($errors) > 0) {
      echo "<p>".get_string("errorsdetected", "quiz", sizeof($errors))."</p><ul>";
      foreach($errors as $error) {
          echo "<li>$error</li>";
      }
      echo "</ul>";
      unset($questions);     // no questions imported
  }

  if (sizeof($warnings) > 0) {
      echo "<p>".get_string("warningsdetected", "quiz", sizeof($warnings))."</p><ul>";
      foreach($warnings as $warning) {
          echo "<li>$warning</li>";
      }
      echo "</ul>";
  }
  */
  return $questions;
}