diff --git a/CANCELED b/CANCELED new file mode 100644 index 00000000000..e69de29bb2d diff --git a/CHANGELOG.md b/CHANGELOG.md index a4fc1151955..b9c75f69d4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ changes in the following format: PR #1234*** #### Bug Fixes - Fix examiner site display (PR #8967) - bvl_feedback updates in real-time (PR #8966) +- DoB and DoD format respected in candidate parameters (PR #9001) ## LORIS 25.0 (Release Date: ????-??-??) ### Core diff --git a/SQL/0000-00-00-schema.sql b/SQL/0000-00-00-schema.sql index 5e15eba00d9..979a87e47c3 100644 --- a/SQL/0000-00-00-schema.sql +++ b/SQL/0000-00-00-schema.sql @@ -2278,7 +2278,7 @@ CREATE TABLE `consent` ( CREATE TABLE `candidate_consent_rel` ( `CandidateID` int(6) NOT NULL, `ConsentID` integer unsigned NOT NULL, - `Status` enum('yes','no') DEFAULT NULL, + `Status` enum('yes','no', 'not_applicable') DEFAULT NULL, `DateGiven` date DEFAULT NULL, `DateWithdrawn` date DEFAULT NULL, CONSTRAINT `PK_candidate_consent_rel` PRIMARY KEY (`CandidateID`,`ConsentID`), @@ -2294,7 +2294,7 @@ CREATE TABLE `candidate_consent_history` ( `PSCID` varchar(255) NOT NULL, `ConsentName` varchar(255) NOT NULL, `ConsentLabel` varchar(255) NOT NULL, - `Status` enum('yes','no') DEFAULT NULL, + `Status` enum('yes','no', 'not_applicable') DEFAULT NULL, `EntryStaff` varchar(255) DEFAULT NULL, CONSTRAINT `PK_candidate_consent_history` PRIMARY KEY (`CandidateConsentHistoryID`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; diff --git a/SQL/New_patches/2023-06-06-add_NA_to_consent_status.sql b/SQL/New_patches/2023-06-06-add_NA_to_consent_status.sql new file mode 100644 index 00000000000..185e13b8a06 --- /dev/null +++ b/SQL/New_patches/2023-06-06-add_NA_to_consent_status.sql @@ -0,0 +1,2 @@ +ALTER TABLE candidate_consent_rel MODIFY COLUMN `Status` enum('yes', 'no', 'not_applicable') DEFAULT NULL; +ALTER TABLE candidate_consent_history MODIFY COLUMN `Status` enum('yes', 'no', 'not_applicable') DEFAULT NULL; \ No newline at end of file diff --git a/a b/a new file mode 100644 index 00000000000..e69de29bb2d diff --git a/modules/candidate_list/php/candidatelistrowprovisioner.class.inc b/modules/candidate_list/php/candidatelistrowprovisioner.class.inc index 33d2a799411..55ce300b407 100644 --- a/modules/candidate_list/php/candidatelistrowprovisioner.class.inc +++ b/modules/candidate_list/php/candidatelistrowprovisioner.class.inc @@ -30,6 +30,7 @@ namespace LORIS\candidate_list; */ class CandidateListRowProvisioner extends \LORIS\Data\Provisioners\DBRowProvisioner { + private string $dobFormat; /** * Create a CandidateListRowProvisioner, which gets rows for * the candidate_list menu table. @@ -43,6 +44,8 @@ class CandidateListRowProvisioner extends \LORIS\Data\Provisioners\DBRowProvisio if ($config->getSetting("useEDC") === "true") { $maybeEDC = ", DATE_FORMAT((c.EDC),'%Y-%m-%d') AS EDC"; } + $this->dobFormat = $config->getSetting("dobFormat"); + parent::__construct( "SELECT c.PSCID, @@ -98,6 +101,19 @@ class CandidateListRowProvisioner extends \LORIS\Data\Provisioners\DBRowProvisio // one of the user's sites. $cid = new \CenterID($row['RegistrationCenterID']); $pid = new \ProjectID($row['RegistrationProjectID']); + if ($row['DoB'] !== null) { + $dob = new \DateTimeImmutable($row['DoB']); + switch ($this->dobFormat) { + case 'Ym': + $row['DoB'] = $dob->format('Y-m'); + break; + case 'Ymd': + $row['DoB'] = $dob->format('Y-m-d'); + break; + default: + throw new \Exception("Unhandled DoB format: $this->dobFormat"); + } + } unset($row['RegistrationCenterID']); unset($row['RegistrationProjectID']); return new CandidateListRow($row, $cid, $pid); diff --git a/modules/candidate_parameters/ajax/formHandler.php b/modules/candidate_parameters/ajax/formHandler.php index f600bfe247f..70783909eac 100644 --- a/modules/candidate_parameters/ajax/formHandler.php +++ b/modules/candidate_parameters/ajax/formHandler.php @@ -501,6 +501,10 @@ function editConsentStatusFields(\Database $db) ) { // Withdrawing from 'yes' status required consent date // and withdrawal date $validated = true; + } else if ($oldStatus === 'not_applicable' && !empty($date) + && empty($withdrawal) + ) { // Add N/A option + $validated = true; } else { http_response_code(400); echo('Data failed validation. Resolve errors and try again.'); @@ -508,6 +512,19 @@ function editConsentStatusFields(\Database $db) } } break; + case 'not_applicable': + // If status is N/A, date is not required. + if (empty($date) && empty($withdrawal) + && ($oldStatus !== 'yes' || $oldStatus !== 'no') + ) { + $validated = true; + } else { + http_response_code(400); + echo('Answering not applicable to a consent type + does not require a date of consent.'); + return; + } + break; default: // If status is empty, and date fields are also empty, // validated is still false diff --git a/modules/candidate_parameters/ajax/getData.php b/modules/candidate_parameters/ajax/getData.php index d641c730a78..b3fadc87803 100644 --- a/modules/candidate_parameters/ajax/getData.php +++ b/modules/candidate_parameters/ajax/getData.php @@ -524,10 +524,22 @@ function getDOBFields(): array ); $pscid = $candidateData['PSCID'] ?? null; $dob = $candidateData['DoB'] ?? null; - $result = [ - 'pscid' => $pscid, - 'candID' => $candID->__toString(), - 'dob' => $dob, + + // Get DoB format + $factory = \NDB_Factory::singleton(); + $config = $factory->config(); + + $dobFormat = $config->getSetting('dobFormat'); + + $dobProcessedFormat = implode("-", str_split($dobFormat, 1)); + $dobDate = DateTime::createFromFormat('Y-m-d', $dob); + $formattedDate = $dobDate ? $dobDate->format($dobProcessedFormat) : null; + + $result = [ + 'pscid' => $pscid, + 'candID' => $candID->__toString(), + 'dob' => $formattedDate, + 'dobFormat' => $dobFormat, ]; return $result; } @@ -549,11 +561,30 @@ function getDODFields(): array if ($candidateData === null) { throw new \LorisException("Invalid candidate"); } + + $factory = \NDB_Factory::singleton(); + $config = $factory->config(); + + // Get formatted dod + $dodFormat = $config->getSetting('dodFormat'); + + $dodProcessedFormat = implode("-", str_split($dodFormat, 1)); + $dodDate = DateTime::createFromFormat('Y-m-d', $candidateData['DoD']); + $dod = $dodDate ? $dodDate->format($dodProcessedFormat) : null; + + // Get formatted dob + $dobFormat = $config->getSetting('dobFormat'); + + $dobProcessedFormat = implode("-", str_split($dobFormat, 1)); + $dobDate = DateTime::createFromFormat('Y-m-d', $candidateData['DoB']); + $dob = $dobDate ? $dobDate->format($dobProcessedFormat) : null; + $result = [ - 'pscid' => $candidateData['PSCID'], - 'candID' => $candID->__toString(), - 'dod' => $candidateData['DoD'], - 'dob' => $candidateData['DoB'], + 'pscid' => $candidateData['PSCID'], + 'candID' => $candID->__toString(), + 'dod' => $dod, + 'dob' => $dob, + 'dodFormat' => $config->getSetting('dodFormat'), ]; return $result; } diff --git a/modules/candidate_parameters/jsx/CandidateDOB.js b/modules/candidate_parameters/jsx/CandidateDOB.js index 2cda15a7c06..e0914da8a7d 100644 --- a/modules/candidate_parameters/jsx/CandidateDOB.js +++ b/modules/candidate_parameters/jsx/CandidateDOB.js @@ -85,6 +85,7 @@ class CandidateDOB extends Component { return ; } + let dateFormat = this.state.data.dobFormat; let disabled = true; let updateButton = null; if (loris.userHasPermission('candidate_dob_edit')) { @@ -116,6 +117,7 @@ class CandidateDOB extends Component { ; } + let dateFormat = this.state.data.dodFormat; let disabled = true; let updateButton = null; if (loris.userHasPermission('candidate_dod_edit')) { @@ -114,6 +115,7 @@ class CandidateDOD extends Component { updated for {label}: Status to {' '} - {this.state.consentOptions[consentStatus]} + {consentStatus} {dateHistory} {withdrawalHistory}

