diff --git a/DemographicDataPlugin.php b/DemographicDataPlugin.php index f47cf61..0b53ea4 100644 --- a/DemographicDataPlugin.php +++ b/DemographicDataPlugin.php @@ -33,6 +33,7 @@ public function register($category, $path, $mainContextId = null): bool Hook::add('Schema::get::author', [$this, 'editAuthorSchema']); Hook::add('Schema::get::demographicQuestion', [$this, 'addCustomSchema']); Hook::add('Schema::get::demographicResponse', [$this, 'addCustomSchema']); + Hook::add('Schema::get::demographicResponseOption', [$this, 'addCustomSchema']); Hook::add('Decision::add', [$this, 'requestDataExternalContributors']); Hook::add('User::edit', [$this, 'checkMigrateResponsesOrcid']); diff --git a/classes/DemographicDataService.php b/classes/DemographicDataService.php index 2aa9d48..e707b43 100644 --- a/classes/DemographicDataService.php +++ b/classes/DemographicDataService.php @@ -26,14 +26,19 @@ public function retrieveAllQuestions(int $contextId, bool $shouldRetrieveRespons 'inputType' => $demographicQuestion->getQuestionInputType(), 'title' => $demographicQuestion->getLocalizedQuestionText(), 'description' => $demographicQuestion->getLocalizedQuestionDescription(), - 'possibleResponses' => $demographicQuestion->getLocalizedPossibleResponses() + 'responseOptions' => $demographicQuestion->getResponseOptions() ]; + if ($demographicQuestion->getQuestionType() == DemographicQuestion::TYPE_DROP_DOWN_BOX) { + $questionData['responseOptions'] = []; + foreach ($demographicQuestion->getResponseOptions() as $responseOption) { + $questionData['responseOptions'][$responseOption->getId()] = $responseOption->getLocalizedOptionText(); + } + } + if ($shouldRetrieveResponses) { $user = $request->getUser(); - $response = $this->getUserResponse($demographicQuestion, $user->getId()); - - $questionData['response'] = $response; + $questionData['response'] = $this->getUserResponse($demographicQuestion, $user->getId()); } $questions[] = $questionData; @@ -56,17 +61,17 @@ private function getUserResponse(DemographicQuestion $question, int $userId) $question->getQuestionType() == DemographicQuestion::TYPE_CHECKBOXES || $question->getQuestionType() == DemographicQuestion::TYPE_RADIO_BUTTONS ) { - return []; + return ['value' => [], 'optionsInputValue' => []]; } - return null; + return ['value' => null]; } $firstResponse = array_shift($demographicResponses); - return $firstResponse->getValue(); + return ['value' => $firstResponse->getValue(), 'optionsInputValue' => $firstResponse->getOptionsInputValue()]; } - public function registerUserResponses(int $userId, array $responses) + public function registerUserResponses(int $userId, array $responses, array $responseOptionsInputs) { foreach ($responses as $question => $responseInput) { $questionId = explode("-", $question)[1]; @@ -77,19 +82,47 @@ public function registerUserResponses(int $userId, array $responses) ->getMany() ->toArray(); $demographicResponse = array_shift($demographicResponses); + + $optionsInputValue = $this->getResponseOptionsInputValue($questionId, $responseOptionsInputs, $responseInput); + if ($demographicResponse) { - Repo::demographicResponse()->edit($demographicResponse, ['responseValue' => $responseInput]); + Repo::demographicResponse()->edit($demographicResponse, [ + 'responseValue' => $responseInput, + 'optionsInputValue' => $optionsInputValue + ]); } else { $response = Repo::demographicResponse()->newDataObject(); $response->setUserId($userId); $response->setDemographicQuestionId($questionId); $response->setData('responseValue', $responseInput); + $response->setOptionsInputValue($optionsInputValue); Repo::demographicResponse()->add($response); } } } - public function registerExternalAuthorResponses(string $externalId, string $externalType, array $responses) + private function getResponseOptionsInputValue($questionId, $responseOptionsInputs, $responseInput) + { + $demographicQuestion = Repo::demographicQuestion()->get($questionId); + + if ($demographicQuestion->getQuestionType() == DemographicQuestion::TYPE_CHECKBOXES + || $demographicQuestion->getQuestionType() == DemographicQuestion::TYPE_RADIO_BUTTONS + ) { + $responseOptionsInputValue = []; + foreach ($responseInput as $responseOptionId) { + $responseOptionInputName = "responseOptionInput-$responseOptionId"; + if (isset($responseOptionsInputs[$responseOptionInputName])) { + $responseOptionsInputValue[$responseOptionId] = $responseOptionsInputs[$responseOptionInputName]; + } + } + + return $responseOptionsInputValue; + } + + return null; + } + + public function registerExternalAuthorResponses(string $externalId, string $externalType, array $responses, array $responseOptionsInputs) { $locale = Locale::getLocale(); @@ -98,10 +131,13 @@ public function registerExternalAuthorResponses(string $externalId, string $exte $questionId = $questionParts[1]; $questionType = $questionParts[2]; + $optionsInputValue = $this->getResponseOptionsInputValue($questionId, $responseOptionsInputs, $responseInput); + $response = Repo::demographicResponse()->newDataObject(); $response->setDemographicQuestionId($questionId); $response->setExternalId($externalId); $response->setExternalType($externalType); + $response->setOptionsInputValue($optionsInputValue); if ($questionType == 'text' or $questionType == 'textarea') { $response->setData('responseValue', $responseInput, $locale); @@ -144,21 +180,29 @@ private function getResponseValueForDisplay($question, $response): string $question->getQuestionType() == DemographicQuestion::TYPE_CHECKBOXES || $question->getQuestionType() == DemographicQuestion::TYPE_RADIO_BUTTONS ) { - $possibleResponses = $question->getLocalizedPossibleResponses(); - $selectedResponsesValues = []; + $responseOptions = $question->getResponseOptions(); + $selectedResponseOptionsTexts = []; + + foreach ($response->getValue() as $selectedResponseOptionId) { + $selectedResponseOption = $responseOptions[$selectedResponseOptionId]; + $selectedResponseOptionsText = $selectedResponseOption->getLocalizedOptionText(); + + if ($selectedResponseOption->hasInputField()) { + $optionsInputValue = $response->getOptionsInputValue(); + $selectedResponseOptionsText .= ' "' . $optionsInputValue[$selectedResponseOptionId] . '"'; + } - foreach ($response->getValue() as $selectedResponse) { - $selectedResponsesValues[] = $possibleResponses[$selectedResponse]; + $selectedResponseOptionsTexts[] = $selectedResponseOptionsText; } - return implode(', ', $selectedResponsesValues); + return implode(', ', $selectedResponseOptionsTexts); } if ($question->getQuestionType() == DemographicQuestion::TYPE_DROP_DOWN_BOX) { - $possibleResponses = $question->getLocalizedPossibleResponses(); - $selectedResponse = $response->getValue(); + $responseOptions = $question->getResponseOptions(); + $selectedResponseOption = $responseOptions[$response->getValue()]; - return $possibleResponses[$selectedResponse]; + return $selectedResponseOption->getLocalizedOptionText(); } return ''; diff --git a/classes/demographicQuestion/DemographicQuestion.php b/classes/demographicQuestion/DemographicQuestion.php index 43a98fa..08c1804 100644 --- a/classes/demographicQuestion/DemographicQuestion.php +++ b/classes/demographicQuestion/DemographicQuestion.php @@ -2,6 +2,8 @@ namespace APP\plugins\generic\demographicData\classes\demographicQuestion; +use APP\plugins\generic\demographicData\classes\facades\Repo; + class DemographicQuestion extends \PKP\core\DataObject { public const TYPE_SMALL_TEXT_FIELD = 1; @@ -77,18 +79,21 @@ public function setQuestionDescription($descriptionText, $locale) $this->setData('questionDescription', $descriptionText, $locale); } - public function getPossibleResponses($locale) + public function getResponseOptions() { - return $this->getData('possibleResponses', $locale); - } + if (is_null($this->getData('responseOptions'))) { + $responseOptions = Repo::demographicResponseOption()->getCollector() + ->filterByQuestionIds([$this->getId()]) + ->getMany(); - public function setPossibleResponses($possibleResponses, $locale) - { - $this->setData('possibleResponses', $possibleResponses, $locale); - } + $mappedResponseOptions = []; + foreach ($responseOptions as $responseOption) { + $mappedResponseOptions[$responseOption->getId()] = $responseOption; + } - public function getLocalizedPossibleResponses() - { - return $this->getLocalizedData('possibleResponses'); + $this->setData('responseOptions', $mappedResponseOptions); + } + + return $this->getData('responseOptions'); } } diff --git a/classes/demographicResponse/DAO.php b/classes/demographicResponse/DAO.php index 74b6799..ea60720 100644 --- a/classes/demographicResponse/DAO.php +++ b/classes/demographicResponse/DAO.php @@ -76,6 +76,11 @@ public function fromRow(object $row): DemographicResponse $demographicResponse->setValue(unserialize($serializedValue)); } + if (@unserialize($demographicResponse->getOptionsInputValue())) { + $serializedValue = $demographicResponse->getOptionsInputValue(); + $demographicResponse->setOptionsInputValue(unserialize($serializedValue)); + } + return $demographicResponse; } } diff --git a/classes/demographicResponse/DemographicResponse.php b/classes/demographicResponse/DemographicResponse.php index 207f0fb..99617a7 100644 --- a/classes/demographicResponse/DemographicResponse.php +++ b/classes/demographicResponse/DemographicResponse.php @@ -53,4 +53,14 @@ public function setValue($responseValue) { $this->setData('responseValue', $responseValue); } + + public function getOptionsInputValue() + { + return $this->getData('optionsInputValue'); + } + + public function setOptionsInputValue($optionsInputValue) + { + $this->setData('optionsInputValue', $optionsInputValue); + } } diff --git a/classes/demographicResponseOption/Collector.php b/classes/demographicResponseOption/Collector.php new file mode 100644 index 0000000..0cb2062 --- /dev/null +++ b/classes/demographicResponseOption/Collector.php @@ -0,0 +1,52 @@ +dao = $dao; + } + + public function filterByQuestionIds(?array $questionIds): Collector + { + $this->questionIds = $questionIds; + return $this; + } + + public function getQueryBuilder(): Builder + { + $queryBuilder = DB::table($this->dao->table . ' AS dro') + ->select(['dro.*']); + + if (isset($this->questionIds)) { + $queryBuilder->whereIn('dro.demographic_question_id', $this->questionIds); + } + + return $queryBuilder; + } + + public function getCount(): int + { + return $this->dao->getCount($this); + } + + public function getIds(): Collection + { + return $this->dao->getIds($this); + } + + public function getMany(): LazyCollection + { + return $this->dao->getMany($this); + } +} diff --git a/classes/demographicResponseOption/DAO.php b/classes/demographicResponseOption/DAO.php new file mode 100644 index 0000000..2787c1c --- /dev/null +++ b/classes/demographicResponseOption/DAO.php @@ -0,0 +1,71 @@ + 'demographic_response_option_id', + 'demographicQuestionId' => 'demographic_question_id', + ]; + + public function getParentColumn(): string + { + return 'demographic_question_id'; + } + + public function newDataObject(): DemographicResponseOption + { + return app(DemographicResponseOption::class); + } + + public function insert(DemographicResponseOption $demographicResponseOption): int + { + return parent::_insert($demographicResponseOption); + } + + public function delete(DemographicResponseOption $demographicResponseOption) + { + return parent::_delete($demographicResponseOption); + } + + public function update(DemographicResponseOption $demographicResponseOption) + { + return parent::_update($demographicResponseOption); + } + + public function getCount(Collector $query): int + { + return $query + ->getQueryBuilder() + ->count(); + } + + public function getMany(Collector $query): LazyCollection + { + $rows = $query + ->getQueryBuilder() + ->get(); + + return LazyCollection::make(function () use ($rows) { + foreach ($rows as $row) { + yield $row->demographic_response_option_id => $this->fromRow($row); + } + }); + } + + public function fromRow(object $row): DemographicResponseOption + { + return parent::fromRow($row); + } +} diff --git a/classes/demographicResponseOption/DemographicResponseOption.php b/classes/demographicResponseOption/DemographicResponseOption.php new file mode 100644 index 0000000..d889985 --- /dev/null +++ b/classes/demographicResponseOption/DemographicResponseOption.php @@ -0,0 +1,36 @@ +getData('demographicQuestionId'); + } + + public function setDemographicQuestionId($demographicQuestionId) + { + $this->setData('demographicQuestionId', $demographicQuestionId); + } + + public function getLocalizedOptionText() + { + return $this->getLocalizedData('optionText'); + } + + public function setOptionText(string $text, string $locale) + { + $this->setData('optionText', $text, $locale); + } + + public function hasInputField(): bool + { + return $this->getData('hasInputField'); + } + + public function setHasInputField(bool $hasInputField) + { + $this->setData('hasInputField', $hasInputField); + } +} diff --git a/classes/demographicResponseOption/Repository.php b/classes/demographicResponseOption/Repository.php new file mode 100644 index 0000000..810d443 --- /dev/null +++ b/classes/demographicResponseOption/Repository.php @@ -0,0 +1,58 @@ +dao = $dao; + } + + public function newDataObject(array $params = []): DemographicResponseOption + { + $object = $this->dao->newDataObject(); + if (!empty($params)) { + $object->setAllData($params); + } + return $object; + } + + public function get(int $id, int $demographicQuestionId = null): ?DemographicResponseOption + { + return $this->dao->get($id, $demographicQuestionId); + } + + public function add(DemographicResponseOption $demographicResponseOption): int + { + $id = $this->dao->insert($demographicResponseOption); + return $id; + } + + public function edit(DemographicResponseOption $demographicResponseOption, array $params) + { + $newDemographicResponseOption = clone $demographicResponseOption; + $newDemographicResponseOption->setAllData(array_merge($newDemographicResponseOption->_data, $params)); + + $this->dao->update($newDemographicResponseOption); + } + + public function delete(DemographicResponseOption $demographicResponseOption) + { + $this->dao->delete($demographicResponseOption); + } + + public function exists(int $id, int $demographicQuestionId = null): bool + { + return $this->dao->exists($id, $demographicQuestionId); + } + + public function getCollector(): Collector + { + return app(Collector::class); + } +} diff --git a/classes/facades/Repo.php b/classes/facades/Repo.php index f5fc9d4..f8fc41c 100644 --- a/classes/facades/Repo.php +++ b/classes/facades/Repo.php @@ -4,6 +4,7 @@ use APP\plugins\generic\demographicData\classes\demographicQuestion\Repository as DemographicQuestionRepository; use APP\plugins\generic\demographicData\classes\demographicResponse\Repository as DemographicResponseRepository; +use APP\plugins\generic\demographicData\classes\demographicResponseOption\Repository as DemographicResponseOptionRepository; class Repo extends \APP\facades\Repo { @@ -16,4 +17,9 @@ public static function demographicResponse(): DemographicResponseRepository { return app(DemographicResponseRepository::class); } + + public static function demographicResponseOption(): DemographicResponseOptionRepository + { + return app(DemographicResponseOptionRepository::class); + } } diff --git a/classes/form/QuestionsForm.php b/classes/form/QuestionsForm.php index 0270b2f..f2945a5 100644 --- a/classes/form/QuestionsForm.php +++ b/classes/form/QuestionsForm.php @@ -35,13 +35,18 @@ public function __construct($request = null, $args = null) private function loadQuestionResponsesByForm($args) { $responses = []; + $responseOptionsInputs = []; + foreach ($args as $key => $value) { if (strpos($key, 'question-') === 0) { $responses[$key] = $value; + } elseif (strpos($key, 'responseOptionInput-') === 0) { + $responseOptionsInputs[$key] = $value; } } $this->setData('responses', $responses); + $this->setData('responseOptionsInputs', $responseOptionsInputs); } public function fetch($request, $template = null, $display = false) @@ -98,7 +103,7 @@ public function execute(...$functionArgs) $demographicDataDao->updateDemographicConsent($context->getId(), $user->getId(), $newConsent); if ($newConsent == '1') { - $demographicDataService->registerUserResponses($user->getId(), $this->getData('responses')); + $demographicDataService->registerUserResponses($user->getId(), $this->getData('responses'), $this->getData('responseOptionsInputs')); } elseif ($newConsent == '0' and $previousConsent) { $demographicDataService->deleteUserResponses($user->getId(), $context->getId()); } diff --git a/classes/migrations/SchemaMigration.php b/classes/migrations/SchemaMigration.php index cecfe54..6141197 100644 --- a/classes/migrations/SchemaMigration.php +++ b/classes/migrations/SchemaMigration.php @@ -29,12 +29,42 @@ public function up(): void $table->string('setting_name', 255); $table->longText('setting_value')->nullable(); + $table->foreign('demographic_question_id') + ->references('demographic_question_id') + ->on('demographic_questions') + ->onDelete('cascade'); $table->index(['demographic_question_id'], 'demographic_question_settings_id'); $table->unique(['demographic_question_id', 'locale', 'setting_name'], 'demographic_question_settings_pkey'); }); + Schema::create('demographic_response_options', function (Blueprint $table) { + $table->bigInteger('demographic_response_option_id')->autoIncrement(); + $table->bigInteger('demographic_question_id'); + + $table->foreign('demographic_question_id') + ->references('demographic_question_id') + ->on('demographic_questions') + ->onDelete('cascade'); + $table->index(['demographic_question_id'], 'demographic_response_options_demographic_question_id'); + }); + + Schema::create('demographic_response_option_settings', function (Blueprint $table) { + $table->bigIncrements('demographic_response_option_setting_id'); + $table->bigInteger('demographic_response_option_id'); + $table->string('locale', 14)->default(''); + $table->string('setting_name', 255); + $table->longText('setting_value')->nullable(); + + $table->foreign('demographic_response_option_id', 'demographic_response_option_settings_option_id') + ->references('demographic_response_option_id') + ->on('demographic_response_options') + ->onDelete('cascade'); + $table->index(['demographic_response_option_id'], 'demographic_response_option_settings_option_id'); + $table->unique(['demographic_response_option_id', 'locale', 'setting_name'], 'demographic_response_option_settings_pkey'); + }); + Schema::create('demographic_responses', function (Blueprint $table) { - $table->bigIncrements('demographic_response_id'); + $table->bigInteger('demographic_response_id')->autoIncrement(); $table->bigInteger('demographic_question_id'); $table->bigInteger('user_id')->nullable(); $table->string('external_id', 255)->nullable(); @@ -60,6 +90,10 @@ public function up(): void $table->string('setting_name', 255); $table->longText('setting_value')->nullable(); + $table->foreign('demographic_response_id') + ->references('demographic_response_id') + ->on('demographic_responses') + ->onDelete('cascade'); $table->index(['demographic_response_id'], 'demographic_response_setting_id'); $table->unique(['demographic_response_id', 'locale', 'setting_name'], 'demographic_response_settings_pkey'); }); diff --git a/classes/test/DefaultTestQuestionsCreator.php b/classes/test/DefaultTestQuestionsCreator.php index d6f8c69..db551fd 100644 --- a/classes/test/DefaultTestQuestionsCreator.php +++ b/classes/test/DefaultTestQuestionsCreator.php @@ -27,7 +27,15 @@ public function createDefaultTestQuestions() foreach ($defaultTestQuestions as $questionData) { $questionObject = Repo::demographicQuestion()->newDataObject($questionData); - Repo::demographicQuestion()->add($questionObject); + $demographicQuestionId = Repo::demographicQuestion()->add($questionObject); + + if (isset($questionData['responseOptions'])) { + foreach ($questionData['responseOptions'] as $optionData) { + $optionData['demographicQuestionId'] = $demographicQuestionId; + $responseOptionObject = Repo::demographicResponseOption()->newDataObject($optionData); + Repo::demographicResponseOption()->add($responseOptionObject); + } + } } } } @@ -58,9 +66,35 @@ private function getDefaultTestQuestionsData(int $contextId): array 'questionType' => DemographicQuestion::TYPE_CHECKBOXES, 'questionText' => ['en' => 'Languages'], 'questionDescription' => ['en' => 'Which of these languages do you speak?'], - 'possibleResponses' => [ - 'en' => ['English', 'French', 'Hindi', 'Mandarin', 'Portuguese', 'Spanish'], - 'fr_CA' => ['Anglais', 'Français', 'Hindi', 'Mandarin', 'Portugais', 'Espagnol'] + 'responseOptions' => [ + [ + 'optionText' => ['en' => 'English', 'fr_CA' => 'Anglais'], + 'hasInputField' => false + ], + [ + 'optionText' => ['en' => 'French', 'fr_CA' => 'Français'], + 'hasInputField' => false + ], + [ + 'optionText' => ['en' => 'Hindi', 'fr_CA' => 'Hindi'], + 'hasInputField' => false + ], + [ + 'optionText' => ['en' => 'Mandarin', 'fr_CA' => 'Mandarin'], + 'hasInputField' => false + ], + [ + 'optionText' => ['en' => 'Portuguese', 'fr_CA' => 'Portugais'], + 'hasInputField' => false + ], + [ + 'optionText' => ['en' => 'Spanish', 'fr_CA' => 'Espagnol'], + 'hasInputField' => false + ], + [ + 'optionText' => ['en' => 'Other:', 'fr_CA' => 'Autre:'], + 'hasInputField' => true + ] ] ], [ @@ -68,9 +102,27 @@ private function getDefaultTestQuestionsData(int $contextId): array 'questionType' => DemographicQuestion::TYPE_RADIO_BUTTONS, 'questionText' => ['en' => 'Nacionality'], 'questionDescription' => ['en' => 'Which continent are you from?'], - 'possibleResponses' => [ - 'en' => ['Africa', 'America', 'Asia', 'Europe', 'Oceania'], - 'fr_CA' => ['Afrique', 'Amérique', 'Asie', 'Europe', 'Océanie'] + 'responseOptions' => [ + [ + 'optionText' => ['en' => 'Africa', 'fr_CA' => 'Afrique'], + 'hasInputField' => false + ], + [ + 'optionText' => ['en' => 'America', 'fr_CA' => 'Amérique'], + 'hasInputField' => false + ], + [ + 'optionText' => ['en' => 'Asia', 'fr_CA' => 'Asie'], + 'hasInputField' => false + ], + [ + 'optionText' => ['en' => 'Europe', 'fr_CA' => 'Europe'], + 'hasInputField' => false + ], + [ + 'optionText' => ['en' => 'Oceania', 'fr_CA' => 'Océanie'], + 'hasInputField' => false + ] ] ], [ @@ -78,18 +130,34 @@ private function getDefaultTestQuestionsData(int $contextId): array 'questionType' => DemographicQuestion::TYPE_DROP_DOWN_BOX, 'questionText' => ['en' => 'Salary'], 'questionDescription' => ['en' => 'What range is your current salary in?'], - 'possibleResponses' => [ - 'en' => [ - 'Less than a minimum wage', - 'One to three minimum wages', - 'Three to five minimum wages', - 'More than five minimum wages' + 'responseOptions' => [ + [ + 'optionText' => [ + 'en' => 'Less than a minimum wage', + 'fr_CA' => "Moins qu'un salaire minimum" + ], + 'hasInputField' => false + ], + [ + 'optionText' => [ + 'en' => 'One to three minimum wages', + 'fr_CA' => 'Un à trois salaires minimums' + ], + 'hasInputField' => false + ], + [ + 'optionText' => [ + 'en' => 'Three to five minimum wages', + 'fr_CA' => 'Trois à cinq salaires minimums' + ], + 'hasInputField' => false ], - 'fr_CA' => [ - "Moins qu'un salaire minimum", - 'Un à trois salaires minimums', - 'Trois à cinq salaires minimums', - 'Plus de cinq salaires minimums' + [ + 'optionText' => [ + 'en' => 'More than five minimum wages', + 'fr_CA' => 'Plus de cinq salaires minimums' + ], + 'hasInputField' => false ] ] ] diff --git a/cypress/tests/Test1_questionsDisplaying.cy.js b/cypress/tests/Test1_questionsDisplaying.cy.js index 6fb70bd..c3e2690 100644 --- a/cypress/tests/Test1_questionsDisplaying.cy.js +++ b/cypress/tests/Test1_questionsDisplaying.cy.js @@ -17,6 +17,7 @@ function assertDefaultQuestionsDisplay() { cy.contains('Mandarin'); cy.contains('Portuguese'); cy.contains('Spanish'); + cy.contains('Other:'); cy.contains('label', 'Nacionality'); cy.contains('.description', 'Which continent are you from?'); @@ -47,6 +48,10 @@ function answerDefaultQuestions() { cy.contains('label', 'Spanish').within(() => { cy.get('input').check(); }); + cy.contains('label', 'Other:').parent().parent().within(() => { + cy.get('input[type="checkbox"]').check(); + cy.get('input[type="text"]').clear().type('Japanese'); + }); cy.contains('label', 'America').within(() => { cy.get('input').check(); }); @@ -70,6 +75,10 @@ function assertResponsesToDefaultQuestions() { cy.contains('label', 'Spanish').within(() => { cy.get('input').should('be.checked'); }); + cy.contains('label', 'Other:').parent().parent().within(() => { + cy.get('input[type="checkbox"]').should('be.checked'); + cy.get('input[type="text"]').should('have.value', 'Japanese'); + }); cy.contains('label', 'America').within(() => { cy.get('input').should('be.checked'); }); @@ -83,6 +92,10 @@ function assertResponsesToQuestionsInFrench() { cy.contains('label', 'Espagnol').within(() => { cy.get('input').should('be.checked'); }); + cy.contains('label', 'Autre:').parent().parent().within(() => { + cy.get('input[type="checkbox"]').should('be.checked'); + cy.get('input[type="text"]').should('have.value', 'Japanese'); + }); cy.contains('label', 'Amérique').within(() => { cy.get('input').should('be.checked'); }); diff --git a/cypress/tests/Test2_externalContributors.cy.js b/cypress/tests/Test2_externalContributors.cy.js index 37b26e1..e048794 100644 --- a/cypress/tests/Test2_externalContributors.cy.js +++ b/cypress/tests/Test2_externalContributors.cy.js @@ -51,6 +51,10 @@ function answerDefaultQuestions() { cy.contains('label', 'Spanish').within(() => { cy.get('input').check(); }); + cy.contains('label', 'Other:').parent().within(() => { + cy.get('input[type="checkbox"]').check(); + cy.get('input[type="text"]').clear().type('Japanese'); + }); cy.contains('label', 'America').within(() => { cy.get('input').check(); }); @@ -66,7 +70,7 @@ function assertResponsesOfExternalAuthor(authorEmail) { cy.contains('Latin'); cy.contains('University of São Paulo'); cy.contains('University of Minas Gerais'); - cy.contains('English, Spanish'); + cy.contains('English, Spanish, Other: "Japanese"'); cy.contains('America'); cy.contains('Three to five minimum wages'); @@ -88,6 +92,10 @@ function assertResponsesOfRegisteredUser() { cy.contains('label', 'Spanish').within(() => { cy.get('input').should('be.checked'); }); + cy.contains('label', 'Other:').parent().parent().within(() => { + cy.get('input[type="checkbox"]').check(); + cy.get('input[type="text"]').clear().type('Japanese'); + }); cy.contains('label', 'America').within(() => { cy.get('input').should('be.checked'); }); diff --git a/pages/demographic/QuestionnaireHandler.php b/pages/demographic/QuestionnaireHandler.php index 8a39467..fb311d3 100644 --- a/pages/demographic/QuestionnaireHandler.php +++ b/pages/demographic/QuestionnaireHandler.php @@ -112,9 +112,12 @@ public function saveQuestionnaire($args, $request) } $responses = []; + $responseOptionsInputs = []; foreach ($request->getUserVars() as $key => $value) { if (strpos($key, 'question-') === 0) { $responses[$key] = $value; + } elseif (strpos($key, 'responseOptionInput-') === 0) { + $responseOptionsInputs[$key] = $value; } } @@ -127,7 +130,7 @@ public function saveQuestionnaire($args, $request) } $demographicDataService = new DemographicDataService(); - $demographicDataService->registerExternalAuthorResponses($responsesExternalId, $responsesExternalType, $responses); + $demographicDataService->registerExternalAuthorResponses($responsesExternalId, $responsesExternalType, $responses, $responseOptionsInputs); $templateMgr->assign([ 'authorId' => $author->getId(), diff --git a/schemas/demographicQuestion.json b/schemas/demographicQuestion.json index 63e9a7d..a030ec2 100644 --- a/schemas/demographicQuestion.json +++ b/schemas/demographicQuestion.json @@ -19,16 +19,6 @@ "questionDescription": { "type": "string", "multilingual": true - }, - "possibleResponses": { - "type": "array", - "items": { - "type": "string" - }, - "multilingual": true, - "validation": [ - "nullable" - ] } } } diff --git a/schemas/demographicResponse.json b/schemas/demographicResponse.json index d6deede..808cd4a 100644 --- a/schemas/demographicResponse.json +++ b/schemas/demographicResponse.json @@ -32,6 +32,12 @@ "validation": [ "nullable" ] + }, + "optionsInputValue": { + "type": "string", + "validation": [ + "nullable" + ] } } } diff --git a/schemas/demographicResponseOption.json b/schemas/demographicResponseOption.json new file mode 100644 index 0000000..71ff3ed --- /dev/null +++ b/schemas/demographicResponseOption.json @@ -0,0 +1,20 @@ +{ + "title": "Demographic response option", + "description": "A response option for a given demographic question", + "properties": { + "id": { + "type": "integer", + "readOnly": true + }, + "demographicQuestionId": { + "type": "integer" + }, + "optionText": { + "type": "string", + "multilingual": true + }, + "hasInputField": { + "type": "boolean" + } + } +} diff --git a/styles/questionsInProfile.css b/styles/questionsInProfile.css new file mode 100644 index 0000000..bda628e --- /dev/null +++ b/styles/questionsInProfile.css @@ -0,0 +1,8 @@ +.responseOption { + display: flex; +} + +.responseOption input[type="text"] { + margin-left: 1rem; + height: 1.5rem; +} \ No newline at end of file diff --git a/templates/question.tpl b/templates/question.tpl index 7f5869a..1a1fd8a 100644 --- a/templates/question.tpl +++ b/templates/question.tpl @@ -1,3 +1,5 @@ + + {assign var="questionId" value="question-{$question['questionId']}-{$question['inputType']}"} {if $question['type'] == $questionTypeConsts['TYPE_CHECKBOXES'] or $question['type'] == $questionTypeConsts['TYPE_RADIO_BUTTONS']} {assign var="isListSection" value=true} @@ -6,38 +8,64 @@ {fbvFormSection title=$question['title'] required=true translate=false} {fbvFormSection for=$questionId description=$question['description'] translate=false list=$isListSection} {if $question['type'] == $questionTypeConsts['TYPE_SMALL_TEXT_FIELD']} - {fbvElement type="text" multilingual="true" name=$questionId id="demographicResponses" value=$question['response'] required=true size=$fbvStyles.size.SMALL} + {fbvElement type="text" multilingual="true" name=$questionId id="demographicResponses" value=$question['response']['value'] required=true size=$fbvStyles.size.SMALL} {elseif $question['type'] == $questionTypeConsts['TYPE_TEXT_FIELD']} - {fbvElement type="text" multilingual="true" name=$questionId id="demographicResponses" value=$question['response'] required=true size=$fbvStyles.size.LARGE} + {fbvElement type="text" multilingual="true" name=$questionId id="demographicResponses" value=$question['response']['value'] required=true size=$fbvStyles.size.LARGE} {elseif $question['type'] == $questionTypeConsts['TYPE_TEXTAREA']} - {fbvElement type="textarea" multilingual="true" name=$questionId id="demographicResponses" value=$question['response'] required=true rich=false size=$fbvStyles.size.LARGE} + {fbvElement type="textarea" multilingual="true" name=$questionId id="demographicResponses" value=$question['response']['value'] required=true rich=false size=$fbvStyles.size.LARGE} {elseif $question['type'] == $questionTypeConsts['TYPE_CHECKBOXES']} - {foreach from=$question['possibleResponses'] key="possibleResponseValue" item="possibleResponseLabel"} - {fbvElement - type="checkbox" - name="{$questionId}[]" - id="demographicResponses" - label=$possibleResponseLabel - value=$possibleResponseValue - checked=in_array($possibleResponseValue, $question['response']) - translate=false - } + {foreach from=$question['responseOptions'] item="responseOption"} +