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;
}
}
