closedquestion-8.x-3.x-dev/src/Question/Mapping/CqMatch.php

src/Question/Mapping/CqMatch.php
<?php

namespace Drupal\closedquestion\Question\Mapping;

/**
 * Class CqMatch.
 *
 * CqMatch checks if a hotspot contains a draggable or if a choice has been
 * filled with a certain option.
 * Can also check if one of a set of hotspots contains a draggable, or if one of
 * a set of draggables is in a hotspot or of one of a set of draggables is in
 * one of a set of hotspots.
 *
 * @package Drupal\closedquestion\Question\Mapping
 */
class CqMatch extends CqAbstractMapping {

  /**
   * Implements CqAbstractMapping::evaluate()
   */
  public function evaluate() {
    $inlinechoice = $this->getParam('inlinechoice');
    $optionId = $this->getParam('inlineoption');
    $pattern = $this->getParam('pattern');
    $matchAll = $this->getParam('matchall');
    $hotspotId = $this->getParam('hotspot');
    $hotspotPattern = $this->getParam('hotspotpattern');
    $draggableId = $this->getParam('draggable');
    $draggablePattern = $this->getParam('draggablepattern');
    $timeStart = $this->getParam('timestart');
    $timeEnd = $this->getParam('timeend');
    $messenger = \Drupal::messenger();

    if ($hotspotId !== NULL && $hotspotPattern !== NULL) {
      $messenger->addMessage(t('Match with both hotspot and hotspotpattern set!'), 'warning');
    }
    if ($hotspotId !== NULL && $hotspotPattern === NULL) {
      $hotspotPattern = $hotspotId;
    }
    if ($draggableId !== NULL && $draggablePattern !== NULL) {
      $messenger->addMessage(t('Match with both draggable and draggablepattern set!'), 'warning');
    }
    if ($draggableId !== NULL && $draggablePattern === NULL) {
      $draggablePattern = $draggableId;
    }

    if ($inlinechoice != NULL && $optionId != NULL) {
      // FillBlanks-selectbox-type.
      $answerForChoice = $this->context->getAnswerForChoice($inlinechoice);
      if (is_array($answerForChoice)) {
        return (in_array($optionId, $answerForChoice));
      }
      else {
        return ($answerForChoice == $optionId);
      }
    }
    elseif ($pattern !== NULL) {
      // FillBlanks-freeform-type OR Hotspot match order type.
      return $this->evaluatePattern($pattern, $inlinechoice, (boolean) $matchAll);
    }
    elseif ($hotspotPattern !== NULL) {
      // Matching hotspots and draggables.
      $matchAll = mb_strtolower(mb_substr($matchAll, 0, 1));
      return $this->evaluateHotspots($hotspotPattern, $draggablePattern, $matchAll, $timeStart, $timeEnd);
    }
    else {
      $messenger->addMessage(t('Warning: Match with a strange combination of attributes found.'), 'warning');
    }
    return FALSE;
  }

  /**
   * Do matching of the answer(s) against a pattern.
   *
   * @param string $pattern
   *   The regular expression pattern to match the answer(s) on.
   * @param string $inlinechoice
   *   A regular expression to find which inline choices (answers) to match
   *   against the regular expression.
   * @param bool $matchAll
   *   If there is more than one answer matched by $inlinechoice, do all of them
   *   have to match the pattern, or only one?
   *
   * @return bool
   *   TRUE if the matching was successfull, FALSE otherwise.
   */
  private function evaluatePattern($pattern, $inlinechoice = '', $matchAll = FALSE) {
    if (mb_substr($pattern, 0, 1) != '/') {
      $pattern = '/' . $pattern . '/';
    }

    $answer = $this->context->getAnswerForChoice($inlinechoice);

    if (is_array($answer)) {
      if (count($answer) == 0) {
        return FALSE;
      }
      foreach ($answer as $choiceId => $subAnswer) {
        $this->topParent->lastMatchedId = $choiceId;
        $match = preg_match($pattern, trim($subAnswer));
        if ($match && !$matchAll) {
          // This sub-answer matched, and we need only one to match, so this
          // range matches, return TRUE.
          return TRUE;
        }
        if (!$match && $matchAll) {
          // This sub-answer did not match, and we need all of 'em to match
          // for true, so the match failed, return FALSE.
          return FALSE;
        }
      }
      if ($matchAll) {
        // We needed all to match, since we got here, none failed to match,
        // return TRUE.
        return TRUE;
      }
      else {
        // We needed only one to match, but since we got here none matched,
        // return FALSE.
        return FALSE;
      }
    }
    else {
      $this->topParent->lastMatchedId = $inlinechoice;
      return preg_match($pattern, trim($answer));
    }

    // Should be unreachable.
    return FALSE;
  }

