You are here

function qformat_gift::readquestion in Quiz 6.6

Same name and namespace in other branches
  1. 6.5 includes/moodle/question/format/gift/format.php \qformat_gift::readquestion()

Given the data known to define a question in this format, this function converts it into a question object suitable for processing and insertion into Moodle.

If your format does not use blank lines to delimit questions (e.g. an XML format) you must override 'readquestions' too

Parameters

$lines mixed data that represents question:

Return value

object question object

Overrides qformat_default::readquestion

File

includes/moodle/question/format/gift/format.php, line 115

Class

qformat_gift
@package questionbank @subpackage importexport

Code

function readquestion($lines) {

  // Given an array of lines known to define a question in this format, this function
  // converts it into a question object suitable for processing and insertion into Moodle.
  $question = $this
    ->defaultquestion();
  $comment = NULL;

  // define replaced by simple assignment, stop redefine notices
  $gift_answerweight_regex = "^%\\-*([0-9]{1,2})\\.?([0-9]*)%";

  // REMOVED COMMENTED LINES and IMPLODE
  foreach ($lines as $key => $line) {
    $line = trim($line);
    if (substr($line, 0, 2) == "//") {
      $lines[$key] = " ";
    }
  }
  $text = trim(implode(" ", $lines));
  if ($text == "") {
    return false;
  }

  // Substitute escaped control characters with placeholders
  $text = $this
    ->escapedchar_pre($text);

  // Look for category modifier
  if (ereg('^\\$CATEGORY:', $text)) {

    // $newcategory = $matches[1];
    $newcategory = trim(substr($text, 10));

    // build fake question to contain category
    $question->qtype = 'category';
    $question->category = $newcategory;
    return $question;
  }

  // QUESTION NAME parser
  if (substr($text, 0, 2) == "::") {
    $text = substr($text, 2);
    $namefinish = strpos($text, "::");
    if ($namefinish === false) {
      $question->name = false;

      // name will be assigned after processing question text below
    }
    else {
      $questionname = substr($text, 0, $namefinish);
      $question->name = addslashes(trim($this
        ->escapedchar_post($questionname)));
      $text = trim(substr($text, $namefinish + 2));

      // Remove name from text
    }
  }
  else {
    $question->name = false;
  }

  // FIND ANSWER section
  // no answer means its a description
  $answerstart = strpos($text, "{");
  $answerfinish = strpos($text, "}");
  $description = false;
  if ($answerstart === false and $answerfinish === false) {
    $description = true;
    $answertext = '';
    $answerlength = 0;
  }
  elseif (!($answerstart !== false and $answerfinish !== false)) {
    $this
      ->error(get_string('braceerror', 'quiz'), $text);
    return false;
  }
  else {
    $answerlength = $answerfinish - $answerstart;
    $answertext = trim(substr($text, $answerstart + 1, $answerlength - 1));
  }

  // Format QUESTION TEXT without answer, inserting "_____" as necessary
  if ($description) {
    $questiontext = $text;
  }
  elseif (substr($text, -1) == "}") {

    // no blank line if answers follow question, outside of closing punctuation
    $questiontext = substr_replace($text, "", $answerstart, $answerlength + 1);
  }
  else {

    // inserts blank line for missing word format
    $questiontext = substr_replace($text, "_____", $answerstart, $answerlength + 1);
  }

  // get questiontext format from questiontext
  $oldquestiontext = $questiontext;
  $questiontextformat = 0;
  if (substr($questiontext, 0, 1) == '[') {
    $questiontext = substr($questiontext, 1);
    $rh_brace = strpos($questiontext, ']');
    $qtformat = substr($questiontext, 0, $rh_brace);
    $questiontext = substr($questiontext, $rh_brace + 1);
    if (!($questiontextformat = text_format_name($qtformat))) {
      $questiontext = $oldquestiontext;
    }
  }
  $question->questiontextformat = $questiontextformat;
  $question->questiontext = addslashes(trim($this
    ->escapedchar_post($questiontext)));

  // set question name if not already set
  if ($question->name === false) {
    $question->name = $question->questiontext;
  }

  // ensure name is not longer than 250 characters

  //$question->name = shorten_text( $question->name, 200 );
  $question->name = substr($question->name, 0, 200);
  $question->name = strip_tags(substr($question->name, 0, 250));

  // determine QUESTION TYPE
  $question->qtype = NULL;

  // give plugins first try
  // plugins must promise not to intercept standard qtypes
  // MDL-12346, this could be called from lesson mod which has its own base class =(
  if (method_exists($this, 'try_importing_using_qtypes') && ($try_question = $this
    ->try_importing_using_qtypes($lines, $question, $answertext))) {
    return $try_question;
  }
  if ($description) {
    $question->qtype = DESCRIPTION;
  }
  elseif ($answertext == '') {
    $question->qtype = ESSAY;
  }
  elseif ($answertext[0] == "#") {
    $question->qtype = NUMERICAL;
  }
  elseif (strpos($answertext, "~") !== false) {

    // only Multiplechoice questions contain tilde ~
    $question->qtype = MULTICHOICE;
  }
  elseif (strpos($answertext, "=") !== false && strpos($answertext, "->") !== false) {

    // only Matching contains both = and ->
    $question->qtype = MATCH;
  }
  else {

    // either TRUEFALSE or SHORTANSWER
    // TRUEFALSE question check
    $truefalse_check = $answertext;
    if (strpos($answertext, "#") > 0) {

      // strip comments to check for TrueFalse question
      $truefalse_check = trim(substr($answertext, 0, strpos($answertext, "#")));
    }
    $valid_tf_answers = array(
      "T",
      "TRUE",
      "F",
      "FALSE",
    );
    if (in_array($truefalse_check, $valid_tf_answers)) {
      $question->qtype = TRUEFALSE;
    }
    else {

      // Must be SHORTANSWER
      $question->qtype = SHORTANSWER;
    }
  }
  if (!isset($question->qtype)) {
    $giftqtypenotset = get_string('giftqtypenotset', 'quiz');
    $this
      ->error($giftqtypenotset, $text);
    return false;
  }
  switch ($question->qtype) {
    case DESCRIPTION:
      $question->defaultgrade = 0;
      $question->length = 0;
      return $question;
      break;
    case ESSAY:
      $question->feedback = '';
      $question->fraction = 0;
      return $question;
      break;
    case MULTICHOICE:
      if (strpos($answertext, "=") === false) {
        $question->single = 0;

        // multiple answers are enabled if no single answer is 100% correct
      }
      else {
        $question->single = 1;

        // only one answer allowed (the default)
      }
      $answertext = str_replace("=", "~=", $answertext);
      $answers = explode("~", $answertext);
      if (isset($answers[0])) {
        $answers[0] = trim($answers[0]);
      }
      if (empty($answers[0])) {
        array_shift($answers);
      }
      $countanswers = count($answers);
      if (!$this
        ->check_answer_count(2, $answers, $text)) {
        return false;
        break;
      }
      foreach ($answers as $key => $answer) {
        $answer = trim($answer);

        // determine answer weight
        if ($answer[0] == "=") {
          $answer_weight = 1;
          $answer = substr($answer, 1);
        }
        elseif (ereg($gift_answerweight_regex, $answer)) {

          // check for properly formatted answer weight
          $answer_weight = $this
            ->answerweightparser($answer);
        }
        else {

          //default, i.e., wrong anwer
          $answer_weight = 0;
        }
        $question->fraction[$key] = $answer_weight;
        $question->feedback[$key] = $this
          ->commentparser($answer);

        // commentparser also removes comment from $answer
        $question->answer[$key] = addslashes($this
          ->escapedchar_post($answer));
        $question->correctfeedback = '';
        $question->partiallycorrectfeedback = '';
        $question->incorrectfeedback = '';
      }

      // end foreach answer

      //$question->defaultgrade = 1;

      //$question->image = "";   // No images with this format
      return $question;
      break;
    case MATCH:
      $answers = explode("=", $answertext);
      if (isset($answers[0])) {
        $answers[0] = trim($answers[0]);
      }
      if (empty($answers[0])) {
        array_shift($answers);
      }
      if (!$this
        ->check_answer_count(2, $answers, $text)) {
        return false;
        break;
      }
      foreach ($answers as $key => $answer) {
        $answer = trim($answer);
        if (strpos($answer, "->") === false) {
          $giftmatchingformat = get_string('giftmatchingformat', 'quiz');
          $this
            ->error($giftmatchingformat, $answer);
          return false;
          break 2;
        }
        $marker = strpos($answer, "->");
        $question->subquestions[$key] = addslashes(trim($this
          ->escapedchar_post(substr($answer, 0, $marker))));
        $question->subanswers[$key] = addslashes(trim($this
          ->escapedchar_post(substr($answer, $marker + 2))));
      }

      // end foreach answer
      return $question;
      break;
    case TRUEFALSE:
      $answer = $answertext;
      $comment = $this
        ->commentparser($answer);

      // commentparser also removes comment from $answer
      $feedback = $this
        ->split_truefalse_comment($comment);
      if ($answer == "T" or $answer == "TRUE") {
        $question->answer = 1;
        $question->feedbacktrue = $feedback['right'];
        $question->feedbackfalse = $feedback['wrong'];
      }
      else {
        $question->answer = 0;
        $question->feedbackfalse = $feedback['right'];
        $question->feedbacktrue = $feedback['wrong'];
      }
      $question->penalty = 1;
      $question->correctanswer = $question->answer;
      return $question;
      break;
    case SHORTANSWER:

      // SHORTANSWER Question
      $answers = explode("=", $answertext);
      if (isset($answers[0])) {
        $answers[0] = trim($answers[0]);
      }
      if (empty($answers[0])) {
        array_shift($answers);
      }
      if (!$this
        ->check_answer_count(1, $answers, $text)) {
        return false;
        break;
      }
      foreach ($answers as $key => $answer) {
        $answer = trim($answer);

        // Answer Weight
        if (ereg($gift_answerweight_regex, $answer)) {

          // check for properly formatted answer weight
          $answer_weight = $this
            ->answerweightparser($answer);
        }
        else {

          //default, i.e., full-credit anwer
          $answer_weight = 1;
        }
        $question->fraction[$key] = $answer_weight;
        $question->feedback[$key] = $this
          ->commentparser($answer);

        //commentparser also removes comment from $answer
        $question->answer[$key] = addslashes($this
          ->escapedchar_post($answer));
      }

      // end foreach

      //$question->usecase = 0;  // Ignore case

      //$question->defaultgrade = 1;

      //$question->image = "";   // No images with this format
      return $question;
      break;
    case NUMERICAL:

      // Note similarities to ShortAnswer
      $answertext = substr($answertext, 1);

      // remove leading "#"
      // If there is feedback for a wrong answer, store it for now.
      if (($pos = strpos($answertext, '~')) !== false) {
        $wrongfeedback = substr($answertext, $pos);
        $answertext = substr($answertext, 0, $pos);
      }
      else {
        $wrongfeedback = '';
      }
      $answers = explode("=", $answertext);
      if (isset($answers[0])) {
        $answers[0] = trim($answers[0]);
      }
      if (empty($answers[0])) {
        array_shift($answers);
      }
      if (count($answers) == 0) {

        // invalid question
        $giftnonumericalanswers = get_string('giftnonumericalanswers', 'quiz');
        $this
          ->error($giftnonumericalanswers, $text);
        return false;
        break;
      }
      foreach ($answers as $key => $answer) {
        $answer = trim($answer);

        // Answer weight
        if (ereg($gift_answerweight_regex, $answer)) {

          // check for properly formatted answer weight
          $answer_weight = $this
            ->answerweightparser($answer);
        }
        else {

          //default, i.e., full-credit anwer
          $answer_weight = 1;
        }
        $question->fraction[$key] = $answer_weight;
        $question->feedback[$key] = $this
          ->commentparser($answer);

        //commentparser also removes comment from $answer

        //Calculate Answer and Min/Max values
        if (strpos($answer, "..") > 0) {

          // optional [min]..[max] format
          $marker = strpos($answer, "..");
          $max = trim(substr($answer, $marker + 2));
          $min = trim(substr($answer, 0, $marker));
          $ans = ($max + $min) / 2;
          $tol = $max - $ans;
        }
        elseif (strpos($answer, ":") > 0) {

          // standard [answer]:[errormargin] format
          $marker = strpos($answer, ":");
          $tol = trim(substr($answer, $marker + 1));
          $ans = trim(substr($answer, 0, $marker));
        }
        else {

          // only one valid answer (zero errormargin)
          $tol = 0;
          $ans = trim($answer);
        }
        if (!(is_numeric($ans) || ($ans = '*')) || !is_numeric($tol)) {
          $errornotnumbers = get_string('errornotnumbers');
          $this
            ->error($errornotnumbers, $text);
          return false;
          break;
        }

        // store results
        $question->answer[$key] = $ans;
        $question->tolerance[$key] = $tol;
      }

      // end foreach
      if ($wrongfeedback) {
        $key += 1;
        $question->fraction[$key] = 0;
        $question->feedback[$key] = $this
          ->commentparser($wrongfeedback);
        $question->answer[$key] = '';
        $question->tolerance[$key] = '';
      }
      return $question;
      break;
    default:
      $giftnovalidquestion = get_string('giftnovalidquestion', 'quiz');
      $this
        ->error($giftnovalidquestion, $text);
      return false;
      break;
  }

  // end switch ($question->qtype)
}