diff --git a/app/V1Module/presenters/base/BasePresenter.php b/app/V1Module/presenters/base/BasePresenter.php index 544f29ef2..cefd811b4 100644 --- a/app/V1Module/presenters/base/BasePresenter.php +++ b/app/V1Module/presenters/base/BasePresenter.php @@ -257,6 +257,17 @@ private function processParamsLoose(array $paramData) } else { $param->conformsToDefinition($paramValue); } + + // Path and query parameter might need patching, so they have correct type for + // being injected into the presenter method. + if ( + $param->type === Type::Path || $param->type === Type::Query + && array_key_exists($param->name, $this->params) + ) { + foreach ($param->validators as $validator) { + $validator->patchQueryParameter($this->params[$param->name]); + } + } } } diff --git a/app/helpers/MetaFormats/Validators/BaseValidator.php b/app/helpers/MetaFormats/Validators/BaseValidator.php index c1c09f9fd..1b5552933 100644 --- a/app/helpers/MetaFormats/Validators/BaseValidator.php +++ b/app/helpers/MetaFormats/Validators/BaseValidator.php @@ -65,4 +65,16 @@ public function validate(mixed $value): bool // return false by default to enforce overriding in derived types return false; } + + /** + * Patches the query/path parameter value after successful validation. + * This is useful for special data types that needs to be converted to a different type before being used + * in the presenter method (e.g., convert strings like "true"/"false" to booleans). + * Note: this method is not called for values that did not pass validation. + * @param mixed $value The value to be patched. Passed by reference, so it can be modified by the method. + */ + public function patchQueryParameter(mixed &$value): void + { + // no-op by default, can be overridden by validators that need to change the value before validation + } } diff --git a/app/helpers/MetaFormats/Validators/VArray.php b/app/helpers/MetaFormats/Validators/VArray.php index 706800641..9a8fe2f28 100644 --- a/app/helpers/MetaFormats/Validators/VArray.php +++ b/app/helpers/MetaFormats/Validators/VArray.php @@ -102,4 +102,13 @@ public function validate(mixed $value): bool } return true; } + + public function patchQueryParameter(mixed &$value): void + { + if (is_array($value) && $this->nestedValidator !== null) { + foreach ($value as &$element) { + $this->nestedValidator->patchQueryParameter($element); + } + } + } } diff --git a/app/helpers/MetaFormats/Validators/VBool.php b/app/helpers/MetaFormats/Validators/VBool.php index 3f55e63c2..d3913740b 100644 --- a/app/helpers/MetaFormats/Validators/VBool.php +++ b/app/helpers/MetaFormats/Validators/VBool.php @@ -22,14 +22,29 @@ public function validate(mixed $value): bool if (!$this->strict) { // FILTER_VALIDATE_BOOL is not used because it additionally allows "on", "yes", "off", "no" and "" - return $value === 0 - || $value === 1 - || $value === "0" - || $value === "1" - || $value === "false" - || $value === "true"; + + if ($value === 0 || $value === 1) { + return true; + } + + if (is_string($value)) { + $lower = strtolower(trim($value)); + return $lower === "0" + || $lower === "1" + || $lower === "false" + || $lower === "true"; + } } return false; } + + public function patchQueryParameter(mixed &$value): void + { + if (is_string($value)) { + $value = filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE); + } else { + $value = (bool)$value; + } + } } diff --git a/app/model/repository/PlagiarismDetectionBatches.php b/app/model/repository/PlagiarismDetectionBatches.php index 06e21bec4..14c85b27e 100644 --- a/app/model/repository/PlagiarismDetectionBatches.php +++ b/app/model/repository/PlagiarismDetectionBatches.php @@ -34,8 +34,9 @@ public function findByToolAndSolution(?string $detectionTool, ?AssignmentSolutio $sub = $qb->getEntityManager()->createQueryBuilder() ->select("ds")->from(PlagiarismDetectedSimilarity::class, "ds"); $sub->andWhere("ds.batch = b.id") - ->andWhere("ds.testedSolution = :sid")->setParameter("sid", $solution->getId()); - $qb->andWhere($qb->expr()->exists($sub->getDQL())); + ->andWhere("ds.testedSolution = :sid"); + $qb->andWhere($qb->expr()->exists($sub->getDQL())) + ->setParameter("sid", $solution->getId()); } $qb->orderBy("b.createdAt", "DESC"); return $qb->getQuery()->getResult();