Skip to content

Commit

Permalink
QE-429 op handler subquestion update (#3492)
Browse files Browse the repository at this point in the history
* OpHandlerSubquestionUpdate.php first steps

* OpHandlerSubquestionUpdate.php with basic unit tests

* re-added deleted function

* exception when no question found

* OpHandlerSubQuestion.php can now handle create and update

* OpHandlerSubQuestion.php fixed a test

* QE-429 dev: throw exception when prepared data is empty array

* adjustments after change of how subquestions are saved

* OpHandlerSubQuestion.php can now handle create and update after changes in SubQuestionService

* fix psalm issue

---------

Co-authored-by: pstelling <patricia.stelling@limesurvey.org>
  • Loading branch information
twilligls and Trischi80 committed Oct 30, 2023
1 parent 28fc516 commit f710b28
Show file tree
Hide file tree
Showing 9 changed files with 404 additions and 77 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
Input\TransformerInputQuestionAggregate,
Input\TransformerInputQuestionAttribute,
Input\TransformerInputQuestionL10ns,
Input\TransformerInputSubQuestion
};
use LimeSurvey\ObjectPatch\{
Op\OpInterface,
Expand All @@ -32,7 +31,6 @@ class OpHandlerQuestionCreate implements OpHandlerInterface
protected TransformerInputQuestionAttribute $transformerAttribute;
protected TransformerInputAnswer $transformerAnswer;
protected TransformerInputAnswerL10ns $transformerAnswerL10n;
protected TransformerInputSubQuestion $transformerSubQuestion;
protected TransformerInputQuestionAggregate $transformerInputQuestionAggregate;

public function __construct(
Expand All @@ -42,7 +40,6 @@ public function __construct(
TransformerInputQuestionAttribute $transformerAttribute,
TransformerInputAnswer $transformerAnswer,
TransformerInputAnswerL10ns $transformerAnswerL10n,
TransformerInputSubQuestion $transformerSubQuestion,
TransformerInputQuestionAggregate $transformerInputQuestionAggregate
) {
$this->entity = 'question';
Expand All @@ -52,7 +49,6 @@ public function __construct(
$this->transformerAttribute = $transformerAttribute;
$this->transformerAnswer = $transformerAnswer;
$this->transformerAnswerL10n = $transformerAnswerL10n;
$this->transformerSubQuestion = $transformerSubQuestion;
$this->transformerInputQuestionAggregate = $transformerInputQuestionAggregate;
}

Expand Down Expand Up @@ -275,7 +271,12 @@ public function prepare(OpInterface $op, string $name, array $data)
$data,
);
case 'subquestions':
return $this->prepareSubQuestions($op, $data);
return $this->prepareSubQuestions(
$op,
$this->transformer,
$this->transformerL10n,
$data
);
}
return $data;
}
Expand Down Expand Up @@ -420,48 +421,4 @@ private function prepareAnswers(OpInterface $op, ?array $data): array
}
return $preparedAnswers;
}

/**
* Converts the subquestions from the raw data to the expected format.
* @param OpInterface $op
* @param array|null $data
* @return array
* @throws OpHandlerException
*/
private function prepareSubQuestions(OpInterface $op, ?array $data): array
{
$preparedSubQuestions = [];
if (is_array($data)) {
foreach ($data as $index => $subQuestion) {
$tfSubQuestion = $this->transformerSubQuestion->transform(
$subQuestion
);
$this->checkRequiredData(
$op,
$tfSubQuestion,
'subquestions'
);
if (
is_array($subQuestion) && array_key_exists(
'l10ns',
$subQuestion
) && is_array($subQuestion['l10ns'])
) {
foreach ($subQuestion['l10ns'] as $lang => $subL10n) {
$tfSubL10n = $this->transformerL10n->transform(
$subL10n
);
$tfSubQuestion['subquestionl10n'][$lang] =
(
is_array($tfSubL10n)
&& isset($tfSubL10n['question'])
) ?
$tfSubL10n['question'] : null;
}
}
$preparedSubQuestions[$index][0] = $tfSubQuestion;
}
}
return $preparedSubQuestions;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

use LimeSurvey\Api\Command\V1\Transformer\Input\TransformerInputAnswer;
use LimeSurvey\Api\Command\V1\Transformer\Input\TransformerInputAnswerL10ns;
use LimeSurvey\Api\Command\V1\Transformer\Input\TransformerInputQuestion;
use LimeSurvey\Api\Command\V1\Transformer\Input\TransformerInputQuestionAttribute;
use LimeSurvey\Api\Command\V1\Transformer\Input\TransformerInputQuestionL10ns;
use LimeSurvey\ObjectPatch\Op\OpInterface;
use LimeSurvey\ObjectPatch\OpHandler\OpHandlerException;

Expand Down Expand Up @@ -198,4 +200,92 @@ public function prepareAdvancedSettings(
}
return $preparedSettings;
}