  /**
   * Match hotspots with draggables.
   *
   * @param string $hotspotPattern
   *   The regular expression used to select which hotspots to match.
   * @param string $draggablePattern
   *   The regular expression used to select with draggables to match.
   * @param string $matchAll
   *   A string of length 1, indicating if:
   *   (h)otspots: All selected hotspots should have one of the selected
   *     draggable.
   *   (d)raggable: All selected draggables should be on one of the selected
   *     hotspots.
   *   (b)oth: All selected hotspots should have one of the selected draggables
   *     and all selected draggables should be on one of the selected hotspots.
   * @param int $timeStart
   *   (Used only by movie questions) A number representing the match'
   *   starting time.
   * @param int $timeEnd
   *   (Used only by movie questions) A number representing the match' ending
   *   time (can be omitted).
   *
   * @return bool
   *   TRUE if any of the hotspots mached any of the draggables.
   */
  private function evaluateHotspots($hotspotPattern, $draggablePattern, $matchAll = '', $timeStart = '', $timeEnd = '') {
    $messenger = \Drupal::messenger();
    $allHotspots = $this->context->getHotspots();
    $allDraggables = $this->context->getDraggables();

    $hotspots = array();
    $draggables = array();

    // Find the hotspots to operate on.
    if ($hotspotPattern != NULL) {
      // First we check if the pattern is an exact match.
      if (isset($allHotspots[$hotspotPattern])) {
        $hotspots[] = $allHotspots[$hotspotPattern];
      }
      else {
        // Not an exact match, do regex matching.
        if (mb_substr($hotspotPattern, 0, 1) != '/') {
          $hotspotPattern = '/' . $hotspotPattern . '/';
        }
        foreach ($allHotspots as $hotspot) {
          if (preg_match($hotspotPattern, $hotspot->getIdentifier())) {
            $hotspots[] = $hotspot;
          }
        }
      }
    }
    if (count($hotspots) == 0) {
      $messenger->addMessage(t('Warning: no hotspots found with: %id', array('%id' => $hotspotPattern)), 'warning');
      return FALSE;
    }

    // Find the draggables to operate on.
    if ($draggablePattern != NULL) {
      // First check if the pattern is an exact match.
      if (isset($allDraggables[$draggablePattern])) {
        $draggables[] = $allDraggables[$draggablePattern];
      }
      else {
        // Not an exact match, do regex matching.
        if (mb_substr($draggablePattern, 0, 1) != '/') {
          $draggablePattern = '/' . $draggablePattern . '/';
        }
        foreach ($allDraggables as $draggable) {
          if (preg_match($draggablePattern, $draggable->getIdentifier())) {
            $draggables[] = $draggable;
          }
        }
      }
    }
    if ($draggablePattern === NULL) {
      $draggables = $allDraggables;
    }
    if ($draggablePattern !== NULL && count($draggables) == 0) {
      $messenger->addMessage(t('Warning: no draggables found with %id', array('%id' => $draggablePattern)), 'warning');
      return FALSE;
    }

    // Do the matching.
    if ($matchAll == 'd' || $matchAll == 'b') {
      foreach ($draggables as $draggable) {
        foreach ($hotspots as $hotspot) {
          if ($hotspot->doMatch($draggable->getLocation()) && $this->evaluateTimeInterval($draggable->getTime(), $timeStart, $timeEnd)) {
            // This draggable has a match, skip all further hotspots and do the
            // next draggable.
            continue 2;
          }
        }
        // This draggable did not have any match. Return FALSE.
        return FALSE;
      }
      if ($matchAll == 'd') {
        // All draggables had a match. We only needed to check all draggables.
        return TRUE;
      }
    }

    if ($matchAll == 'h' || $matchAll == 'b') {
      foreach ($hotspots as $hotspot) {
        foreach ($draggables as $draggable) {
          if ($hotspot->doMatch($draggable->getLocation()) && $this->evaluateTimeInterval($draggable->getTime(), $timeStart, $timeEnd)) {
            // This hotspot has a draggable, skip to the next hotspot.
            continue 2;
          }
        }
        // This hotspot did not have any draggables. Return FALSE.
        return FALSE;
      }
      // All hotspots had a match. If matchAll was 'b' then all draggables also
      // had a match, so we can return TRUE.
      return TRUE;
    }

    if ($matchAll) {
      $messenger->addMessage(t('Unknown matchAll value: %matchall, expected one of h(otspot),d(raggable) or b(oth)', array('%matchall' => $matchAll)));
    }
    foreach ($hotspots as $hotspot) {
      foreach ($draggables as $draggable) {
        if ($hotspot->doMatch($draggable->getLocation()) && $this->evaluateTimeInterval($draggable->getTime(), $timeStart, $timeEnd)) {
          return TRUE;
        }
      }
    }
    return FALSE;
  }

