diff --git a/CHANGELOG.md b/CHANGELOG.md index 64f03b72389..b2088c4f3eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -148,6 +148,7 @@ the `Data` key instead of `$InstrumentName` (PR #7857) - Add psr/log to composer (PR #8109) - Fixed broken DB calls in `assign_missing_instruments` and `instruments` (PR #8162) - Add support for PHP 8.1 (PR #7989) +- Fix Project tab of Configuration module to give correct errors, and prevent saving without Alias (PR #8349) ### Modules #### API - Ability to use PSCID instead of the CandID in the candidates API (PR #8138) diff --git a/Makefile b/Makefile index acae120b91c..d7f7b8ff2f2 100755 --- a/Makefile +++ b/Makefile @@ -64,3 +64,5 @@ login: mri_violations: target=mri_violations npm run compile +dashboard: + target=dashboard npm run compile diff --git a/htdocs/survey.php b/htdocs/survey.php index eea41d879b8..0ec1823062e 100644 --- a/htdocs/survey.php +++ b/htdocs/survey.php @@ -86,7 +86,10 @@ function initialize() $this->loris = new \LORIS\LorisInstance( $DB, $config, - [] + [ + __DIR__ . "/../project/modules", + __DIR__ . "/../modules/", + ] ); $this->TestName = $DB->pselectOne( "SELECT Test_name FROM participant_accounts diff --git a/jsx/Form.js b/jsx/Form.js index f90e2b386b9..d32b558c768 100644 --- a/jsx/Form.js +++ b/jsx/Form.js @@ -1568,25 +1568,6 @@ class DateElement extends Component { if (this.props.maxYear === '' || this.props.maxYear === null) { maxYear = '9999'; } - let monthInputs = $('input[type=month][name=' + this.props.name+']'); - monthInputs.datepicker({ - dateFormat: 'yy-mm', - changeMonth: true, - changeYear: true, - yearRange: minYear + ':' + maxYear, - constrainInput: true, - onChangeMonthYear: (y, m, d) => { - // Update date in the input field - $(this).datepicker('setDate', new Date(y, m - 1, d.selectedDay)); - }, - onSelect: (dateText, picker) => { - this.props.onUserInput(this.props.name, dateText); - }, - }); - monthInputs.attr('placeholder', 'yyyy-mm'); - monthInputs.on('keydown paste', (e) => { - e.preventDefault(); - }); } // //////// START OF COPN OVERRIDE ////////// @@ -1738,8 +1719,8 @@ DateElement.propTypes = { label: PropTypes.string, value: PropTypes.string, id: PropTypes.string, - maxYear: PropTypes.string, - minYear: PropTypes.string, + maxYear: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + minYear: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), dateFormat: PropTypes.string, disabled: PropTypes.bool, required: PropTypes.bool, diff --git a/modules/acknowledgements/test/TestPlan.md b/modules/acknowledgements/test/TestPlan.md index 2642c2feb99..e59486f6679 100644 --- a/modules/acknowledgements/test/TestPlan.md +++ b/modules/acknowledgements/test/TestPlan.md @@ -5,10 +5,11 @@ 2. Access the acknowledgements module page, ensure that it renders.[Automation Testing] 3. Verify that a new record can not be added Without acknowledgements_edit permission [Manual Testing] 4. Check that each dropdown has the correct options.[Manual Testing] -5. Click "Clear Form" and ensure filters are reset to same state.[Automation Testing] +5. Click "Clear Filter" and ensure filters are reset the default state.[Automation Testing] 6. Verify that the "Save" button on the modal window form inserts a new record in the data table and that the data table is refreshed automatically.[Automation Testing] 7. Start entering information in the modal window form and close the modal window without saving. A message asking 'Are You Sure?' should appear to confirm that the form should indeed be closed without saving the information previously entered [Manual Testing] - +8. Modify the "citation_policy" setting in the LORIS configuration module and ensure that the citation policy + at the top of the page is modified. diff --git a/modules/api/php/endpoint.class.inc b/modules/api/php/endpoint.class.inc index 68aca5b707a..b7f6055f4ba 100644 --- a/modules/api/php/endpoint.class.inc +++ b/modules/api/php/endpoint.class.inc @@ -59,7 +59,9 @@ abstract class Endpoint extends LORISEndpoint implements RequestHandlerInterface $versions = $this->supportedVersions() ?? []; $version = $request->getAttribute("LORIS-API-Version") ?? "unknown"; if (!in_array($version, $versions)) { - return new \LORIS\Http\Response\JSON\BadRequest('Unsupported version'); + // If it's not supported by a version of the API, that means the + // endpoint for that version should be not found + return new \LORIS\Http\Response\JSON\NotFound('Unsupported version'); } return parent::process($request, $handler); diff --git a/modules/battery_manager/jsx/batteryManagerForm.js b/modules/battery_manager/jsx/batteryManagerForm.js index d0777635c62..133a9c4308b 100644 --- a/modules/battery_manager/jsx/batteryManagerForm.js +++ b/modules/battery_manager/jsx/batteryManagerForm.js @@ -67,8 +67,8 @@ class BatteryManagerForm extends Component { name="ageMinDays" label="Minimum age (days)" onUserInput={setTest} - min="0" - max="99999" + min={0} + max={99999} required={true} value={test.ageMinDays} errorMessage={errors.ageMinDays} @@ -78,8 +78,8 @@ class BatteryManagerForm extends Component { name="ageMaxDays" label="Maximum age (days)" onUserInput={setTest} - min="0" - max="99999" + min={0} + max={99999} required={true} value={test.ageMaxDays} errorMessage={errors.ageMaxDays} @@ -134,8 +134,8 @@ class BatteryManagerForm extends Component { label="Instrument Order" onUserInput={setTest} required={false} - min="0" - max="127" // max value allowed by default column type of instr_order + min={0} + max={127} // max value allowed by default column type of instr_order value={test.instrumentOrder} /> update( + $DB->unsafeUpdate( 'Config', ['Value' => $value], ['ID' => $key] @@ -108,7 +108,7 @@ } } // Add the new setting - $DB->insert( + $DB->unsafeInsert( 'Config', [ 'ConfigID' => $ConfigSettingsID, // FK to ConfigSettings. diff --git a/modules/configuration/ajax/updateProject.php b/modules/configuration/ajax/updateProject.php index 33424bf4653..795be816f38 100644 --- a/modules/configuration/ajax/updateProject.php +++ b/modules/configuration/ajax/updateProject.php @@ -44,18 +44,35 @@ exit; } + if (empty($_POST['Name']) + || empty($_POST['Alias']) + || strlen($_POST['Alias']) > 4 + ) { + http_response_code(400); + echo json_encode(['error' => 'Bad Request']); + exit; + } + $project = \Project::createNew($projectName, $projectAlias, $recTarget); $db->insert( 'user_project_rel', ["UserID"=>$user->getId(),"ProjectID"=>$project->getId()] ); } else { + if (empty($_POST['Name']) + || empty($_POST['Alias']) + || strlen($_POST['Alias']) > 4 + ) { + http_response_code(400); + echo json_encode(['error' => 'Bad Request']); + exit; + } + // Update Project fields $project = \Project::getProjectFromID(new \ProjectID($projectID)); $project->updateName($projectName); $project->updateAlias($projectAlias); $project->updateRecruitmentTarget($recTarget); - } // Cohort information isn't mandatory. If the array is empty, give an diff --git a/modules/configuration/js/project.js b/modules/configuration/js/project.js index 5ce45652a3b..e0d690aa61f 100644 --- a/modules/configuration/js/project.js +++ b/modules/configuration/js/project.js @@ -31,7 +31,15 @@ $(document).ready(function() { } var errorClosure = function(i, form) { - if (isNaN(recruitmentTarget)) { + if (!Name) { + return function () { + $(form.find(".saveStatus")).text("Failed to save, must enter a Project Name!").css({'color': 'red'}).fadeIn(500).delay(1000).fadeOut(500); + } + } else if (!Alias) { + return function () { + $(form.find(".saveStatus")).text("Failed to save, must enter an Alias!").css({'color': 'red'}).fadeIn(500).delay(1000).fadeOut(500); + } + } else if (isNaN(recruitmentTarget)) { return function () { $(form.find(".saveStatus")).text("Failed to save, recruitment target must be an integer!").css({'color': 'red'}).fadeIn(500).delay(1000).fadeOut(500); } @@ -39,7 +47,7 @@ $(document).ready(function() { return function () { $(form.find(".saveStatus")).text("Failed to save, Alias should be at most 4 characters long!").css({'color': 'red'}).fadeIn(500).delay(1000).fadeOut(500); } - }else { + } else { return function () { $(form.find(".saveStatus")).text("Failed to save, same name already exist!").css({'color': 'red'}).fadeIn(500).delay(1000).fadeOut(500); } diff --git a/modules/dashboard/.gitignore b/modules/dashboard/.gitignore new file mode 100644 index 00000000000..408f23218b0 --- /dev/null +++ b/modules/dashboard/.gitignore @@ -0,0 +1 @@ +js/welcome.js diff --git a/modules/dashboard/jsx/welcome.js b/modules/dashboard/jsx/welcome.js new file mode 100644 index 00000000000..13ea9300c53 --- /dev/null +++ b/modules/dashboard/jsx/welcome.js @@ -0,0 +1,13 @@ +import DOMPurify from 'dompurify'; + +window.addEventListener('load', () => { + fetch(loris.BaseURL + '/dashboard/projectdescription').then( (resp) => { + if (!resp.ok) { + throw new Error('Could not get project description'); + } + return resp.json(); + }).then( (json) => { + const el = document.getElementById('project-description'); + el.innerHTML = DOMPurify.sanitize(json.Description); + }).catch( (e) => console.error(e)); +}); diff --git a/modules/dashboard/php/projectdescription.class.inc b/modules/dashboard/php/projectdescription.class.inc new file mode 100644 index 00000000000..9af197c8545 --- /dev/null +++ b/modules/dashboard/php/projectdescription.class.inc @@ -0,0 +1,38 @@ +loris->getConfiguration()->getSetting('projectDescription'); + return new \LORIS\Http\Response\JSON\OK(['Description' => $desc]); + } + + /** + * The hasAccess call called by the Module class before loading the page. + * + * @param \User $user The user accessing the page + * + * @return bool + */ + function _hasAccess(\User $user) + { + return true; + } +} diff --git a/modules/dashboard/templates/welcomebody.tpl b/modules/dashboard/templates/welcomebody.tpl index 9eeda274f67..6b35be902b7 100644 --- a/modules/dashboard/templates/welcomebody.tpl +++ b/modules/dashboard/templates/welcomebody.tpl @@ -1,5 +1,4 @@

Welcome, {$username}.

Last login: {$last_login}

-{if !is_null($project_description)} -

{$project_description}

-{/if} +

+ diff --git a/modules/document_repository/help/document_repository.md b/modules/document_repository/help/document_repository.md index 3f11d97c8ec..1f442e4d54f 100644 --- a/modules/document_repository/help/document_repository.md +++ b/modules/document_repository/help/document_repository.md @@ -5,6 +5,6 @@ This module provides studies with a centralized location for important documents In the **Browse** tab, you can search for specific files using the **Selection Filter** section. Click on any file name in the data table to download. Click on any of the folders to reveal subfolders and files organized within. You can click on **Edit** or **Delete** in any cell, in those respective columns, to modify a file. Click on certain column headers to sort the list of documents. The **Filter globally** only works in the current directory (root folder only contains directories). The **Filter globally** will filter the file across the directories. -Navigate to the **Upload** tab to upload a file. Populate the fields and upload your file. Your newly uploaded file should now appear in the list within the Browse tab. +Navigate to the **Upload** tab to upload one or more files. Populate the fields and upload your files. Your newly uploaded files should now appear in the list within the Browse tab. If you need to add a new category or subcategory, you can do so in the **Category** tab. Populate the fields and click **Add Category**. diff --git a/modules/document_repository/test/TestPlan.md b/modules/document_repository/test/TestPlan.md index d1e569b07d9..bc1f69f29ef 100644 --- a/modules/document_repository/test/TestPlan.md +++ b/modules/document_repository/test/TestPlan.md @@ -6,7 +6,7 @@ [Manual Testing] 3. Check that the comments for a category are displayed properly as a tooltip. [Manual Testing] -4. Upload a file. +4. Upload one file. Upload multiple files at once. [Manual Testing] 5. User is able to delete a file if they have the "Delete files in Document Repository" permission or is the super user. [Manual Testing] @@ -29,13 +29,13 @@ [Manual Testing] 14. Edit a file in the repository: check that “Date Uploaded” date is updated. [Manual Testing] -15. Visit the My Preferences module and enable notifications for the document_repository. +15. Visit the "My Preferences" module and enable notifications for the document_repository. Make sure that you are notified for the follow changes: - Addition, deletion or modification of a file (by a user other than yourself) - Addition of a category (by a user other than yourself) [Manual Testing] 16. Try uploading a file that exceeds the max upload limit. Ensure that an error message occurs - when the server has detected that the file is too large. + when the server has detected that the file is too large. Do this again, with multiple files at once, to ensure proper individual error reporting for all files. [Manual Testing] ### Widget registration on the dashboard page diff --git a/modules/electrophysiology_uploader/help/electrophysiology_uploader.md b/modules/electrophysiology_uploader/help/electrophysiology_uploader.md new file mode 100644 index 00000000000..fcba1cc81f9 --- /dev/null +++ b/modules/electrophysiology_uploader/help/electrophysiology_uploader.md @@ -0,0 +1,13 @@ +# Electrophysiology Uploader + +This module allows you to upload EEG recordings and browse past uploads. + +In the **Browse** tab, you can use the *Selection Filter* section to search for existing uploads. + +In the data table, you can click any link in the *Upload Location* column to download the uploaded files. + +To upload recordings, click on the **Upload** tab. Select your file for upload and enter the required information about the recording. + +Note: The candidate must already be registered in LORIS and the visit label must be known for the study. + +Pay attention to the *Notes* for upload rules. Your dataset should be [BIDS-compliant](https://bids-specification.readthedocs.io). diff --git a/modules/electrophysiology_uploader/php/upload.class.inc b/modules/electrophysiology_uploader/php/upload.class.inc index 7d55372ae76..373564681da 100644 --- a/modules/electrophysiology_uploader/php/upload.class.inc +++ b/modules/electrophysiology_uploader/php/upload.class.inc @@ -46,7 +46,7 @@ class Upload extends \NDB_Page // Ensure GET or POST request. switch ($request->getMethod()) { case 'GET': - return $this->_handleGet($request); + return $this->_handleGET($request); case 'POST': return $this->_handlePOST($request); default: @@ -63,7 +63,7 @@ class Upload extends \NDB_Page * * @return ResponseInterface */ - private function _handleGet(ServerRequestInterface $request) : ResponseInterface + private function _handleGET(ServerRequestInterface $request) : ResponseInterface { $qParams = $request->getQueryParams(); if (!isset($qParams['upload_id'])) { @@ -244,6 +244,7 @@ class Upload extends \NDB_Page 'UploadDate' => date('Y-m-d H:i:s'), 'UploadLocation' => "$filename", 'SessionID' => $session['ID'], + 'Checksum' => $values['checksum'] ?? null, ]; $db->insert('electrophysiology_uploader', $saveValues); diff --git a/modules/examiner/templates/form_editExaminer.tpl b/modules/examiner/templates/form_editExaminer.tpl index 6b07bb6462d..2f637e4b10f 100644 --- a/modules/examiner/templates/form_editExaminer.tpl +++ b/modules/examiner/templates/form_editExaminer.tpl @@ -12,7 +12,7 @@ {/foreach}