/**
* Converts the subquestions from the raw data to the expected format.
* @param OpInterface $op
* @param TransformerInputQuestion $transformerQuestion
* @param TransformerInputQuestionL10ns $transformerL10n
* @param array|null $data
* @param array|null $additionalRequiredEntities
* @return array
* @throws OpHandlerException
*/
private function prepareSubQuestions(
OpInterface $op,
TransformerInputQuestion $transformerQuestion,
TransformerInputQuestionL10ns $transformerL10n,
?array $data,
?array $additionalRequiredEntities = null
): array {
$preparedSubQuestions = [];
if (is_array($data)) {
foreach ($data as $index => $subQuestion) {
$tfSubQuestion = $transformerQuestion->transform(
$subQuestion
);
if (
is_array($tfSubQuestion) && array_key_exists(
'title',
$tfSubQuestion
)
) {
$tfSubQuestion['code'] = $tfSubQuestion['title'];
}
$this->checkRequiredData(
$op,
$tfSubQuestion,
'subquestions',
$additionalRequiredEntities
);
if (
is_array($subQuestion) && array_key_exists(
'l10ns',
$subQuestion
) && is_array($subQuestion['l10ns'])
) {
foreach ($subQuestion['l10ns'] as $lang => $subL10n) {
$tfSubL10n = $transformerL10n->transform(
$subL10n
);
$tfSubQuestion['subquestionl10n'][$lang] =
(
is_array($tfSubL10n)
&& isset($tfSubL10n['question'])
) ?
$tfSubL10n['question'] : null;
}
}
$qid = $this->getQidFromData($index, $tfSubQuestion);
$scaleId = $this->getScaleIdFromData($tfSubQuestion);
$preparedSubQuestions[$qid][$scaleId] = $tfSubQuestion;
}
}
return $preparedSubQuestions;
}

/**
* @param int $index
* @param array $questionData
* @return int
*/
private function getQidFromData(int $index, array $questionData)
{
return array_key_exists(
'qid',
$questionData
) ? (int)$questionData['qid'] : $index;
}