  /**
   * Overrides CqAbstractMapping::getAllText()
   */
  public function getAllText() {
    $messenger = \Drupal::messenger();
    $inlinechoice = $this->getParam('inlinechoice');
    $optionId = $this->getParam('inlineoption');
    $pattern = $this->getParam('pattern');
    $matchAll = $this->getParam('matchall');
    $hotspotId = $this->getParam('hotspot');
    $hotspotPattern = $this->getParam('hotspotpattern');
    $draggableId = $this->getParam('draggable');
    $draggablePattern = $this->getParam('draggablepattern');

    if ($hotspotId !== NULL && $hotspotPattern !== NULL) {
      $messenger->addMessage(t('Match with both hotspot and hotspotpattern set!'), 'warning');
    }
    if ($hotspotId !== NULL && $hotspotPattern === NULL) {
      $hotspotPattern = $hotspotId;
    }

    if ($draggableId !== NULL && $draggablePattern !== NULL) {
      $messenger->addMessage(t('Match with both draggable and draggablepattern set!'), 'warning');
    }
    if ($draggableId !== NULL && $draggablePattern === NULL) {
      $draggablePattern = $draggableId;
    }

    $retval = array();
    $retval['logic']['#markup'] = t('Match %choice %hotspot against %option %draggable %pattern', array(
      '%choice' => $inlinechoice,
      '%hotspot' => $hotspotPattern,
      '%option' => $optionId,
      '%pattern' => $pattern,
      '%draggable' => $draggablePattern,
    ));
    if ($matchAll !== NULL) {
      $retval['logic']['#markup'] .= t('<br/>Match all = %matchall', array('%matchall' => $matchAll));
    }

    $retval += parent::getAllText();
    return $retval;
  }

  /**
   * Evaluates whether a certain time falls within a time interval.
   *
   * @param int $assessed_time
   *   The time being assessed.
   * @param int $timeIntervalStart
   *   The start of the time interval.
   * @param int $timeIntervalEnd
   *   The end of the time interval (optional)
   *
   * @return bool
   *   TRUE if given time is within interval.
   */
  private function evaluateTimeInterval($assessed_time = '', $timeIntervalStart = '', $timeIntervalEnd = '') {
    if ($assessed_time == '' || $timeIntervalStart == '') {
      // No time interval.
      return TRUE;
    }

    $returnValue = FALSE;

    if ($assessed_time >= $timeIntervalStart) {
      $returnValue = TRUE;
    }

    if ($timeIntervalEnd != '' && $assessed_time > $timeIntervalEnd) {
      $returnValue = FALSE;
    }

    return $returnValue;
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc