diff --git a/modules/api/php/endpoints/candidates.class.inc b/modules/api/php/endpoints/candidates.class.inc index 64ae7d6c7e2..0cf6904debb 100644 --- a/modules/api/php/endpoints/candidates.class.inc +++ b/modules/api/php/endpoints/candidates.class.inc @@ -150,6 +150,10 @@ class Candidates extends Endpoint implements \LORIS\Middleware\ETagCalculator $candidate = \NDB_Factory::singleton()->candidate($candID); + if (!$candidate->isAccessibleBy($user)) { + return new \LORIS\Http\Response\JSON\Forbidden(); + } + $endpoint = new Candidate\Candidate($candidate); $pathparts = array_slice($pathparts, 2); @@ -241,8 +245,14 @@ class Candidates extends Endpoint implements \LORIS\Middleware\ETagCalculator $pscid, $project->getId() ); - } catch (\LorisException | \InvalidArgumentException $e) { - return new \LORIS\Http\Response\JSON\BadRequest($e->getMessage()); + } catch (\ConflictException $e) { + return new \LORIS\Http\Response\JSON\Conflict( + $e->getMessage() + ); + } catch (\Exception | \LorisException | \InvalidArgumentException $e) { + return new \LORIS\Http\Response\JSON\BadRequest( + $e->getMessage() + ); } $candidate = \NDB_Factory::singleton()->candidate($candid); diff --git a/modules/api/php/views/visit/flags.class.inc b/modules/api/php/views/visit/flags.class.inc index e1d88c84c2d..fa92f8d6e12 100644 --- a/modules/api/php/views/visit/flags.class.inc +++ b/modules/api/php/views/visit/flags.class.inc @@ -50,9 +50,9 @@ class Flags public function toArray(): array { $instrumentname = $this->_instrument->testName; - $instrumentdata = $this->_instrument->getInstanceData(); + $commentid = $this->_instrument->getCommentID() ?? ''; - $isDDE = strpos($instrumentdata['CommentID'], 'DDE_') === 0; + $isDDE = strpos($commentid, 'DDE_') === 0; $meta = [ 'Candidate' => $this->_timepoint->getCandID(), diff --git a/modules/api/php/views/visit/instrument.class.inc b/modules/api/php/views/visit/instrument.class.inc index f95f5f1f62d..3a764fd5081 100644 --- a/modules/api/php/views/visit/instrument.class.inc +++ b/modules/api/php/views/visit/instrument.class.inc @@ -51,8 +51,9 @@ class Instrument { $instrumentname = $this->_instrument->testName; $instrumentdata = $this->_instrument->getInstanceData(); + $commentid = $this->_instrument->getCommentID() ?? ''; - $isDDE = strpos($instrumentdata['CommentID'], 'DDE_') === 0; + $isDDE = strpos($commentid, 'DDE_') === 0; $meta = [ 'Candidate' => $this->_timepoint->getCandID(), diff --git a/modules/battery_manager/php/testoptionsendpoint.class.inc b/modules/battery_manager/php/testoptionsendpoint.class.inc index 4fa79395547..3d093bcd8f0 100644 --- a/modules/battery_manager/php/testoptionsendpoint.class.inc +++ b/modules/battery_manager/php/testoptionsendpoint.class.inc @@ -11,6 +11,7 @@ * @link https://www.github.com/aces/Loris/ */ namespace LORIS\battery_manager; +use LORIS\VisitController; use \Psr\Http\Message\ServerRequestInterface; use \Psr\Http\Message\ResponseInterface; @@ -58,11 +59,16 @@ class TestOptionsEndpoint extends \NDB_Page */ private function _getOptions() : array { + $visitController = new VisitController( + $this->loris->getDatabaseConnection() + ); return [ - 'instruments' => \Utility::getAllInstruments(), + 'instruments' => \NDB_BVL_Instrument::getInstrumentNamesList( + $this->loris + ), 'stages' => $this->_getStageList(), 'subprojects' => \Utility::getSubprojectList(null), - 'visits' => \Utility::getVisitList(), + 'visits' => $visitController->getVisitlabels(), 'sites' => \Utility::getSiteList(false), 'firstVisit' => $this->_getYesNoList(), 'active' => $this->_getYesNoList(), diff --git a/modules/imaging_uploader/jsx/UploadForm.js b/modules/imaging_uploader/jsx/UploadForm.js index 3b2fe0251a1..1561f009b20 100644 --- a/modules/imaging_uploader/jsx/UploadForm.js +++ b/modules/imaging_uploader/jsx/UploadForm.js @@ -66,10 +66,22 @@ class UploadForm extends Component { let ids = patientName.split('_'); formData.candID = ids[1]; formData.pSCID = ids[0]; - // visitLabel can contain underscores - // join the remaining elements of patientName and use as visitLabel + // visitLabel can contain underscores, filename can have suffix appended to PSCID_CandID_VisitLabel + // join the remaining elements of patientName and pattern match + // against each visit label. Use as visitLabel the best (longest) match ids.splice(0, 2); - formData.visitLabel = ids.join('_'); + const suffix = ids.join('_'); + const visitLabels = Object.keys(form.visitLabel.options); + let bestMatch = ''; + visitLabels.map((visitLabel) => { + if (suffix.match(visitLabel) !== null) { + // consider the first match only + if (suffix.match(visitLabel)[0].length > bestMatch.length) { + bestMatch = suffix.match(visitLabel)[0]; + } + } + }); + formData.visitLabel = bestMatch; } } @@ -81,10 +93,22 @@ class UploadForm extends Component { let ids = patientName.split('_'); formData.candID = ids[1]; formData.pSCID = ids[0]; - // visitLabel can contain underscores - // join the remaining elements of patientName and use as visitLabel + // visitLabel can contain underscores, filename can have suffix appended to PSCID_CandID_VisitLabel + // join the remaining elements of patientName and pattern match + // against each visit label. Use as visitLabel the best (longest) match ids.splice(0, 2); - formData.visitLabel = ids.join('_'); + const suffix = ids.join('_'); + const visitLabels = Object.keys(form.visitLabel.options); + let bestMatch = ''; + visitLabels.map((visitLabel) => { + if (suffix.match(visitLabel) !== null) { + // consider the first match only + if (suffix.match(visitLabel)[0].length > bestMatch.length) { + bestMatch = suffix.match(visitLabel)[0]; + } + } + }); + formData.visitLabel = bestMatch; } } diff --git a/modules/issue_tracker/php/edit.class.inc b/modules/issue_tracker/php/edit.class.inc index 448dac4997b..0e3a0680d52 100644 --- a/modules/issue_tracker/php/edit.class.inc +++ b/modules/issue_tracker/php/edit.class.inc @@ -447,11 +447,11 @@ class Edit extends \NDB_Page implements ETagCalculator $historyValues = $this->getChangedValues($issueValues, $issueID, $user); if (!empty($issueID)) { - $db->update('issues', $issueValues, ['issueID' => $issueID]); + $db->unsafeUpdate('issues', $issueValues, ['issueID' => $issueID]); } else { $issueValues['reporter'] = $user->getUsername(); $issueValues['dateCreated'] = date('Y-m-d H:i:s'); - $db->insert('issues', $issueValues); + $db->unsafeInsert('issues', $issueValues); $issueID = intval($db->getLastInsertId()); } @@ -815,7 +815,7 @@ class Edit extends \NDB_Page implements ETagCalculator 'issueID' => $issueID, 'addedBy' => $user->getUsername(), ]; - $db->insert('issues_history', $changedValues); + $db->unsafeInsert('issues_history', $changedValues); } } } @@ -838,7 +838,7 @@ class Edit extends \NDB_Page implements ETagCalculator 'addedBy' => $user->getUsername(), 'issueID' => $issueID, ]; - $db->insert('issues_comments', $commentValues); + $db->unsafeInsert('issues_comments', $commentValues); } } diff --git a/modules/survey_accounts/js/survey_accounts_helper.js b/modules/survey_accounts/js/survey_accounts_helper.js index 917e91eef37..ce3acbbad9b 100644 --- a/modules/survey_accounts/js/survey_accounts_helper.js +++ b/modules/survey_accounts/js/survey_accounts_helper.js @@ -16,11 +16,13 @@ $(document).ready(function () { // Handles cases where there was an error on the page and we're resubmitting var email2 = $("input[name=Email2]").val(); var email = $("input[name=Email]").val(); - if (email.length > 0 && email2.length > 0 && email == email2) - { - $('#email_survey').removeAttr('disabled'); - } else { - $('#email_survey').attr('disabled','disabled'); + if (email && email2) { + if (email.length > 0 && email2.length > 0 && email == email2) + { + $('#email_survey').removeAttr('disabled'); + } else { + $('#email_survey').attr('disabled','disabled'); + } } // Reset Test_name so that the template can be loaded by ajax below $("select[name=Test_name]").val(""); @@ -93,7 +95,7 @@ $(document).ready(function () { $("#emailContent").val(content); } ); - + }); }); diff --git a/modules/survey_accounts/jsx/surveyAccountsIndex.js b/modules/survey_accounts/jsx/surveyAccountsIndex.js index 0fac30465de..7241e266873 100644 --- a/modules/survey_accounts/jsx/surveyAccountsIndex.js +++ b/modules/survey_accounts/jsx/surveyAccountsIndex.js @@ -112,7 +112,11 @@ class SurveyAccountsIndex extends Component { options: options.instruments, }}, {label: 'URL', show: true}, - {label: 'Status', show: true}, + {label: 'Status', show: true, filter: { + name: 'Status', + type: 'select', + options: options.statusOptions, + }}, ]; const addSurvey = () => { location.href='/survey_accounts/addSurvey/'; diff --git a/modules/survey_accounts/php/addsurvey.class.inc b/modules/survey_accounts/php/addsurvey.class.inc index 7bd74d60ae9..17e23330d55 100644 --- a/modules/survey_accounts/php/addsurvey.class.inc +++ b/modules/survey_accounts/php/addsurvey.class.inc @@ -162,8 +162,9 @@ class AddSurvey extends \NDB_Form ]; } } - - if ($_REQUEST['fire_away'] !== 'Create survey') { + if (!isset($_REQUEST['fire_away']) + || ($_REQUEST['fire_away'] !== 'Create survey') + ) { if (!filter_var( $values['Email'], FILTER_VALIDATE_EMAIL @@ -241,11 +242,11 @@ class AddSurvey extends \NDB_Form 'CommentID' => $commentID, ] ); + $this->tpl_data['success'] = true; } catch (\DatabaseException $e) { error_log($e->getMessage()); $this->tpl_data['success'] = false; } - $this->tpl_data['success'] = true; if ($email && ($values['send_email'] == 'true')) { $config = \NDB_Config::singleton(); @@ -291,7 +292,7 @@ class AddSurvey extends \NDB_Form "Instrument", array_merge( ['' => ''], - \Utility::getDirectInstruments() + \NDB_BVL_Instrument::getDirectEntryInstrumentNamesList($this->loris) ) ); $this->addBasicText("Email", "Email address"); diff --git a/modules/survey_accounts/php/survey_accounts.class.inc b/modules/survey_accounts/php/survey_accounts.class.inc index 9154921e4a1..7256ccc759e 100644 --- a/modules/survey_accounts/php/survey_accounts.class.inc +++ b/modules/survey_accounts/php/survey_accounts.class.inc @@ -74,14 +74,22 @@ class Survey_Accounts extends \DataFrameworkMenu */ public function getFieldOptions() : array { + $statusOptions = [ + 'Created' => 'Created', + 'Sent' => 'Sent', + 'In Progress' => 'In Progress', + 'Complete' => 'Complete', + ]; + $instruments = \NDB_BVL_Instrument::getDirectEntryInstrumentNamesList( $this->loris ); return [ - 'visits' => \Utility::getVisitList(), - 'instruments' => $instruments, + 'visits' => \Utility::getVisitList(), + 'instruments' => $instruments, + 'statusOptions' => $statusOptions, ]; } diff --git a/php/exceptions/ConflictException.class.inc b/php/exceptions/ConflictException.class.inc new file mode 100644 index 00000000000..0c5ef1b0bfa --- /dev/null +++ b/php/exceptions/ConflictException.class.inc @@ -0,0 +1,11 @@ +feedbackThread->getSummaryOfThreads(); $this->tpl_data['thread_summary_headers'] = json_encode($summary); - $field_names = Utility::getSourcefields($_REQUEST['test_name'] ?? ''); + $test_name = ''; + if (array_key_exists('test_name', $_REQUEST)) { + $test_name = $_REQUEST['test_name']; + } else if (array_key_exists('lorispath', $_REQUEST)) { + $test_name = preg_split("#/#", $_REQUEST['lorispath'])[1] ?? ''; + } + + // Get field names + $field_names = Utility::getSourcefields($test_name); $fields = []; $fields['Across All Fields'] = 'Across All Fields'; foreach ($field_names as $field_name) { diff --git a/php/libraries/Candidate.class.inc b/php/libraries/Candidate.class.inc index cf987cf047b..cf71013f0e9 100644 --- a/php/libraries/Candidate.class.inc +++ b/php/libraries/Candidate.class.inc @@ -223,6 +223,7 @@ class Candidate implements \LORIS\StudyEntities\AccessibleResource, ProjectID $registrationProjectID = null ): CandID { $factory = NDB_Factory::singleton(); + $db = $factory->database(); $site = \Site::singleton($centerID); @@ -264,6 +265,25 @@ class Candidate implements \LORIS\StudyEntities\AccessibleResource, ); } + // check pscid uniqueness + $existing = $db->pselectOne( + 'SELECT + COUNT(*) + FROM candidate + WHERE PSCID = :v_pscid + GROUP BY + PSCID + ', + ['v_pscid' => $PSCID] + ); + + if ($existing > 0) { + throw new \ConflictException( + "PSCID must be unique", + PSCID_NOT_UNIQUE + ); + } + // check pscid structure if (!Candidate::validatePSCID( $PSCID, diff --git a/php/libraries/LorisForm.class.inc b/php/libraries/LorisForm.class.inc index bc29b94bc18..8a3211d37e8 100644 --- a/php/libraries/LorisForm.class.inc +++ b/php/libraries/LorisForm.class.inc @@ -1592,6 +1592,7 @@ class LorisForm $checked = ''; $value = ''; $disabled = ''; + $required = ''; if (!empty($val)) { $checked = 'checked="checked"'; } @@ -1601,6 +1602,9 @@ class LorisForm if (isset($el['disabled']) || $this->frozen) { $disabled = 'disabled'; } + if (isset($el['required'])) { + $required = 'required'; + } /// XXX: There seems to be a problem when using   to separate the // checkbox from the label. Both Firefox and Chrome will still put a // linebreak between the space and the checkbox. Instead, we wrap use @@ -1609,7 +1613,7 @@ class LorisForm // label it's still allowed to have linebreaks. return "" + . "$disabled $required/>" . " $el[label]"; } diff --git a/php/libraries/LorisFormDictionaryImpl.class.inc b/php/libraries/LorisFormDictionaryImpl.class.inc index 9fae78137c8..f6290a60407 100644 --- a/php/libraries/LorisFormDictionaryImpl.class.inc +++ b/php/libraries/LorisFormDictionaryImpl.class.inc @@ -131,6 +131,7 @@ trait LorisFormDictionaryImpl $t = new \LORIS\Data\Types\StringType(255); break; case 'header': + case 'hidden': continue 2; default: throw new \LorisException( diff --git a/php/libraries/NDB_BVL_Instrument_LINST.class.inc b/php/libraries/NDB_BVL_Instrument_LINST.class.inc index 495843a4358..1c196e3753d 100644 --- a/php/libraries/NDB_BVL_Instrument_LINST.class.inc +++ b/php/libraries/NDB_BVL_Instrument_LINST.class.inc @@ -734,14 +734,14 @@ class NDB_BVL_Instrument_LINST extends \NDB_BVL_Instrument case 'numeric': if ($addElements) { $this->addNumericElement($pieces[1], $pieces[2]); - $this->dictionary[] = new DictionaryItem( - $this->testName."_".$pieces[1], - $pieces[2], - $scope, - new IntegerType(), - new Cardinality(Cardinality::SINGLE), - ); } + $this->dictionary[] = new DictionaryItem( + $this->testName."_".$pieces[1], + $pieces[2], + $scope, + new IntegerType(), + new Cardinality(Cardinality::SINGLE), + ); if ($firstFieldOfPage) { $this->_requiredElements[] = $fieldname; $firstFieldOfPage = false; diff --git a/php/libraries/VisitController.class.inc b/php/libraries/VisitController.class.inc index aec1ca84cd7..69a5563f28e 100644 --- a/php/libraries/VisitController.class.inc +++ b/php/libraries/VisitController.class.inc @@ -54,22 +54,31 @@ class VisitController return array_map( function ($row) { return new \LORIS\Visit( - $row['name'], - $row['ID'] + $row['VisitName'], + $row['VisitLabel'] ); }, $this->database->pselect( - 'SELECT - v.VisitID as "ID", v.VisitName as "name" - FROM - visit v -ORDER BY ID - ', + 'SELECT VisitName, VisitLabel FROM visit ORDER BY VisitName', [] ) ); } + /** + * Get an associative array of the type + * + * @return array + */ + public function getVisitlabels(): array + { + $visitLabels = []; + foreach ($this->getAllVisits() as $visitObj) { + $visitLabels[$visitObj->getName()] = $visitObj->getLabel(); + } + return $visitLabels; + } + /** * Retruns a VisitID by name * diff --git a/test/unittests/LorisForms_Test.php b/test/unittests/LorisForms_Test.php index 276616dbb56..61b269c357e 100644 --- a/test/unittests/LorisForms_Test.php +++ b/test/unittests/LorisForms_Test.php @@ -1336,7 +1336,7 @@ function testCheckboxHTMLWithNoAttributes() $this->form->addCheckbox("abc", "Hello", []); $this->assertEquals( " Hello", + "type=\"checkbox\" /> Hello", $this->form->checkboxHTML($this->form->form['abc']) ); } @@ -1358,7 +1358,7 @@ function testCheckboxHTMLWithAttributesSet() $this->assertEquals( " Hello", + " value=\"value1\" disabled /> Hello", $this->form->checkboxHTML($this->form->form['abc']) ); } diff --git a/tools/CouchDB_MRI_Importer.php b/tools/CouchDB_MRI_Importer.php index c58c28f73c5..a8d3dde87ec 100755 --- a/tools/CouchDB_MRI_Importer.php +++ b/tools/CouchDB_MRI_Importer.php @@ -249,7 +249,10 @@ function _addMRIHeaderInfo($FileObj, $scan_type) $FileObj, 'acquisition_date' ); - $header['FileInsertDate_'.$type] = $FileObj->getParameter('InsertTime'); + $header['FileInsertDate_'.$type] = date( + 'Y-m-d', + $FileObj->getParameter('InsertTime') + ); $header['SeriesDescription_'.$type] = $FileObj->getParameter($ser_desc); $header['SeriesNumber_'.$type] = $FileObj->getParameter($ser_num); $header['EchoTime_'.$type] = number_format( @@ -438,7 +441,7 @@ function updateCandidateDocs($data, $ScanTypes) $row['PSCID'], $row['Visit_label'], ]; - $docid = 'MRI_Files:' . join($identifier, '_'); + $docid = 'MRI_Files:' . join('_', $identifier); unset($doc['PSCID']); unset($doc['Visit_label']); unset($doc['SessionID']);