@@ -354,6 +365,7 @@ class ConsentStatus extends Component { // Set up elements const label = this.state.Data.consents[consentName]; + const consentOptions = consentName + '_options'; const statusLabel = 'Response'; const consentDate = consentName + '_date'; const consentDate2 = consentName + '_date2'; @@ -373,7 +385,7 @@ class ConsentStatus extends Component { tpl_data['candidate'] = $this->candidate; $this->tpl_data['candID'] = $this->candidate->getCandID(); $this->tpl_data['sessionID'] = $this->timePoint->getSessionID(); $this->tpl_data['stage'] = \Utility::getStageUsingCandID( diff --git a/modules/instrument_list/templates/menu_instrument_list.tpl b/modules/instrument_list/templates/menu_instrument_list.tpl index 02239599d0b..a9a4bdf8bdf 100644 --- a/modules/instrument_list/templates/menu_instrument_list.tpl +++ b/modules/instrument_list/templates/menu_instrument_list.tpl @@ -3,8 +3,9 @@ + {assign var="DoB" value=$candidate->getDisplayDoB()} - DOB + {$DoB['label']} {if $display.EDC!=""} @@ -57,7 +58,7 @@ - {$display.DoB} + {$DoB['value']} {if $display.EDC!=""} diff --git a/modules/my_preferences/php/my_preferences.class.inc b/modules/my_preferences/php/my_preferences.class.inc index 32b8eb01cba..b95aedd7c7e 100644 --- a/modules/my_preferences/php/my_preferences.class.inc +++ b/modules/my_preferences/php/my_preferences.class.inc @@ -349,6 +349,25 @@ class My_Preferences extends \NDB_Form $this->tpl_data['notification_rows'] = $notification_rows; //------------------------------------------------------------ + $widgets = []; + $modules = $this->loris->getActiveModules(); + foreach ($modules as $module) { + if ($module->hasAccess($user)) { + $mwidgets = $module->getWidgets( + 'userpreference', + $user, + [], + ); + foreach ($mwidgets as $widget) { + if (!($widget instanceof UserPreferenceWidget)) { + continue; + } + $widgets[] = $widget; + } + } + } + $this->tpl_data['module_userpreference_widgets'] = $widgets; + // unique key and password rules $this->form->addFormRule([&$this, '_validateMyPreferences']); } @@ -468,6 +487,7 @@ class My_Preferences extends \NDB_Form [ $baseurl . '/js/passwordVisibility.js', $baseurl . '/my_preferences/js/my_preferences_helper.js', + $baseurl . '/js/components/CSSGrid.js', ] ); } diff --git a/modules/my_preferences/php/userpreferencewidget.class.inc b/modules/my_preferences/php/userpreferencewidget.class.inc new file mode 100644 index 00000000000..b5c8316d738 --- /dev/null +++ b/modules/my_preferences/php/userpreferencewidget.class.inc @@ -0,0 +1,42 @@ +jsurl; + } +} diff --git a/modules/my_preferences/templates/form_my_preferences.tpl b/modules/my_preferences/templates/form_my_preferences.tpl index 78015da0bcf..7a1534f6740 100644 --- a/modules/my_preferences/templates/form_my_preferences.tpl +++ b/modules/my_preferences/templates/form_my_preferences.tpl @@ -1,4 +1,3 @@ -

Password Rules

    @@ -89,6 +88,26 @@ {/foreach} + {* Add any preferences registered from other modules *} +
    + {section name=widget loop=$module_userpreference_widgets} + {assign var="widget" value=$module_userpreference_widgets[widget]} +
    +

    {$widget->title}

    + {* Include the widget's javascript before trying to invoke it *} + + + {* Create a react root to render it into *} + +
    + {/section} +
    diff --git a/modules/timepoint_list/php/timepoint_list.class.inc b/modules/timepoint_list/php/timepoint_list.class.inc index 107ffcf5806..5f5ba4db493 100644 --- a/modules/timepoint_list/php/timepoint_list.class.inc +++ b/modules/timepoint_list/php/timepoint_list.class.inc @@ -80,6 +80,7 @@ class Timepoint_List extends \NDB_Menu $this->tpl_data['candID'] = $this->candID; $this->tpl_data['PSCID'] = $candidate->getPSCID(); $this->tpl_data['candidate'] = $candidate->getData(); + $this->tpl_data['cand'] = $candidate; $listOfSessionIDs = $candidate->getListOfTimePoints(); diff --git a/modules/timepoint_list/templates/menu_timepoint_list.tpl b/modules/timepoint_list/templates/menu_timepoint_list.tpl index 961018b781d..197a4bd985e 100644 --- a/modules/timepoint_list/templates/menu_timepoint_list.tpl +++ b/modules/timepoint_list/templates/menu_timepoint_list.tpl @@ -4,7 +4,8 @@ - DOB + {assign var="DoB" value=$cand->getDisplayDoB()} + {$DoB['label']} {if $candidate.EDC!=""} @@ -28,7 +29,7 @@ - {$candidate.DoB} + {$DoB['value']} {if $candidate.EDC!=""} diff --git a/php/libraries/Candidate.class.inc b/php/libraries/Candidate.class.inc index 4073bb90a8c..36fb44d1e9a 100644 --- a/php/libraries/Candidate.class.inc +++ b/php/libraries/Candidate.class.inc @@ -624,6 +624,40 @@ class Candidate implements \LORIS\StudyEntities\AccessibleResource, return $this->candidateInfo["DoB"]; } + /** + * Returns the way that the DoB should be displayed for this + * candidate to a user on the frontend. + * + * @return ?array{label:string,value:string} + */ + function getDisplayDoB(): ?array + { + if ($this->getCandidateDob() === null) { + return null; + } + + $factory = NDB_Factory::singleton(); + $config = $factory->config(); + + $dobFormat = $config->getSetting('dobFormat'); + + $dobDate = new DateTimeImmutable($this->getCandidateDoB()); + switch ($dobFormat) { + case 'Ym': + return [ + 'label' => 'Date of Birth', + 'value' => $dobDate->format('Y-m'), + ]; + case 'Ymd': + return [ + 'label' => 'Date of Birth', + 'value' => $dobDate->format('Y-m-d'), + ]; + default: + throw new Exception("Unhandled DoB format: $dobFormat"); + } + } + /** * Returns the expect date of confinement (due date) * of this candidate.