/**
* @param array $questionData
* @return int
*/
private function getScaleIdFromData(array $questionData)
{
return array_key_exists(
'scale_id',
$questionData
) ? (int)$questionData['scale_id'] : 0;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
<?php

namespace LimeSurvey\Api\Command\V1\SurveyPatch;

use LimeSurvey\Api\Command\V1\Transformer\Input\{
TransformerInputQuestion,
TransformerInputQuestionL10ns
};
use LimeSurvey\Models\Services\{
Exception\NotFoundException,
Exception\PermissionDeniedException,
Exception\PersistErrorException,
QuestionAggregateService,
QuestionAggregateService\QuestionService,
QuestionAggregateService\SubQuestionsService
};
use LimeSurvey\ObjectPatch\{Op\OpInterface,
OpHandler\OpHandlerException,
OpHandler\OpHandlerInterface,
OpType\OpTypeCreate,
OpType\OpTypeUpdate
};

/**
* Class OpHandlerSubQuestion can handle create and update
* of subquestions which belong to a single question.
*/
class OpHandlerSubQuestion implements OpHandlerInterface
{
use OpHandlerSurveyTrait;
use OpHandlerQuestionTrait;

protected QuestionAggregateService $questionAggregateService;
protected SubQuestionsService $subQuestionsService;
protected QuestionService $questionService;
protected TransformerInputQuestion $transformer;
protected TransformerInputQuestionL10ns $transformerL10ns;

public function __construct(
QuestionAggregateService $questionAggregateService,
SubQuestionsService $subQuestionsService,
QuestionService $questionService,
TransformerInputQuestionL10ns $transformerL10n,
TransformerInputQuestion $transformer
) {
$this->questionAggregateService = $questionAggregateService;
$this->subQuestionsService = $subQuestionsService;
$this->questionService = $questionService;
$this->transformer = $transformer;
$this->transformerL10ns = $transformerL10n;
}

/**
* Checks if the operation is applicable for the given entity.
*
* @param OpInterface $op
* @return bool
*/
public function canHandle(OpInterface $op): bool
{
return (
$op->getType()->getId() === OpTypeUpdate::ID
|| $op->getType()->getId() === OpTypeCreate::ID
)
&& $op->getEntityType() === 'subquestion';
}

/**
* Handle subquestion create or update operation.
* Attention: subquestions not present in the patch will be deleted.
* Expects a patch structure like this for update:
* {
* "patch": [{
* "entity": "subquestion",
* "op": "update",
* "id": 722, //parent qid
* "props": {
* "0": {
* "qid": 728,
* "title": "SQ001new",
* "l10ns": {
* "de": {
* "question": "subger1updated",
* "language": "de"
* },
* "en": {
* "question": "sub1updated",
* "language": "en"
* }
* }
* },
* "1": {
* "qid": 729,
* "title": "SQ002new",
* "l10ns": {
* "de": {
* "question": "subger2updated",
* "language": "de"
* },
* "en": {
* "question": "sub2updated",
* "language": "en"
* }
* }
* }
* }
* }
* ]
* }
*
* Expects a patch structure like this for update:
* {
* "patch": [{
* "entity": "subquestion",
* "op": "create",
* "id": 722,
* "props": {
* "0": {
* "title": "SQ011",
* "l10ns": {
* "de": {
* "question": "germanized1",
* "language": "de"
* },
* "en": {
* "question": "englishized",
* "language": "en"
* }
* }
* },
* "1": {
* "title": "SQ012",
* "l10ns": {
* "de": {
* "question": "germanized2",
* "language": "de"
* },
* "en": {
* "question": "englishized2",
* "language": "en"
* }
* }
* }
* }
* }
* ]
* }
*
* @param OpInterface $op
* @throws OpHandlerException
* @throws NotFoundException
* @throws PermissionDeniedException
* @throws PersistErrorException
*/
public function handle(OpInterface $op): void
{
$surveyId = $this->getSurveyIdFromContext($op);
$this->questionAggregateService->checkUpdatePermission($surveyId);
$preparedData = $this->prepareSubQuestions(
$op,
$this->transformer,
$this->transformerL10ns,
$op->getProps(),
['subquestions']
);
//be careful here! if for any reason the incoming data is not prepared
//as it should, all existing subquestions will be deleted!
if (count($preparedData) === 0) {
throw new OpHandlerException('No data to create or update a subquestion');
}
$questionId = $op->getEntityId();
$this->subQuestionsService->save(
$this->questionService->getQuestionBySidAndQid(
$surveyId,
$questionId
),
$preparedData
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,5 +62,8 @@ public function __construct(FactoryInterface $diFactory, ContainerInterface $diC
$this->addOpHandler($diContainer->get(
OpHandlerAnswerDelete::class
));
$this->addOpHandler($diContainer->get(
OpHandlerSubQuestion::class
));
}
}

0 comments on commit f710b28

Please sign in to comment.